Include a reference to the validation data in the candidate descriptor (#1442)

* rename GlobalValidationSchedule to GlobalValidationData

* guide: update candidate descriptor to contain validation data hash

* guide: add note in inclusion module about checking validation data hash

* primitives: update CandidateDescriptor to contain new hash

* fix payload computation

* add helpers for computing validation data to runtime modules

* guide: note routines

* inclusion: check validation data hash and fix local_validation_data bug

* add a case to candidate_checks and improve that test substantially

* bump versions

* address review comments

* add a test for including code upgrade

* bump kusama version

* bump westend & polkadot versions
This commit is contained in:
Robert Habermeier
2020-07-23 15:02:24 -04:00
committed by GitHub
parent 1ed17cd467
commit 09f602f8de
26 changed files with 434 additions and 175 deletions
+5 -5
View File
@@ -58,7 +58,7 @@ use sp_core::Pair;
use polkadot_primitives::v0::{ use polkadot_primitives::v0::{
BlockId, Hash, Block, DownwardMessage, BlockId, Hash, Block, DownwardMessage,
BlockData, DutyRoster, HeadData, Id as ParaId, BlockData, DutyRoster, HeadData, Id as ParaId,
PoVBlock, ValidatorId, CollatorPair, LocalValidationData, GlobalValidationSchedule, PoVBlock, ValidatorId, CollatorPair, LocalValidationData, GlobalValidationData,
Collation, CollationInfo, collator_signature_payload, Collation, CollationInfo, collator_signature_payload,
}; };
use polkadot_cli::{ use polkadot_cli::{
@@ -148,7 +148,7 @@ pub trait ParachainContext: Clone {
fn produce_candidate( fn produce_candidate(
&mut self, &mut self,
relay_parent: Hash, relay_parent: Hash,
global_validation: GlobalValidationSchedule, global_validation: GlobalValidationData,
local_validation: LocalValidationData, local_validation: LocalValidationData,
downward_messages: Vec<DownwardMessage>, downward_messages: Vec<DownwardMessage>,
) -> Self::ProduceCandidate; ) -> Self::ProduceCandidate;
@@ -158,7 +158,7 @@ pub trait ParachainContext: Clone {
pub async fn collate<P>( pub async fn collate<P>(
relay_parent: Hash, relay_parent: Hash,
local_id: ParaId, local_id: ParaId,
global_validation: GlobalValidationSchedule, global_validation: GlobalValidationData,
local_validation_data: LocalValidationData, local_validation_data: LocalValidationData,
downward_messages: Vec<DownwardMessage>, downward_messages: Vec<DownwardMessage>,
mut para_context: P, mut para_context: P,
@@ -315,7 +315,7 @@ fn build_collator_service<P, C, R, Extrinsic>(
let work = future::lazy(move |_| { let work = future::lazy(move |_| {
let api = client.runtime_api(); let api = client.runtime_api();
let global_validation = try_fr!(api.global_validation_schedule(&id)); let global_validation = try_fr!(api.global_validation_data(&id));
let local_validation = match try_fr!(api.local_validation_data(&id, para_id)) { let local_validation = match try_fr!(api.local_validation_data(&id, para_id)) {
Some(local_validation) => local_validation, Some(local_validation) => local_validation,
None => return future::Either::Left(future::ok(())), None => return future::Either::Left(future::ok(())),
@@ -477,7 +477,7 @@ mod tests {
fn produce_candidate( fn produce_candidate(
&mut self, &mut self,
_relay_parent: Hash, _relay_parent: Hash,
_global: GlobalValidationSchedule, _global: GlobalValidationData,
_local_validation: LocalValidationData, _local_validation: LocalValidationData,
_: Vec<DownwardMessage>, _: Vec<DownwardMessage>,
) -> Self::ProduceCandidate { ) -> Self::ProduceCandidate {
+2 -2
View File
@@ -21,7 +21,7 @@ use polkadot_primitives::v0::{
Block, Block,
Id as ParaId, Chain, DutyRoster, ParachainHost, ValidatorId, Id as ParaId, Chain, DutyRoster, ParachainHost, ValidatorId,
Retriable, CollatorId, AbridgedCandidateReceipt, Retriable, CollatorId, AbridgedCandidateReceipt,
GlobalValidationSchedule, LocalValidationData, ErasureChunk, SigningContext, GlobalValidationData, LocalValidationData, ErasureChunk, SigningContext,
PoVBlock, BlockData, ValidationCode, PoVBlock, BlockData, ValidationCode,
}; };
use polkadot_validation::{SharedTable, TableRouter}; use polkadot_validation::{SharedTable, TableRouter};
@@ -180,7 +180,7 @@ sp_api::mock_impl_runtime_apis! {
Some(ValidationCode(Vec::new())) Some(ValidationCode(Vec::new()))
} }
fn global_validation_schedule() -> GlobalValidationSchedule { fn global_validation_data() -> GlobalValidationData {
Default::default() Default::default()
} }
+10 -10
View File
@@ -606,7 +606,7 @@ impl CandidateBackingJob {
with_commitments: impl FnOnce(CandidateCommitments) -> Result<T, E>, with_commitments: impl FnOnce(CandidateCommitments) -> Result<T, E>,
) -> Result<Result<T, E>, Error> { ) -> Result<Result<T, E>, Error> {
let omitted_validation = OmittedValidationData { let omitted_validation = OmittedValidationData {
global_validation: outputs.global_validation_schedule, global_validation: outputs.global_validation_data,
local_validation: outputs.local_validation_data, local_validation: outputs.local_validation_data,
}; };
@@ -773,7 +773,7 @@ mod tests {
use futures::{executor, future, Future}; use futures::{executor, future, Future};
use polkadot_primitives::v1::{ use polkadot_primitives::v1::{
AssignmentKind, BlockData, CandidateCommitments, CollatorId, CoreAssignment, CoreIndex, AssignmentKind, BlockData, CandidateCommitments, CollatorId, CoreAssignment, CoreIndex,
LocalValidationData, GlobalValidationSchedule, GroupIndex, HeadData, LocalValidationData, GlobalValidationData, GroupIndex, HeadData,
ValidatorPair, ValidityAttestation, ValidatorPair, ValidityAttestation,
}; };
use polkadot_subsystem::{ use polkadot_subsystem::{
@@ -792,7 +792,7 @@ mod tests {
keystore: KeyStorePtr, keystore: KeyStorePtr,
validators: Vec<Sr25519Keyring>, validators: Vec<Sr25519Keyring>,
validator_public: Vec<ValidatorId>, validator_public: Vec<ValidatorId>,
global_validation_schedule: GlobalValidationSchedule, global_validation_data: GlobalValidationData,
local_validation_data: LocalValidationData, local_validation_data: LocalValidationData,
roster: SchedulerRoster, roster: SchedulerRoster,
head_data: HashMap<ParaId, HeadData>, head_data: HashMap<ParaId, HeadData>,
@@ -877,7 +877,7 @@ mod tests {
validation_code_hash: Default::default(), validation_code_hash: Default::default(),
}; };
let global_validation_schedule = GlobalValidationSchedule { let global_validation_data = GlobalValidationData {
max_code_size: 1000, max_code_size: 1000,
max_head_data_size: 1000, max_head_data_size: 1000,
block_number: Default::default(), block_number: Default::default(),
@@ -891,7 +891,7 @@ mod tests {
roster, roster,
head_data, head_data,
local_validation_data, local_validation_data,
global_validation_schedule, global_validation_data,
signing_context, signing_context,
relay_parent, relay_parent,
} }
@@ -921,7 +921,7 @@ mod tests {
fn make_erasure_root(test: &TestState, pov: PoV) -> Hash { fn make_erasure_root(test: &TestState, pov: PoV) -> Hash {
let omitted_validation = OmittedValidationData { let omitted_validation = OmittedValidationData {
global_validation: test.global_validation_schedule.clone(), global_validation: test.global_validation_data.clone(),
local_validation: test.local_validation_data.clone(), local_validation: test.local_validation_data.clone(),
}; };
@@ -1048,7 +1048,7 @@ mod tests {
) if pov == pov && &c == candidate.descriptor() => { ) if pov == pov && &c == candidate.descriptor() => {
tx.send(Ok( tx.send(Ok(
ValidationResult::Valid(ValidationOutputs { ValidationResult::Valid(ValidationOutputs {
global_validation_schedule: test_state.global_validation_schedule, global_validation_data: test_state.global_validation_data,
local_validation_data: test_state.local_validation_data, local_validation_data: test_state.local_validation_data,
head_data: expected_head_data.clone(), head_data: expected_head_data.clone(),
upward_messages: Vec::new(), upward_messages: Vec::new(),
@@ -1160,7 +1160,7 @@ mod tests {
) if pov == pov && &c == candidate_a.descriptor() => { ) if pov == pov && &c == candidate_a.descriptor() => {
tx.send(Ok( tx.send(Ok(
ValidationResult::Valid(ValidationOutputs { ValidationResult::Valid(ValidationOutputs {
global_validation_schedule: test_state.global_validation_schedule, global_validation_data: test_state.global_validation_data,
local_validation_data: test_state.local_validation_data, local_validation_data: test_state.local_validation_data,
head_data: expected_head_data.clone(), head_data: expected_head_data.clone(),
upward_messages: Vec::new(), upward_messages: Vec::new(),
@@ -1281,7 +1281,7 @@ mod tests {
) if pov == pov && &c == candidate_a.descriptor() => { ) if pov == pov && &c == candidate_a.descriptor() => {
tx.send(Ok( tx.send(Ok(
ValidationResult::Valid(ValidationOutputs { ValidationResult::Valid(ValidationOutputs {
global_validation_schedule: test_state.global_validation_schedule, global_validation_data: test_state.global_validation_data,
local_validation_data: test_state.local_validation_data, local_validation_data: test_state.local_validation_data,
head_data: expected_head_data.clone(), head_data: expected_head_data.clone(),
upward_messages: Vec::new(), upward_messages: Vec::new(),
@@ -1438,7 +1438,7 @@ mod tests {
) if pov == pov && &c == candidate_b.descriptor() => { ) if pov == pov && &c == candidate_b.descriptor() => {
tx.send(Ok( tx.send(Ok(
ValidationResult::Valid(ValidationOutputs { ValidationResult::Valid(ValidationOutputs {
global_validation_schedule: test_state.global_validation_schedule, global_validation_data: test_state.global_validation_data,
local_validation_data: test_state.local_validation_data, local_validation_data: test_state.local_validation_data,
head_data: expected_head_data.clone(), head_data: expected_head_data.clone(),
upward_messages: Vec::new(), upward_messages: Vec::new(),
+2 -2
View File
@@ -24,7 +24,7 @@ use parity_scale_codec::{Decode, Encode};
use polkadot_primitives::v1::{ use polkadot_primitives::v1::{
Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement, Hash, CommittedCandidateReceipt, CandidateReceipt, CompactStatement,
EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId, EncodeAs, Signed, SigningContext, ValidatorIndex, ValidatorId,
UpwardMessage, Balance, ValidationCode, GlobalValidationSchedule, LocalValidationData, UpwardMessage, Balance, ValidationCode, GlobalValidationData, LocalValidationData,
HeadData, HeadData,
}; };
use polkadot_statement_table::{ use polkadot_statement_table::{
@@ -118,7 +118,7 @@ pub struct ValidationOutputs {
/// The head-data produced by validation. /// The head-data produced by validation.
pub head_data: HeadData, pub head_data: HeadData,
/// The global validation schedule. /// The global validation schedule.
pub global_validation_schedule: GlobalValidationSchedule, pub global_validation_data: GlobalValidationData,
/// The local validation data. /// The local validation data.
pub local_validation_data: LocalValidationData, pub local_validation_data: LocalValidationData,
/// Upward messages to the relay chain. /// Upward messages to the relay chain.
@@ -24,7 +24,7 @@ use sp_core::Pair;
use codec::{Encode, Decode}; use codec::{Encode, Decode};
use primitives::v0::{ use primitives::v0::{
Hash, DownwardMessage, Hash, DownwardMessage,
HeadData, BlockData, Id as ParaId, LocalValidationData, GlobalValidationSchedule, HeadData, BlockData, Id as ParaId, LocalValidationData, GlobalValidationData,
}; };
use collator::{ParachainContext, Network, BuildParachainContext, Cli, SubstrateCli}; use collator::{ParachainContext, Network, BuildParachainContext, Cli, SubstrateCli};
use parking_lot::Mutex; use parking_lot::Mutex;
@@ -58,7 +58,7 @@ impl ParachainContext for AdderContext {
fn produce_candidate( fn produce_candidate(
&mut self, &mut self,
_relay_parent: Hash, _relay_parent: Hash,
_global_validation: GlobalValidationSchedule, _global_validation: GlobalValidationData,
local_validation: LocalValidationData, local_validation: LocalValidationData,
_: Vec<DownwardMessage>, _: Vec<DownwardMessage>,
) -> Self::ProduceCandidate ) -> Self::ProduceCandidate
+4 -4
View File
@@ -179,7 +179,7 @@ pub struct DutyRoster {
/// These are global parameters that apply to all parachain candidates in a block. /// These are global parameters that apply to all parachain candidates in a block.
#[derive(PartialEq, Eq, Clone, Encode, Decode)] #[derive(PartialEq, Eq, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Default))] #[cfg_attr(feature = "std", derive(Debug, Default))]
pub struct GlobalValidationSchedule<N = BlockNumber> { pub struct GlobalValidationData<N = BlockNumber> {
/// The maximum code size permitted, in bytes. /// The maximum code size permitted, in bytes.
pub max_code_size: u32, pub max_code_size: u32,
/// The maximum head-data size permitted, in bytes. /// The maximum head-data size permitted, in bytes.
@@ -278,7 +278,7 @@ pub struct CandidateReceipt<H = Hash, N = BlockNumber> {
/// The hash of the PoV-block. /// The hash of the PoV-block.
pub pov_block_hash: H, pub pov_block_hash: H,
/// The global validation schedule. /// The global validation schedule.
pub global_validation: GlobalValidationSchedule<N>, pub global_validation: GlobalValidationData<N>,
/// The local validation data. /// The local validation data.
pub local_validation: LocalValidationData<N>, pub local_validation: LocalValidationData<N>,
/// Commitments made as a result of validation. /// Commitments made as a result of validation.
@@ -352,7 +352,7 @@ impl Ord for CandidateReceipt {
#[cfg_attr(feature = "std", derive(Debug, Default))] #[cfg_attr(feature = "std", derive(Debug, Default))]
pub struct OmittedValidationData<N = BlockNumber> { pub struct OmittedValidationData<N = BlockNumber> {
/// The global validation schedule. /// The global validation schedule.
pub global_validation: GlobalValidationSchedule<N>, pub global_validation: GlobalValidationData<N>,
/// The local validation data. /// The local validation data.
pub local_validation: LocalValidationData<N>, pub local_validation: LocalValidationData<N>,
} }
@@ -762,7 +762,7 @@ sp_api::decl_runtime_apis! {
fn active_parachains() -> Vec<(Id, Option<(CollatorId, Retriable)>)>; fn active_parachains() -> Vec<(Id, Option<(CollatorId, Retriable)>)>;
/// Get the global validation schedule that all parachains should /// Get the global validation schedule that all parachains should
/// be validated under. /// be validated under.
fn global_validation_schedule() -> GlobalValidationSchedule; fn global_validation_data() -> GlobalValidationData;
/// Get the local validation data for a particular parachain. /// Get the local validation data for a particular parachain.
fn local_validation_data(id: Id) -> Option<LocalValidationData>; fn local_validation_data(id: Id) -> Option<LocalValidationData>;
/// Get the given parachain's head code blob. /// Get the given parachain's head code blob.
+49 -12
View File
@@ -60,14 +60,16 @@ pub const INCLUSION_INHERENT_IDENTIFIER: InherentIdentifier = *b"inclusn0";
pub fn collator_signature_payload<H: AsRef<[u8]>>( pub fn collator_signature_payload<H: AsRef<[u8]>>(
relay_parent: &H, relay_parent: &H,
para_id: &Id, para_id: &Id,
validation_data_hash: &Hash,
pov_hash: &Hash, pov_hash: &Hash,
) -> [u8; 68] { ) -> [u8; 100] {
// 32-byte hash length is protected in a test below. // 32-byte hash length is protected in a test below.
let mut payload = [0u8; 68]; let mut payload = [0u8; 100];
payload[0..32].copy_from_slice(relay_parent.as_ref()); payload[0..32].copy_from_slice(relay_parent.as_ref());
u32::from(*para_id).using_encoded(|s| payload[32..32 + s.len()].copy_from_slice(s)); u32::from(*para_id).using_encoded(|s| payload[32..32 + s.len()].copy_from_slice(s));
payload[36..68].copy_from_slice(pov_hash.as_ref()); payload[36..68].copy_from_slice(validation_data_hash.as_ref());
payload[68..100].copy_from_slice(pov_hash.as_ref());
payload payload
} }
@@ -75,11 +77,18 @@ pub fn collator_signature_payload<H: AsRef<[u8]>>(
fn check_collator_signature<H: AsRef<[u8]>>( fn check_collator_signature<H: AsRef<[u8]>>(
relay_parent: &H, relay_parent: &H,
para_id: &Id, para_id: &Id,
validation_data_hash: &Hash,
pov_hash: &Hash, pov_hash: &Hash,
collator: &CollatorId, collator: &CollatorId,
signature: &CollatorSignature, signature: &CollatorSignature,
) -> Result<(),()> { ) -> Result<(),()> {
let payload = collator_signature_payload(relay_parent, para_id, pov_hash); let payload = collator_signature_payload(
relay_parent,
para_id,
validation_data_hash,
pov_hash,
);
if signature.verify(&payload[..], collator) { if signature.verify(&payload[..], collator) {
Ok(()) Ok(())
} else { } else {
@@ -87,6 +96,14 @@ fn check_collator_signature<H: AsRef<[u8]>>(
} }
} }
/// Compute the `validation_data_hash` from global & local validation data.
pub fn validation_data_hash<N: Encode>(
global: &GlobalValidationData<N>,
local: &LocalValidationData<N>,
) -> Hash {
BlakeTwo256::hash_of(&(global, local))
}
/// A unique descriptor of the candidate receipt. /// A unique descriptor of the candidate receipt.
#[derive(PartialEq, Eq, Clone, Encode, Decode)] #[derive(PartialEq, Eq, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Default))] #[cfg_attr(feature = "std", derive(Debug, Default))]
@@ -97,11 +114,16 @@ pub struct CandidateDescriptor<H = Hash> {
pub relay_parent: H, pub relay_parent: H,
/// The collator's sr25519 public key. /// The collator's sr25519 public key.
pub collator: CollatorId, pub collator: CollatorId,
/// Signature on blake2-256 of components of this receipt: /// The blake2-256 hash of the validation data. This is extra data derived from
/// The parachain index, the relay parent, and the pov_hash. /// relay-chain state which may vary based on bitfields included before the candidate.
pub signature: CollatorSignature, /// Thus it cannot be derived entirely from the relay-parent.
pub validation_data_hash: Hash,
/// The blake2-256 hash of the pov. /// The blake2-256 hash of the pov.
pub pov_hash: Hash, pub pov_hash: Hash,
/// Signature on blake2-256 of components of this receipt:
/// The parachain index, the relay parent, the validation data hash, and the pov_hash.
pub signature: CollatorSignature,
} }
impl<H: AsRef<[u8]>> CandidateDescriptor<H> { impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
@@ -110,6 +132,7 @@ impl<H: AsRef<[u8]>> CandidateDescriptor<H> {
check_collator_signature( check_collator_signature(
&self.relay_parent, &self.relay_parent,
&self.para_id, &self.para_id,
&self.validation_data_hash,
&self.pov_hash, &self.pov_hash,
&self.collator, &self.collator,
&self.signature, &self.signature,
@@ -146,7 +169,7 @@ pub struct FullCandidateReceipt<H = Hash, N = BlockNumber> {
/// The inner candidate receipt. /// The inner candidate receipt.
pub inner: CandidateReceipt<H>, pub inner: CandidateReceipt<H>,
/// The global validation schedule. /// The global validation schedule.
pub global_validation: GlobalValidationSchedule<N>, pub global_validation: GlobalValidationData<N>,
/// The local validation data. /// The local validation data.
pub local_validation: LocalValidationData<N>, pub local_validation: LocalValidationData<N>,
} }
@@ -232,7 +255,7 @@ pub struct LocalValidationData<N = BlockNumber> {
/// These are global parameters that apply to all candidates in a block. /// These are global parameters that apply to all candidates in a block.
#[derive(PartialEq, Eq, Clone, Encode, Decode)] #[derive(PartialEq, Eq, Clone, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug, Default))] #[cfg_attr(feature = "std", derive(Debug, Default))]
pub struct GlobalValidationSchedule<N = BlockNumber> { pub struct GlobalValidationData<N = BlockNumber> {
/// The maximum code size permitted, in bytes. /// The maximum code size permitted, in bytes.
pub max_code_size: u32, pub max_code_size: u32,
/// The maximum head-data size permitted, in bytes. /// The maximum head-data size permitted, in bytes.
@@ -465,7 +488,7 @@ impl CoreAssignment {
#[cfg_attr(feature = "std", derive(PartialEq, Debug))] #[cfg_attr(feature = "std", derive(PartialEq, Debug))]
pub struct OmittedValidationData { pub struct OmittedValidationData {
/// The global validation schedule. /// The global validation schedule.
pub global_validation: GlobalValidationSchedule, pub global_validation: GlobalValidationData,
/// The local validation data. /// The local validation data.
pub local_validation: LocalValidationData, pub local_validation: LocalValidationData,
} }
@@ -636,9 +659,9 @@ sp_api::decl_runtime_apis! {
/// cores can have paras assigned to them. /// cores can have paras assigned to them.
fn availability_cores() -> Vec<CoreState<N>>; fn availability_cores() -> Vec<CoreState<N>>;
/// Yields the GlobalValidationSchedule. This applies to all para candidates with the /// Yields the GlobalValidationData. This applies to all para candidates with the
/// relay-parent equal to the block in which context this is invoked in. /// relay-parent equal to the block in which context this is invoked in.
fn global_validation_schedule() -> GlobalValidationSchedule<N>; fn global_validation_data() -> GlobalValidationData<N>;
/// Yields the LocalValidationData for the given ParaId along with an assumption that /// Yields the LocalValidationData for the given ParaId along with an assumption that
/// should be used if the para currently occupies a core. /// should be used if the para currently occupies a core.
@@ -696,4 +719,18 @@ mod tests {
assert_eq!(info.next_rotation_at(), 0); assert_eq!(info.next_rotation_at(), 0);
assert_eq!(info.last_rotation_at(), 0); assert_eq!(info.last_rotation_at(), 0);
} }
#[test]
fn collator_signature_payload_is_valid() {
// if this fails, collator signature verification code has to be updated.
let h = Hash::default();
assert_eq!(h.as_ref().len(), 32);
let _payload = collator_signature_payload(
&Hash::from([1; 32]),
&5u32.into(),
&Hash::from([2; 32]),
&Hash::from([3; 32]),
);
}
} }
@@ -137,10 +137,10 @@ enum CoreState {
## Global Validation Schedule ## Global Validation Schedule
Yields the [`GlobalValidationSchedule`](../types/candidate.md#globalvalidationschedule) at the state of a given block. This applies to all para candidates with the relay-parent equal to that block. Yields the [`GlobalValidationData`](../types/candidate.md#globalvalidationschedule) at the state of a given block. This applies to all para candidates with the relay-parent equal to that block.
```rust ```rust
fn global_validation_schedule(at: Block) -> GlobalValidationSchedule; fn global_validation_data(at: Block) -> GlobalValidationData;
``` ```
## Local Validation Data ## Local Validation Data
@@ -35,6 +35,9 @@ fn update_configuration(f: impl FnOnce(&mut HostConfiguration)) {
*pending = Some(x); *pending = Some(x);
}) })
} }
/// Get the GlobalValidationData, assuming the context is the parent block.
fn global_validation_data() -> GlobalValidationData;
``` ```
## Entry-points ## Entry-points
@@ -62,6 +62,7 @@ All failed checks should lead to an unrecoverable error making the block invalid
1. check that each candidate corresponds to a scheduled core and that they are ordered in the same order the cores appear in assignments in `scheduled`. 1. check that each candidate corresponds to a scheduled core and that they are ordered in the same order the cores appear in assignments in `scheduled`.
1. check that `scheduled` is sorted ascending by `CoreIndex`, without duplicates. 1. check that `scheduled` is sorted ascending by `CoreIndex`, without duplicates.
1. check that there is no candidate pending availability for any scheduled `ParaId`. 1. check that there is no candidate pending availability for any scheduled `ParaId`.
1. check that each candidate's `validation_data_hash` corresponds to a `(LocalValidationData, GlobalValidationData)` computed from the current state.
1. If the core assignment includes a specific collator, ensure the backed candidate is issued by that collator. 1. If the core assignment includes a specific collator, ensure the backed candidate is issued by that collator.
1. Ensure that any code upgrade scheduled by the candidate does not happen within `config.validation_upgrade_frequency` of `Paras::last_code_upgrade(para_id, true)`, if any, comparing against the value of `Paras::FutureCodeUpgrades` for the given para ID. 1. Ensure that any code upgrade scheduled by the candidate does not happen within `config.validation_upgrade_frequency` of `Paras::last_code_upgrade(para_id, true)`, if any, comparing against the value of `Paras::FutureCodeUpgrades` for the given para ID.
1. Check the collator's signature on the candidate data. 1. Check the collator's signature on the candidate data.
@@ -112,6 +112,7 @@ OutgoingParas: Vec<ParaId>;
* `is_parathread(ParaId) -> bool`: Returns true if the para ID references any live parathread. * `is_parathread(ParaId) -> bool`: Returns true if the para ID references any live parathread.
* `last_code_upgrade(id: ParaId, include_future: bool) -> Option<BlockNumber>`: The block number of the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. This is the `expected_at` number, not the `activated_at` number. * `last_code_upgrade(id: ParaId, include_future: bool) -> Option<BlockNumber>`: The block number of the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. This is the `expected_at` number, not the `activated_at` number.
* `local_validation_data(id: ParaId) -> Option<LocalValidationData>`: Get the LocalValidationData of the given para, assuming the context is the parent block. Returns `None` if the para is not known.
## Finalization ## Finalization
@@ -26,12 +26,12 @@ struct PoV(Vec<u8>);
Validation data that is often omitted from types describing candidates as it can be derived from the relay-parent of the candidate. However, with the expectation of state pruning, these are best kept available elsewhere as well. Validation data that is often omitted from types describing candidates as it can be derived from the relay-parent of the candidate. However, with the expectation of state pruning, these are best kept available elsewhere as well.
This contains the [`GlobalValidationSchedule`](candidate.md#globalvalidationschedule) and [`LocalValidationData`](candidate.md#localvalidationdata) This contains the [`GlobalValidationData`](candidate.md#globalvalidationschedule) and [`LocalValidationData`](candidate.md#localvalidationdata)
```rust ```rust
struct OmittedValidationData { struct OmittedValidationData {
/// The global validation schedule. /// The global validation schedule.
global_validation: GlobalValidationSchedule, global_validation: GlobalValidationData,
/// The local validation data. /// The local validation data.
local_validation: LocalValidationData, local_validation: LocalValidationData,
} }
@@ -33,7 +33,7 @@ struct CandidateReceipt {
## Full Candidate Receipt ## Full Candidate Receipt
This is the full receipt type. The `GlobalValidationSchedule` and the `LocalValidationData` are technically redundant with the `inner.relay_parent`, which uniquely describes the a block in the blockchain from whose state these values are derived. The [`CandidateReceipt`](#candidate-receipt) variant is often used instead for this reason. This is the full receipt type. The `GlobalValidationData` and the `LocalValidationData` are technically redundant with the `inner.relay_parent`, which uniquely describes the a block in the blockchain from whose state these values are derived. The [`CandidateReceipt`](#candidate-receipt) variant is often used instead for this reason.
However, the Full Candidate Receipt type is useful as a means of avoiding the implicit dependency on availability of old blockchain state. In situations such as availability and approval, having the full description of the candidate within a self-contained struct is convenient. However, the Full Candidate Receipt type is useful as a means of avoiding the implicit dependency on availability of old blockchain state. In situations such as availability and approval, having the full description of the candidate within a self-contained struct is convenient.
@@ -42,7 +42,7 @@ However, the Full Candidate Receipt type is useful as a means of avoiding the im
struct FullCandidateReceipt { struct FullCandidateReceipt {
inner: CandidateReceipt, inner: CandidateReceipt,
/// The global validation schedule. /// The global validation schedule.
global_validation: GlobalValidationSchedule, global_validation: GlobalValidationData,
/// The local validation data. /// The local validation data.
local_validation: LocalValidationData, local_validation: LocalValidationData,
} }
@@ -77,16 +77,19 @@ struct CandidateDescriptor {
relay_parent: Hash, relay_parent: Hash,
/// The collator's sr25519 public key. /// The collator's sr25519 public key.
collator: CollatorId, collator: CollatorId,
/// Signature on blake2-256 of components of this receipt: /// The blake2-256 hash of the validation data. These are extra parameters
/// The parachain index, the relay parent, and the pov_hash. /// derived from relay-chain state that influence the validity of the block.
signature: CollatorSignature, validation_data_hash: Hash,
/// The blake2-256 hash of the pov-block. /// The blake2-256 hash of the pov-block.
pov_hash: Hash, pov_hash: Hash,
/// Signature on blake2-256 of components of this receipt:
/// The parachain index, the relay parent, the validation data hash, and the pov_hash.
signature: CollatorSignature,
} }
``` ```
## GlobalValidationSchedule ## GlobalValidationData
The global validation schedule comprises of information describing the global environment for para execution, as derived from a particular relay-parent. These are parameters that will apply to all parablocks executed in the context of this relay-parent. The global validation schedule comprises of information describing the global environment for para execution, as derived from a particular relay-parent. These are parameters that will apply to all parablocks executed in the context of this relay-parent.
@@ -95,7 +98,7 @@ The global validation schedule comprises of information describing the global en
/// to fully validate the candidate. /// to fully validate the candidate.
/// ///
/// These are global parameters that apply to all candidates in a block. /// These are global parameters that apply to all candidates in a block.
struct GlobalValidationSchedule { struct GlobalValidationData {
/// The maximum code size permitted, in bytes. /// The maximum code size permitted, in bytes.
max_code_size: u32, max_code_size: u32,
/// The maximum head-data size permitted, in bytes. /// The maximum head-data size permitted, in bytes.
@@ -197,7 +200,7 @@ struct ValidationOutputs {
/// The head-data produced by validation. /// The head-data produced by validation.
head_data: HeadData, head_data: HeadData,
/// The global validation schedule. /// The global validation schedule.
global_validation_schedule: GlobalValidationSchedule, global_validation_data: GlobalValidationData,
/// The local validation data. /// The local validation data.
local_validation_data: LocalValidationData, local_validation_data: LocalValidationData,
/// Upwards messages to the relay chain. /// Upwards messages to the relay chain.
@@ -255,7 +255,7 @@ enum RuntimeApiRequest {
/// Get the validation code for a specific para, using the given occupied core assumption. /// Get the validation code for a specific para, using the given occupied core assumption.
ValidationCode(ParaId, OccupiedCoreAssumption, ResponseChannel<Option<ValidationCode>>), ValidationCode(ParaId, OccupiedCoreAssumption, ResponseChannel<Option<ValidationCode>>),
/// Get the global validation schedule at the state of a given block. /// Get the global validation schedule at the state of a given block.
GlobalValidationSchedule(ResponseChannel<GlobalValidationSchedule>), GlobalValidationData(ResponseChannel<GlobalValidationData>),
/// Get the local validation data for a specific para, with the given occupied core assumption. /// Get the local validation data for a specific para, with the given occupied core assumption.
LocalValidationData( LocalValidationData(
ParaId, ParaId,
+6 -6
View File
@@ -41,7 +41,7 @@ use primitives::v0::{
Balance, BlockNumber, Balance, BlockNumber,
Id as ParaId, Chain, DutyRoster, AttestedCandidate, CompactStatement as Statement, ParachainDispatchOrigin, Id as ParaId, Chain, DutyRoster, AttestedCandidate, CompactStatement as Statement, ParachainDispatchOrigin,
UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData, UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData,
CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt, CandidateReceipt, GlobalValidationData, AbridgedCandidateReceipt,
LocalValidationData, Scheduling, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, LocalValidationData, Scheduling, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID,
ValidatorSignature, SigningContext, HeadData, ValidationCode, ValidatorSignature, SigningContext, HeadData, ValidationCode,
Remark, DownwardMessage Remark, DownwardMessage
@@ -601,7 +601,7 @@ decl_module! {
let mut proceeded = Vec::with_capacity(heads.len()); let mut proceeded = Vec::with_capacity(heads.len());
let schedule = Self::global_validation_schedule(); let schedule = Self::global_validation_data();
if !active_parachains.is_empty() { if !active_parachains.is_empty() {
// perform integrity checks before writing to storage. // perform integrity checks before writing to storage.
@@ -1168,9 +1168,9 @@ impl<T: Trait> Module<T> {
} }
/// Get the global validation schedule for all parachains. /// Get the global validation schedule for all parachains.
pub fn global_validation_schedule() -> GlobalValidationSchedule { pub fn global_validation_data() -> GlobalValidationData {
let now = <system::Module<T>>::block_number(); let now = <system::Module<T>>::block_number();
GlobalValidationSchedule { GlobalValidationData {
max_code_size: T::MaxCodeSize::get(), max_code_size: T::MaxCodeSize::get(),
max_head_data_size: T::MaxHeadDataSize::get(), max_head_data_size: T::MaxHeadDataSize::get(),
block_number: T::BlockNumberConversion::convert(if now.is_zero() { block_number: T::BlockNumberConversion::convert(if now.is_zero() {
@@ -1322,7 +1322,7 @@ impl<T: Trait> Module<T> {
// check the attestations on these candidates. The candidates should have been checked // check the attestations on these candidates. The candidates should have been checked
// that each candidates' chain ID is valid. // that each candidates' chain ID is valid.
fn check_candidates( fn check_candidates(
schedule: &GlobalValidationSchedule, schedule: &GlobalValidationData,
attested_candidates: &[AttestedCandidate], attested_candidates: &[AttestedCandidate],
active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)] active_parachains: &[(ParaId, Option<(CollatorId, Retriable)>)]
) -> sp_std::result::Result<IncludedBlocks<T>, sp_runtime::DispatchError> { ) -> sp_std::result::Result<IncludedBlocks<T>, sp_runtime::DispatchError> {
@@ -2157,7 +2157,7 @@ mod tests {
collator: Default::default(), collator: Default::default(),
signature: Default::default(), signature: Default::default(),
pov_block_hash: Default::default(), pov_block_hash: Default::default(),
global_validation: Parachains::global_validation_schedule(), global_validation: Parachains::global_validation_data(),
local_validation: Parachains::current_local_validation_data(&para_id).unwrap(), local_validation: Parachains::current_local_validation_data(&para_id).unwrap(),
commitments: CandidateCommitments::default(), commitments: CandidateCommitments::default(),
} }
+1 -1
View File
@@ -1070,7 +1070,7 @@ mod tests {
collator: collator.public(), collator: collator.public(),
signature: pov_block_hash.using_encoded(|d| collator.sign(d)), signature: pov_block_hash.using_encoded(|d| collator.sign(d)),
pov_block_hash, pov_block_hash,
global_validation: Parachains::global_validation_schedule(), global_validation: Parachains::global_validation_data(),
local_validation: Parachains::current_local_validation_data(&id).unwrap(), local_validation: Parachains::current_local_validation_data(&id).unwrap(),
commitments: CandidateCommitments { commitments: CandidateCommitments {
fees: 0, fees: 0,
+3 -3
View File
@@ -87,7 +87,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("kusama"), spec_name: create_runtime_str!("kusama"),
impl_name: create_runtime_str!("parity-kusama"), impl_name: create_runtime_str!("parity-kusama"),
authoring_version: 2, authoring_version: 2,
spec_version: 2019, spec_version: 2020,
impl_version: 0, impl_version: 0,
#[cfg(not(feature = "disable-runtime-api"))] #[cfg(not(feature = "disable-runtime-api"))]
apis: RUNTIME_API_VERSIONS, apis: RUNTIME_API_VERSIONS,
@@ -1115,8 +1115,8 @@ sp_api::impl_runtime_apis! {
fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> { fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
Registrar::active_paras() Registrar::active_paras()
} }
fn global_validation_schedule() -> parachain::GlobalValidationSchedule { fn global_validation_data() -> parachain::GlobalValidationData {
Parachains::global_validation_schedule() Parachains::global_validation_data()
} }
fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> { fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
Parachains::current_local_validation_data(&id) Parachains::current_local_validation_data(&id)
@@ -19,12 +19,13 @@
//! Configuration can change only at session boundaries and is buffered until then. //! Configuration can change only at session boundaries and is buffered until then.
use sp_std::prelude::*; use sp_std::prelude::*;
use primitives::v1::ValidatorId; use primitives::v1::{ValidatorId, GlobalValidationData};
use frame_support::{ use frame_support::{
decl_storage, decl_module, decl_error, decl_storage, decl_module, decl_error,
dispatch::DispatchResult, dispatch::DispatchResult,
weights::{DispatchClass, Weight}, weights::{DispatchClass, Weight},
}; };
use sp_runtime::traits::One;
use codec::{Encode, Decode}; use codec::{Encode, Decode};
use system::ensure_root; use system::ensure_root;
@@ -219,6 +220,16 @@ impl<T: Trait> Module<T> {
<Self as Store>::PendingConfig::set(Some(prev)); <Self as Store>::PendingConfig::set(Some(prev));
} }
} }
/// Computes the global validation-data, assuming the context of the parent block.
pub(crate) fn global_validation_data() -> GlobalValidationData<T::BlockNumber> {
let config = Self::config();
GlobalValidationData {
max_code_size: config.max_code_size,
max_head_data_size: config.max_head_data_size,
block_number: <system::Module<T>>::block_number() - One::one(),
}
}
} }
#[cfg(test)] #[cfg(test)]
+260 -56
View File
@@ -22,6 +22,7 @@
use sp_std::prelude::*; use sp_std::prelude::*;
use primitives::v1::{ use primitives::v1::{
validation_data_hash,
ValidatorId, CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId, ValidatorId, CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId,
AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext, AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext,
BackedCandidate, CoreIndex, GroupIndex, CoreAssignment, CommittedCandidateReceipt, BackedCandidate, CoreIndex, GroupIndex, CoreAssignment, CommittedCandidateReceipt,
@@ -145,6 +146,8 @@ decl_error! {
InvalidBacking, InvalidBacking,
/// Collator did not sign PoV. /// Collator did not sign PoV.
NotCollatorSigned, NotCollatorSigned,
/// The validation data hash does not match expected.
ValidationDataHashMismatch,
/// Internal error only returned when compiled with debug assertions. /// Internal error only returned when compiled with debug assertions.
InternalError, InternalError,
} }
@@ -399,14 +402,21 @@ impl<T: Trait> Module<T> {
Error::<T>::CandidateNotInParentContext, Error::<T>::CandidateNotInParentContext,
); );
let code_upgrade_allowed = <paras::Module<T>>::last_code_upgrade(para_id, true) // if any, the code upgrade attempt is allowed.
.map_or( let valid_upgrade_attempt =
true, candidate.candidate.commitments.new_validation_code.is_none() ||
|last| last <= relay_parent_number && <paras::Module<T>>::last_code_upgrade(para_id, true)
relay_parent_number.saturating_sub(last) >= config.validation_upgrade_frequency, .map_or(
); true,
|last| last <= relay_parent_number &&
relay_parent_number.saturating_sub(last)
>= config.validation_upgrade_frequency,
);
ensure!(code_upgrade_allowed, Error::<T>::PrematureCodeUpgrade); ensure!(
valid_upgrade_attempt,
Error::<T>::PrematureCodeUpgrade,
);
ensure!( ensure!(
candidate.descriptor().check_collator_signature().is_ok(), candidate.descriptor().check_collator_signature().is_ok(),
Error::<T>::NotCollatorSigned, Error::<T>::NotCollatorSigned,
@@ -423,6 +433,32 @@ impl<T: Trait> Module<T> {
); );
} }
{
// this should never fail because the para is registered
let (global_validation_data, local_validation_data) = (
<configuration::Module<T>>::global_validation_data(),
match <paras::Module<T>>::local_validation_data(para_id) {
Some(l) => l,
None => {
// We don't want to error out here because it will
// brick the relay-chain. So we return early without
// doing anything.
return Ok(Vec::new());
}
}
);
let expected = validation_data_hash(
&global_validation_data,
&local_validation_data,
);
ensure!(
expected == candidate.descriptor().validation_data_hash,
Error::<T>::ValidationDataHashMismatch,
);
}
ensure!( ensure!(
<PendingAvailability<T>>::get(&para_id).is_none() && <PendingAvailability<T>>::get(&para_id).is_none() &&
<PendingAvailabilityCommitments>::get(&para_id).is_none(), <PendingAvailabilityCommitments>::get(&para_id).is_none(),
@@ -686,6 +722,7 @@ mod tests {
let payload = primitives::v1::collator_signature_payload( let payload = primitives::v1::collator_signature_payload(
&candidate.descriptor.relay_parent, &candidate.descriptor.relay_parent,
&candidate.descriptor.para_id, &candidate.descriptor.para_id,
&candidate.descriptor.validation_data_hash,
&candidate.descriptor.pov_hash, &candidate.descriptor.pov_hash,
); );
@@ -814,6 +851,7 @@ mod tests {
head_data: HeadData, head_data: HeadData,
pov_hash: Hash, pov_hash: Hash,
relay_parent: Hash, relay_parent: Hash,
validation_data_hash: Hash,
new_validation_code: Option<ValidationCode>, new_validation_code: Option<ValidationCode>,
} }
@@ -824,6 +862,7 @@ mod tests {
para_id: self.para_id, para_id: self.para_id,
pov_hash: self.pov_hash, pov_hash: self.pov_hash,
relay_parent: self.relay_parent, relay_parent: self.relay_parent,
validation_data_hash: self.validation_data_hash,
..Default::default() ..Default::default()
}, },
commitments: CandidateCommitments { commitments: CandidateCommitments {
@@ -835,6 +874,12 @@ mod tests {
} }
} }
fn make_vdata_hash(para_id: ParaId) -> Option<Hash> {
let global_validation_data = Configuration::global_validation_data();
let local_validation_data = Paras::local_validation_data(para_id)?;
Some(validation_data_hash(&global_validation_data, &local_validation_data))
}
#[test] #[test]
fn collect_pending_cleans_up_pending() { fn collect_pending_cleans_up_pending() {
let chain_a = ParaId::from(1); let chain_a = ParaId::from(1);
@@ -1261,6 +1306,7 @@ mod tests {
para_id: chain_a, para_id: chain_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
collator_sign_candidate( collator_sign_candidate(
@@ -1276,11 +1322,14 @@ mod tests {
BackingKind::Threshold, BackingKind::Threshold,
); );
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![chain_b_assignment.clone()], vec![backed],
&group_validators, vec![chain_b_assignment.clone()],
).is_err()); &group_validators,
),
Err(Error::<Test>::UnscheduledCandidate.into()),
);
} }
// candidates out of order. // candidates out of order.
@@ -1289,12 +1338,14 @@ mod tests {
para_id: chain_a, para_id: chain_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
let mut candidate_b = TestCandidateBuilder { let mut candidate_b = TestCandidateBuilder {
para_id: chain_b, para_id: chain_b,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([2; 32]), pov_hash: Hash::from([2; 32]),
validation_data_hash: make_vdata_hash(chain_b).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
@@ -1324,11 +1375,15 @@ mod tests {
BackingKind::Threshold, BackingKind::Threshold,
); );
assert!(Inclusion::process_candidates( // out-of-order manifests as unscheduled.
vec![backed_b, backed_a], assert_eq!(
vec![chain_a_assignment.clone(), chain_b_assignment.clone()], Inclusion::process_candidates(
&group_validators, vec![backed_b, backed_a],
).is_err()); vec![chain_a_assignment.clone(), chain_b_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::UnscheduledCandidate.into()),
);
} }
// candidate not backed. // candidate not backed.
@@ -1337,6 +1392,7 @@ mod tests {
para_id: chain_a, para_id: chain_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
collator_sign_candidate( collator_sign_candidate(
@@ -1352,11 +1408,14 @@ mod tests {
BackingKind::Lacking, BackingKind::Lacking,
); );
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![chain_a_assignment.clone()], vec![backed],
&group_validators, vec![chain_a_assignment.clone()],
).is_err()); &group_validators,
),
Err(Error::<Test>::InsufficientBacking.into()),
);
} }
// candidate not in parent context. // candidate not in parent context.
@@ -1368,6 +1427,7 @@ mod tests {
para_id: chain_a, para_id: chain_a,
relay_parent: wrong_parent_hash, relay_parent: wrong_parent_hash,
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
collator_sign_candidate( collator_sign_candidate(
@@ -1383,11 +1443,14 @@ mod tests {
BackingKind::Threshold, BackingKind::Threshold,
); );
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![chain_a_assignment.clone()], vec![backed],
&group_validators, vec![chain_a_assignment.clone()],
).is_err()); &group_validators,
),
Err(Error::<Test>::CandidateNotInParentContext.into()),
);
} }
// candidate has wrong collator. // candidate has wrong collator.
@@ -1396,6 +1459,7 @@ mod tests {
para_id: thread_a, para_id: thread_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(thread_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
@@ -1413,15 +1477,18 @@ mod tests {
BackingKind::Threshold, BackingKind::Threshold,
); );
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![ vec![backed],
chain_a_assignment.clone(), vec![
chain_b_assignment.clone(), chain_a_assignment.clone(),
thread_a_assignment.clone(), chain_b_assignment.clone(),
], thread_a_assignment.clone(),
&group_validators, ],
).is_err()); &group_validators,
),
Err(Error::<Test>::WrongCollator.into()),
);
} }
// candidate not well-signed by collator. // candidate not well-signed by collator.
@@ -1430,6 +1497,7 @@ mod tests {
para_id: thread_a, para_id: thread_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(thread_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
@@ -1450,11 +1518,14 @@ mod tests {
BackingKind::Threshold, BackingKind::Threshold,
); );
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![thread_a_assignment.clone()], vec![backed],
&group_validators, vec![thread_a_assignment.clone()],
).is_err()); &group_validators,
),
Err(Error::<Test>::NotCollatorSigned.into()),
);
} }
// para occupied - reject. // para occupied - reject.
@@ -1463,6 +1534,7 @@ mod tests {
para_id: chain_a, para_id: chain_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
@@ -1489,11 +1561,14 @@ mod tests {
}); });
<PendingAvailabilityCommitments>::insert(&chain_a, candidate.commitments); <PendingAvailabilityCommitments>::insert(&chain_a, candidate.commitments);
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![chain_a_assignment.clone()], vec![backed],
&group_validators, vec![chain_a_assignment.clone()],
).is_err()); &group_validators,
),
Err(Error::<Test>::CandidateScheduledBeforeParaFree.into()),
);
<PendingAvailability<Test>>::remove(&chain_a); <PendingAvailability<Test>>::remove(&chain_a);
<PendingAvailabilityCommitments>::remove(&chain_a); <PendingAvailabilityCommitments>::remove(&chain_a);
@@ -1505,6 +1580,7 @@ mod tests {
para_id: chain_a, para_id: chain_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
@@ -1524,11 +1600,14 @@ mod tests {
BackingKind::Threshold, BackingKind::Threshold,
); );
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![chain_a_assignment.clone()], vec![backed],
&group_validators, vec![chain_a_assignment.clone()],
).is_err()); &group_validators,
),
Err(Error::<Test>::CandidateScheduledBeforeParaFree.into()),
);
<PendingAvailabilityCommitments>::remove(&chain_a); <PendingAvailabilityCommitments>::remove(&chain_a);
} }
@@ -1540,6 +1619,7 @@ mod tests {
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
new_validation_code: Some(vec![5, 6, 7, 8].into()), new_validation_code: Some(vec![5, 6, 7, 8].into()),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
@@ -1564,11 +1644,47 @@ mod tests {
assert_eq!(Paras::last_code_upgrade(chain_a, true), Some(10)); assert_eq!(Paras::last_code_upgrade(chain_a, true), Some(10));
assert!(Inclusion::process_candidates( assert_eq!(
vec![backed], Inclusion::process_candidates(
vec![thread_a_assignment.clone()], vec![backed],
&group_validators, vec![chain_a_assignment.clone()],
).is_err()); &group_validators,
),
Err(Error::<Test>::PrematureCodeUpgrade.into()),
);
}
// Bad validation data hash - reject
{
let mut candidate = TestCandidateBuilder {
para_id: chain_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: [42u8; 32].into(),
..Default::default()
}.build();
collator_sign_candidate(
Sr25519Keyring::One,
&mut candidate,
);
let backed = back_candidate(
candidate,
&validators,
group_validators(GroupIndex::from(0)).unwrap().as_ref(),
&signing_context,
BackingKind::Threshold,
);
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::ValidationDataHashMismatch.into()),
);
} }
}); });
} }
@@ -1634,6 +1750,7 @@ mod tests {
para_id: chain_a, para_id: chain_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]), pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
collator_sign_candidate( collator_sign_candidate(
@@ -1645,6 +1762,7 @@ mod tests {
para_id: chain_b, para_id: chain_b,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([2; 32]), pov_hash: Hash::from([2; 32]),
validation_data_hash: make_vdata_hash(chain_b).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
collator_sign_candidate( collator_sign_candidate(
@@ -1656,6 +1774,7 @@ mod tests {
para_id: thread_a, para_id: thread_a,
relay_parent: System::parent_hash(), relay_parent: System::parent_hash(),
pov_hash: Hash::from([3; 32]), pov_hash: Hash::from([3; 32]),
validation_data_hash: make_vdata_hash(thread_a).unwrap(),
..Default::default() ..Default::default()
}.build(); }.build();
collator_sign_candidate( collator_sign_candidate(
@@ -1746,6 +1865,91 @@ mod tests {
}); });
} }
#[test]
fn can_include_candidate_with_ok_code_upgrade() {
let chain_a = ParaId::from(1);
let paras = vec![(chain_a, true)];
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Ferdie,
];
let validator_public = validator_pubkeys(&validators);
new_test_ext(genesis_config(paras)).execute_with(|| {
Validators::set(validator_public.clone());
CurrentSessionIndex::set(5);
run_to_block(5, |_| None);
let signing_context = SigningContext {
parent_hash: System::parent_hash(),
session_index: 5,
};
let group_validators = |group_index: GroupIndex| match group_index {
group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1, 2, 3, 4]),
_ => panic!("Group index out of bounds for 1 parachain"),
};
let chain_a_assignment = CoreAssignment {
core: CoreIndex::from(0),
para_id: chain_a,
kind: AssignmentKind::Parachain,
group_idx: GroupIndex::from(0),
};
let mut candidate_a = TestCandidateBuilder {
para_id: chain_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
new_validation_code: Some(vec![1, 2, 3].into()),
..Default::default()
}.build();
collator_sign_candidate(
Sr25519Keyring::One,
&mut candidate_a,
);
let backed_a = back_candidate(
candidate_a.clone(),
&validators,
group_validators(GroupIndex::from(0)).unwrap().as_ref(),
&signing_context,
BackingKind::Threshold,
);
let occupied_cores = Inclusion::process_candidates(
vec![backed_a],
vec![
chain_a_assignment.clone(),
],
&group_validators,
).expect("candidates scheduled, in order, and backed");
assert_eq!(occupied_cores, vec![CoreIndex::from(0)]);
assert_eq!(
<PendingAvailability<Test>>::get(&chain_a),
Some(CandidatePendingAvailability {
core: CoreIndex::from(0),
descriptor: candidate_a.descriptor,
availability_votes: default_availability_votes(),
relay_parent_number: System::block_number() - 1,
backed_in_number: System::block_number(),
})
);
assert_eq!(
<PendingAvailabilityCommitments>::get(&chain_a),
Some(candidate_a.commitments),
);
});
}
#[test] #[test]
fn session_change_wipes_and_updates_session_info() { fn session_change_wipes_and_updates_session_info() {
let chain_a = ParaId::from(1); let chain_a = ParaId::from(1);
+33 -2
View File
@@ -25,9 +25,9 @@
use sp_std::prelude::*; use sp_std::prelude::*;
use sp_std::marker::PhantomData; use sp_std::marker::PhantomData;
use sp_runtime::traits::One; use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating};
use primitives::v1::{ use primitives::v1::{
Id as ParaId, ValidationCode, HeadData, Id as ParaId, ValidationCode, HeadData, LocalValidationData,
}; };
use frame_support::{ use frame_support::{
decl_storage, decl_module, decl_error, decl_storage, decl_module, decl_error,
@@ -536,6 +536,37 @@ impl<T: Trait> Module<T> {
Self::past_code_meta(&id).most_recent_change() Self::past_code_meta(&id).most_recent_change()
} }
/// Compute the local-validation data based on the head of the para. This assumes the
/// relay-parent is the parent of the current block.
pub(crate) fn local_validation_data(para_id: ParaId) -> Option<LocalValidationData<T::BlockNumber>> {
let relay_parent_number = <system::Module<T>>::block_number() - One::one();
let config = <configuration::Module<T>>::config();
let freq = config.validation_upgrade_frequency;
let delay = config.validation_upgrade_delay;
let last_code_upgrade = Self::last_code_upgrade(para_id, true);
let can_upgrade_code = last_code_upgrade.map_or(
true,
|l| { l <= relay_parent_number && relay_parent_number.saturating_sub(l) >= freq },
);
let code_upgrade_allowed = if can_upgrade_code {
Some(relay_parent_number + delay)
} else {
None
};
Some(LocalValidationData {
parent_head: Self::para_head(&para_id)?,
balance: 0,
validation_code_hash: BlakeTwo256::hash_of(
&Self::current_code(&para_id)?
),
code_upgrade_allowed,
})
}
} }
#[cfg(test)] #[cfg(test)]
@@ -18,12 +18,12 @@
//! functions. //! functions.
use primitives::v1::{ use primitives::v1::{
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationSchedule, ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationData,
Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode, Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode,
CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex, CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex,
GroupIndex, CandidateEvent, GroupIndex, CandidateEvent,
}; };
use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating, Zero}; use sp_runtime::traits::Zero;
use frame_support::debug; use frame_support::debug;
use crate::{initializer, inclusion, scheduler, configuration, paras}; use crate::{initializer, inclusion, scheduler, configuration, paras};
@@ -160,16 +160,11 @@ pub fn availability_cores<T: initializer::Trait>() -> Vec<CoreState<T::BlockNumb
core_states core_states
} }
/// Implementation for the `global_validation_schedule` function of the runtime API. /// Implementation for the `global_validation_data` function of the runtime API.
pub fn global_validation_schedule<T: initializer::Trait>() pub fn global_validation_data<T: initializer::Trait>()
-> GlobalValidationSchedule<T::BlockNumber> -> GlobalValidationData<T::BlockNumber>
{ {
let config = <configuration::Module<T>>::config(); <configuration::Module<T>>::global_validation_data()
GlobalValidationSchedule {
max_code_size: config.max_code_size,
max_head_data_size: config.max_head_data_size,
block_number: <system::Module<T>>::block_number() - One::one(),
}
} }
/// Implementation for the `local_validation_data` function of the runtime API. /// Implementation for the `local_validation_data` function of the runtime API.
@@ -177,46 +172,19 @@ pub fn local_validation_data<T: initializer::Trait>(
para_id: ParaId, para_id: ParaId,
assumption: OccupiedCoreAssumption, assumption: OccupiedCoreAssumption,
) -> Option<LocalValidationData<T::BlockNumber>> { ) -> Option<LocalValidationData<T::BlockNumber>> {
let construct = || {
let relay_parent_number = <system::Module<T>>::block_number() - One::one();
let config = <configuration::Module<T>>::config();
let freq = config.validation_upgrade_frequency;
let delay = config.validation_upgrade_delay;
let last_code_upgrade = <paras::Module<T>>::last_code_upgrade(para_id, true)?;
let can_upgrade_code = last_code_upgrade <= relay_parent_number
&& relay_parent_number.saturating_sub(last_code_upgrade) >= freq;
let code_upgrade_allowed = if can_upgrade_code {
Some(relay_parent_number + delay)
} else {
None
};
Some(LocalValidationData {
parent_head: <paras::Module<T>>::para_head(&para_id)?,
balance: 0,
validation_code_hash: BlakeTwo256::hash_of(
&<paras::Module<T>>::current_code(&para_id)?
),
code_upgrade_allowed,
})
};
match assumption { match assumption {
OccupiedCoreAssumption::Included => { OccupiedCoreAssumption::Included => {
<inclusion::Module<T>>::force_enact(para_id); <inclusion::Module<T>>::force_enact(para_id);
construct() <paras::Module<T>>::local_validation_data(para_id)
} }
OccupiedCoreAssumption::TimedOut => { OccupiedCoreAssumption::TimedOut => {
construct() <paras::Module<T>>::local_validation_data(para_id)
} }
OccupiedCoreAssumption::Free => { OccupiedCoreAssumption::Free => {
if <inclusion::Module<T>>::pending_availability(para_id).is_some() { if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
None None
} else { } else {
construct() <paras::Module<T>>::local_validation_data(para_id)
} }
} }
} }
+3 -3
View File
@@ -86,7 +86,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("polkadot"), spec_name: create_runtime_str!("polkadot"),
impl_name: create_runtime_str!("parity-polkadot"), impl_name: create_runtime_str!("parity-polkadot"),
authoring_version: 0, authoring_version: 0,
spec_version: 19, spec_version: 20,
impl_version: 0, impl_version: 0,
#[cfg(not(feature = "disable-runtime-api"))] #[cfg(not(feature = "disable-runtime-api"))]
apis: RUNTIME_API_VERSIONS, apis: RUNTIME_API_VERSIONS,
@@ -1256,8 +1256,8 @@ sp_api::impl_runtime_apis! {
fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> { fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
Registrar::active_paras() Registrar::active_paras()
} }
fn global_validation_schedule() -> parachain::GlobalValidationSchedule { fn global_validation_data() -> parachain::GlobalValidationData {
Parachains::global_validation_schedule() Parachains::global_validation_data()
} }
fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> { fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
Parachains::current_local_validation_data(&id) Parachains::current_local_validation_data(&id)
+2 -2
View File
@@ -676,8 +676,8 @@ sp_api::impl_runtime_apis! {
fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> { fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
Registrar::active_paras() Registrar::active_paras()
} }
fn global_validation_schedule() -> parachain::GlobalValidationSchedule { fn global_validation_data() -> parachain::GlobalValidationData {
Parachains::global_validation_schedule() Parachains::global_validation_data()
} }
fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> { fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
Parachains::current_local_validation_data(&id) Parachains::current_local_validation_data(&id)
+3 -3
View File
@@ -83,7 +83,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("westend"), spec_name: create_runtime_str!("westend"),
impl_name: create_runtime_str!("parity-westend"), impl_name: create_runtime_str!("parity-westend"),
authoring_version: 2, authoring_version: 2,
spec_version: 39, spec_version: 40,
impl_version: 0, impl_version: 0,
#[cfg(not(feature = "disable-runtime-api"))] #[cfg(not(feature = "disable-runtime-api"))]
apis: RUNTIME_API_VERSIONS, apis: RUNTIME_API_VERSIONS,
@@ -897,8 +897,8 @@ sp_api::impl_runtime_apis! {
fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> { fn active_parachains() -> Vec<(parachain::Id, Option<(parachain::CollatorId, parachain::Retriable)>)> {
Registrar::active_paras() Registrar::active_paras()
} }
fn global_validation_schedule() -> parachain::GlobalValidationSchedule { fn global_validation_data() -> parachain::GlobalValidationData {
Parachains::global_validation_schedule() Parachains::global_validation_data()
} }
fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> { fn local_validation_data(id: parachain::Id) -> Option<parachain::LocalValidationData> {
Parachains::current_local_validation_data(&id) Parachains::current_local_validation_data(&id)
+5 -5
View File
@@ -20,7 +20,7 @@
use codec::Encode; use codec::Encode;
use polkadot_erasure_coding as erasure; use polkadot_erasure_coding as erasure;
use polkadot_primitives::v0::{ use polkadot_primitives::v0::{
CollationInfo, PoVBlock, LocalValidationData, GlobalValidationSchedule, OmittedValidationData, CollationInfo, PoVBlock, LocalValidationData, GlobalValidationData, OmittedValidationData,
AvailableData, FeeSchedule, CandidateCommitments, ErasureChunk, ParachainHost, AvailableData, FeeSchedule, CandidateCommitments, ErasureChunk, ParachainHost,
Id as ParaId, AbridgedCandidateReceipt, ValidationCode, Id as ParaId, AbridgedCandidateReceipt, ValidationCode,
}; };
@@ -95,7 +95,7 @@ impl FullOutput {
/// validation are needed, call `full_output`. Otherwise, safely drop this value. /// validation are needed, call `full_output`. Otherwise, safely drop this value.
pub struct ValidatedCandidate<'a> { pub struct ValidatedCandidate<'a> {
pov_block: &'a PoVBlock, pov_block: &'a PoVBlock,
global_validation: &'a GlobalValidationSchedule, global_validation: &'a GlobalValidationData,
local_validation: &'a LocalValidationData, local_validation: &'a LocalValidationData,
upward_messages: Vec<UpwardMessage>, upward_messages: Vec<UpwardMessage>,
fees: Balance, fees: Balance,
@@ -189,7 +189,7 @@ pub fn validate<'a>(
collation: &'a CollationInfo, collation: &'a CollationInfo,
pov_block: &'a PoVBlock, pov_block: &'a PoVBlock,
local_validation: &'a LocalValidationData, local_validation: &'a LocalValidationData,
global_validation: &'a GlobalValidationSchedule, global_validation: &'a GlobalValidationData,
validation_code: &ValidationCode, validation_code: &ValidationCode,
) -> Result<ValidatedCandidate<'a>, Error> { ) -> Result<ValidatedCandidate<'a>, Error> {
if collation.head_data.0.len() > global_validation.max_head_data_size as _ { if collation.head_data.0.len() > global_validation.max_head_data_size as _ {
@@ -249,7 +249,7 @@ pub fn validate<'a>(
/// Extracts validation parameters from a Polkadot runtime API for a specific parachain. /// Extracts validation parameters from a Polkadot runtime API for a specific parachain.
pub fn validation_params<P>(api: &P, relay_parent: Hash, para_id: ParaId) pub fn validation_params<P>(api: &P, relay_parent: Hash, para_id: ParaId)
-> Result<(LocalValidationData, GlobalValidationSchedule, ValidationCode), Error> -> Result<(LocalValidationData, GlobalValidationData, ValidationCode), Error>
where where
P: ProvideRuntimeApi<Block>, P: ProvideRuntimeApi<Block>,
P::Api: ParachainHost<Block, Error = sp_blockchain::Error>, P::Api: ParachainHost<Block, Error = sp_blockchain::Error>,
@@ -261,7 +261,7 @@ where
let local_validation = api.local_validation_data(&relay_parent, para_id)? let local_validation = api.local_validation_data(&relay_parent, para_id)?
.ok_or_else(|| Error::InactiveParachain(para_id))?; .ok_or_else(|| Error::InactiveParachain(para_id))?;
let global_validation = api.global_validation_schedule(&relay_parent)?; let global_validation = api.global_validation_data(&relay_parent)?;
let validation_code = api.parachain_code(&relay_parent, para_id)? let validation_code = api.parachain_code(&relay_parent, para_id)?
.ok_or_else(|| Error::InactiveParachain(para_id))?; .ok_or_else(|| Error::InactiveParachain(para_id))?;
@@ -547,7 +547,7 @@ mod tests {
use availability_store::ErasureNetworking; use availability_store::ErasureNetworking;
use polkadot_primitives::v0::{ use polkadot_primitives::v0::{
PoVBlock, AbridgedCandidateReceipt, ErasureChunk, ValidatorIndex, PoVBlock, AbridgedCandidateReceipt, ErasureChunk, ValidatorIndex,
CollationInfo, DutyRoster, GlobalValidationSchedule, LocalValidationData, CollationInfo, DutyRoster, GlobalValidationData, LocalValidationData,
Retriable, CollatorId, BlockData, Chain, AvailableData, SigningContext, ValidationCode, Retriable, CollatorId, BlockData, Chain, AvailableData, SigningContext, ValidationCode,
}; };
use runtime_primitives::traits::Block as BlockT; use runtime_primitives::traits::Block as BlockT;
@@ -697,7 +697,7 @@ mod tests {
fn validators(&self) -> Vec<ValidatorId> { self.validators.clone() } fn validators(&self) -> Vec<ValidatorId> { self.validators.clone() }
fn duty_roster(&self) -> DutyRoster { self.duty_roster.clone() } fn duty_roster(&self) -> DutyRoster { self.duty_roster.clone() }
fn active_parachains() -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { vec![(ParaId::from(1), None)] } fn active_parachains() -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { vec![(ParaId::from(1), None)] }
fn global_validation_schedule() -> GlobalValidationSchedule { Default::default() } fn global_validation_data() -> GlobalValidationData { Default::default() }
fn local_validation_data(_: ParaId) -> Option<LocalValidationData> { None } fn local_validation_data(_: ParaId) -> Option<LocalValidationData> { None }
fn parachain_code(_: ParaId) -> Option<ValidationCode> { None } fn parachain_code(_: ParaId) -> Option<ValidationCode> { None }
fn get_heads(_: Vec<<Block as BlockT>::Extrinsic>) -> Option<Vec<AbridgedCandidateReceipt>> { fn get_heads(_: Vec<<Block as BlockT>::Extrinsic>) -> Option<Vec<AbridgedCandidateReceipt>> {