mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 15:41:02 +00:00
Tunable Justification Generator (#835)
* Add way to create tunable GRANDPA justifications * Use new function in tests * Allow multiple authorities on a single fork * Only store pre-commit targets instead of full ancestry chains * Rename precommit_header to be more generic * Push new digest item instead of overriding entire digest * Ensure that we generate chains with non-zero length * Extract justification creation parameters into struct * Appease Clippy
This commit is contained in:
committed by
Bastian Köcher
parent
4105575794
commit
1c7b5d1b30
@@ -522,7 +522,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{run_test, test_header, Origin, TestHash, TestHeader, TestNumber, TestRuntime};
|
||||
use bp_test_utils::{
|
||||
authority_list, keyring, make_justification_for_header,
|
||||
authority_list, make_default_justification, make_justification_for_header, JustificationGeneratorParams,
|
||||
Keyring::{Alice, Bob},
|
||||
};
|
||||
use codec::Encode;
|
||||
@@ -551,10 +551,7 @@ mod tests {
|
||||
|
||||
fn submit_finality_proof(header: u8) -> frame_support::dispatch::DispatchResultWithPostInfo {
|
||||
let header = test_header(header.into());
|
||||
|
||||
let set_id = 1;
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
|
||||
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification)
|
||||
}
|
||||
@@ -726,9 +723,11 @@ mod tests {
|
||||
|
||||
let header = test_header(1);
|
||||
|
||||
let set_id = 2;
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let params = JustificationGeneratorParams::<TestHeader> {
|
||||
set_id: 2,
|
||||
..Default::default()
|
||||
};
|
||||
let justification = make_justification_for_header(params).encode();
|
||||
|
||||
assert_err!(
|
||||
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification,),
|
||||
@@ -802,9 +801,7 @@ mod tests {
|
||||
header.digest = change_log(0);
|
||||
|
||||
// Create a valid justification for the header
|
||||
let set_id = 1;
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
|
||||
// Let's import our test header
|
||||
assert_ok!(Module::<TestRuntime>::submit_finality_proof(
|
||||
@@ -836,9 +833,7 @@ mod tests {
|
||||
header.digest = change_log(1);
|
||||
|
||||
// Create a valid justification for the header
|
||||
let set_id = 1;
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
|
||||
// Should not be allowed to import this header
|
||||
assert_err!(
|
||||
@@ -859,9 +854,7 @@ mod tests {
|
||||
header.digest = forced_change_log(0);
|
||||
|
||||
// Create a valid justification for the header
|
||||
let set_id = 1;
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
|
||||
// Should not be allowed to import this header
|
||||
assert_err!(
|
||||
|
||||
@@ -60,7 +60,7 @@ use crate::verifier::*;
|
||||
use crate::{BestFinalized, BestHeight, BridgeStorage, NextScheduledChange, PalletStorage};
|
||||
use bp_header_chain::AuthoritySet;
|
||||
use bp_test_utils::{
|
||||
authority_list, keyring, make_justification_for_header,
|
||||
authority_list, make_default_justification,
|
||||
Keyring::{Alice, Bob},
|
||||
};
|
||||
use codec::Encode;
|
||||
@@ -457,9 +457,7 @@ where
|
||||
// `grandpa_round`).
|
||||
//
|
||||
// See for more: https://github.com/paritytech/parity-bridges-common/issues/430
|
||||
let grandpa_round = 1;
|
||||
let set_id = 1;
|
||||
let justification = make_justification_for_header(header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(header).encode();
|
||||
|
||||
let res = verifier
|
||||
.import_finality_proof(header.hash(), justification.into())
|
||||
|
||||
@@ -343,7 +343,7 @@ mod tests {
|
||||
use crate::mock::*;
|
||||
use crate::{BestFinalized, BestHeight, HeaderId, ImportedHeaders, PalletStorage};
|
||||
use bp_test_utils::{
|
||||
authority_list, keyring, make_justification_for_header,
|
||||
authority_list, make_default_justification,
|
||||
Keyring::{Alice, Bob},
|
||||
};
|
||||
use codec::Encode;
|
||||
@@ -605,8 +605,7 @@ mod tests {
|
||||
assert_eq!(storage.best_headers().len(), 1);
|
||||
|
||||
// Now lets finalize our best header
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), justification.into()));
|
||||
|
||||
// Our best header should only appear once in the list of best headers
|
||||
@@ -729,8 +728,7 @@ mod tests {
|
||||
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, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
@@ -754,8 +752,7 @@ mod tests {
|
||||
let authority_set = AuthoritySet { authorities, set_id };
|
||||
storage.update_current_authority_set(authority_set);
|
||||
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
@@ -798,8 +795,7 @@ mod tests {
|
||||
// This header enacts an authority set change upon finalization
|
||||
let header = test_header(2);
|
||||
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &keyring()).encode();
|
||||
let justification = make_default_justification(&header).encode();
|
||||
|
||||
// Schedule a change at the height of our header
|
||||
let set_id = 2;
|
||||
|
||||
@@ -16,14 +16,55 @@
|
||||
|
||||
//! Tests for Grandpa Justification code.
|
||||
|
||||
use bp_header_chain::justification::{verify_justification, Error, GrandpaJustification};
|
||||
use bp_header_chain::justification::{verify_justification, Error};
|
||||
use bp_test_utils::Keyring::*;
|
||||
use bp_test_utils::*;
|
||||
use codec::Encode;
|
||||
|
||||
type TestHeader = sp_runtime::testing::Header;
|
||||
|
||||
fn make_justification_for_header_1() -> GrandpaJustification<TestHeader> {
|
||||
make_justification_for_header(&test_header(1), TEST_GRANDPA_ROUND, TEST_GRANDPA_SET_ID, &keyring())
|
||||
#[test]
|
||||
fn valid_justification_accepted() {
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: vec![(Alice, 1), (Bob, 1), (Charlie, 1), (Dave, 1), (Eve, 1)],
|
||||
depth: 5,
|
||||
forks: 5,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&make_justification_for_header::<TestHeader>(params).encode()
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_justification_accepted_with_single_fork() {
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: vec![(Alice, 1), (Bob, 1), (Charlie, 1), (Dave, 1), (Eve, 1)],
|
||||
depth: 5,
|
||||
forks: 1,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&make_justification_for_header::<TestHeader>(params).encode()
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -41,7 +82,7 @@ fn justification_with_invalid_target_rejected() {
|
||||
header_id::<TestHeader>(2),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&make_justification_for_header_1().encode(),
|
||||
&make_default_justification::<TestHeader>(&test_header(1)).encode(),
|
||||
),
|
||||
Err(Error::InvalidJustificationTarget),
|
||||
);
|
||||
@@ -49,7 +90,7 @@ fn justification_with_invalid_target_rejected() {
|
||||
|
||||
#[test]
|
||||
fn justification_with_invalid_commit_rejected() {
|
||||
let mut justification = make_justification_for_header_1();
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.commit.precommits.clear();
|
||||
|
||||
assert_eq!(
|
||||
@@ -65,7 +106,7 @@ fn justification_with_invalid_commit_rejected() {
|
||||
|
||||
#[test]
|
||||
fn justification_with_invalid_authority_signature_rejected() {
|
||||
let mut justification = make_justification_for_header_1();
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.commit.precommits[0].signature = Default::default();
|
||||
|
||||
assert_eq!(
|
||||
@@ -81,7 +122,7 @@ fn justification_with_invalid_authority_signature_rejected() {
|
||||
|
||||
#[test]
|
||||
fn justification_with_invalid_precommit_ancestry() {
|
||||
let mut justification = make_justification_for_header_1();
|
||||
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
|
||||
justification.votes_ancestries.push(test_header(10));
|
||||
|
||||
assert_eq!(
|
||||
@@ -96,14 +137,24 @@ fn justification_with_invalid_precommit_ancestry() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_justification_accepted() {
|
||||
fn justification_is_invalid_if_we_dont_meet_threshold() {
|
||||
// Need at least three authorities to sign off or else the voter set threshold can't be reached
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: vec![(Alice, 1), (Bob, 1)],
|
||||
depth: 2,
|
||||
forks: 2,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&voter_set(),
|
||||
&make_justification_for_header_1().encode(),
|
||||
&make_justification_for_header::<TestHeader>(params).encode()
|
||||
),
|
||||
Ok(()),
|
||||
Err(Error::InvalidJustificationCommit),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,29 +31,95 @@ use sp_std::prelude::*;
|
||||
pub const TEST_GRANDPA_ROUND: u64 = 1;
|
||||
pub const TEST_GRANDPA_SET_ID: SetId = 1;
|
||||
|
||||
/// Get a valid Grandpa justification for a header given a Grandpa round, authority set ID, and
|
||||
/// authority list.
|
||||
pub fn make_justification_for_header<H: HeaderT>(
|
||||
header: &H,
|
||||
round: u64,
|
||||
set_id: SetId,
|
||||
authorities: &[(Keyring, AuthorityWeight)],
|
||||
) -> GrandpaJustification<H> {
|
||||
/// Configuration parameters when generating test GRANDPA justifications.
|
||||
pub struct JustificationGeneratorParams<H> {
|
||||
/// The header which we want to finalize.
|
||||
pub header: H,
|
||||
/// The GRANDPA round number for the current authority set.
|
||||
pub round: u64,
|
||||
/// The current authority set ID.
|
||||
pub set_id: SetId,
|
||||
/// The current GRANDPA authority set.
|
||||
pub authorities: Vec<(Keyring, AuthorityWeight)>,
|
||||
/// The number of headers included in our justification's vote ancestries.
|
||||
pub depth: u32,
|
||||
/// The number of forks, and thus the number of pre-commits in our justification.
|
||||
pub forks: u32,
|
||||
}
|
||||
|
||||
impl<H: HeaderT> Default for JustificationGeneratorParams<H> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
header: test_header(One::one()),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: keyring(),
|
||||
depth: 2,
|
||||
forks: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a valid GRANDPA justification with sensible defaults
|
||||
pub fn make_default_justification<H: HeaderT>(header: &H) -> GrandpaJustification<H> {
|
||||
let params = JustificationGeneratorParams::<H> {
|
||||
header: header.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
make_justification_for_header(params)
|
||||
}
|
||||
|
||||
/// Generate justifications in a way where we are able to tune the number of pre-commits
|
||||
/// and vote ancestries which are included in the justification.
|
||||
///
|
||||
/// This is useful for benchmarkings where we want to generate valid justifications with
|
||||
/// a specific number of pre-commits (tuned with the "forks" parameter) and/or a specific
|
||||
/// number of vote ancestries (tuned with the "depth" parameter).
|
||||
///
|
||||
/// Note: This needs at least three authorities or else the verifier will complain about
|
||||
/// being given an invalid commit.
|
||||
pub fn make_justification_for_header<H: HeaderT>(params: JustificationGeneratorParams<H>) -> GrandpaJustification<H> {
|
||||
let JustificationGeneratorParams {
|
||||
header,
|
||||
round,
|
||||
set_id,
|
||||
authorities,
|
||||
depth,
|
||||
forks,
|
||||
} = params;
|
||||
|
||||
let (target_hash, target_number) = (header.hash(), *header.number());
|
||||
let mut precommits = vec![];
|
||||
let mut votes_ancestries = vec![];
|
||||
|
||||
// We want to make sure that the header included in the vote ancestries
|
||||
// is actually related to our target header
|
||||
let mut precommit_header = test_header::<H>(target_number + One::one());
|
||||
precommit_header.set_parent_hash(target_hash);
|
||||
assert!(depth != 0, "Can't have a chain of zero length.");
|
||||
assert!(
|
||||
forks as usize <= authorities.len(),
|
||||
"If we have more forks than authorities we can't create valid pre-commits for all the forks."
|
||||
);
|
||||
|
||||
let mut unsigned_precommits = vec![];
|
||||
for i in 0..forks {
|
||||
let chain = generate_chain(i as u8, depth, &header);
|
||||
|
||||
// We don't include our finality target header in the vote ancestries
|
||||
for child in &chain[1..] {
|
||||
votes_ancestries.push(child.clone());
|
||||
}
|
||||
|
||||
// The header we need to use when pre-commiting is the one at the highest height
|
||||
// on our chain.
|
||||
let precommit_candidate = chain.last().map(|h| (h.hash(), *h.number())).unwrap();
|
||||
unsigned_precommits.push(precommit_candidate);
|
||||
}
|
||||
|
||||
for (i, (id, _weight)) in authorities.iter().enumerate() {
|
||||
// Assign authorities to sign pre-commits in a round-robin fashion
|
||||
let target = unsigned_precommits[i % forks as usize];
|
||||
let precommit = signed_precommit::<H>(&id, target, round, set_id);
|
||||
|
||||
// I'm using the same header for all the voters since it doesn't matter as long
|
||||
// as they all vote on blocks _ahead_ of the one we're interested in finalizing
|
||||
for (id, _weight) in authorities.iter() {
|
||||
let precommit = signed_precommit::<H>(id, (precommit_header.hash(), *precommit_header.number()), round, set_id);
|
||||
precommits.push(precommit);
|
||||
votes_ancestries.push(precommit_header.clone());
|
||||
}
|
||||
|
||||
GrandpaJustification {
|
||||
@@ -67,6 +133,29 @@ pub fn make_justification_for_header<H: HeaderT>(
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_chain<H: HeaderT>(fork_id: u8, depth: u32, ancestor: &H) -> Vec<H> {
|
||||
let mut headers = vec![ancestor.clone()];
|
||||
|
||||
for i in 1..depth {
|
||||
let parent = &headers[(i - 1) as usize];
|
||||
let (hash, num) = (parent.hash(), *parent.number());
|
||||
|
||||
let mut header = test_header::<H>(num + One::one());
|
||||
header.set_parent_hash(hash);
|
||||
|
||||
// Modifying the digest so headers at the same height but in different forks have different
|
||||
// hashes
|
||||
header
|
||||
.digest_mut()
|
||||
.logs
|
||||
.push(sp_runtime::DigestItem::Other(vec![fork_id]));
|
||||
|
||||
headers.push(header);
|
||||
}
|
||||
|
||||
headers
|
||||
}
|
||||
|
||||
fn signed_precommit<H: HeaderT>(
|
||||
signer: &Keyring,
|
||||
target: (H::Hash, H::Number),
|
||||
|
||||
Reference in New Issue
Block a user