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
@@ -19,12 +19,13 @@
//! Configuration can change only at session boundaries and is buffered until then.
use sp_std::prelude::*;
use primitives::v1::ValidatorId;
use primitives::v1::{ValidatorId, GlobalValidationData};
use frame_support::{
decl_storage, decl_module, decl_error,
dispatch::DispatchResult,
weights::{DispatchClass, Weight},
};
use sp_runtime::traits::One;
use codec::{Encode, Decode};
use system::ensure_root;
@@ -219,6 +220,16 @@ impl<T: Trait> Module<T> {
<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)]
+260 -56
View File
@@ -22,6 +22,7 @@
use sp_std::prelude::*;
use primitives::v1::{
validation_data_hash,
ValidatorId, CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId,
AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext,
BackedCandidate, CoreIndex, GroupIndex, CoreAssignment, CommittedCandidateReceipt,
@@ -145,6 +146,8 @@ decl_error! {
InvalidBacking,
/// Collator did not sign PoV.
NotCollatorSigned,
/// The validation data hash does not match expected.
ValidationDataHashMismatch,
/// Internal error only returned when compiled with debug assertions.
InternalError,
}
@@ -399,14 +402,21 @@ impl<T: Trait> Module<T> {
Error::<T>::CandidateNotInParentContext,
);
let code_upgrade_allowed = <paras::Module<T>>::last_code_upgrade(para_id, true)
.map_or(
true,
|last| last <= relay_parent_number &&
relay_parent_number.saturating_sub(last) >= config.validation_upgrade_frequency,
);
// if any, the code upgrade attempt is allowed.
let valid_upgrade_attempt =
candidate.candidate.commitments.new_validation_code.is_none() ||
<paras::Module<T>>::last_code_upgrade(para_id, true)
.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!(
candidate.descriptor().check_collator_signature().is_ok(),
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!(
<PendingAvailability<T>>::get(&para_id).is_none() &&
<PendingAvailabilityCommitments>::get(&para_id).is_none(),
@@ -686,6 +722,7 @@ mod tests {
let payload = primitives::v1::collator_signature_payload(
&candidate.descriptor.relay_parent,
&candidate.descriptor.para_id,
&candidate.descriptor.validation_data_hash,
&candidate.descriptor.pov_hash,
);
@@ -814,6 +851,7 @@ mod tests {
head_data: HeadData,
pov_hash: Hash,
relay_parent: Hash,
validation_data_hash: Hash,
new_validation_code: Option<ValidationCode>,
}
@@ -824,6 +862,7 @@ mod tests {
para_id: self.para_id,
pov_hash: self.pov_hash,
relay_parent: self.relay_parent,
validation_data_hash: self.validation_data_hash,
..Default::default()
},
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]
fn collect_pending_cleans_up_pending() {
let chain_a = ParaId::from(1);
@@ -1261,6 +1306,7 @@ mod tests {
para_id: chain_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
collator_sign_candidate(
@@ -1276,11 +1322,14 @@ mod tests {
BackingKind::Threshold,
);
assert!(Inclusion::process_candidates(
vec![backed],
vec![chain_b_assignment.clone()],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![chain_b_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::UnscheduledCandidate.into()),
);
}
// candidates out of order.
@@ -1289,12 +1338,14 @@ mod tests {
para_id: chain_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
let mut candidate_b = TestCandidateBuilder {
para_id: chain_b,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([2; 32]),
validation_data_hash: make_vdata_hash(chain_b).unwrap(),
..Default::default()
}.build();
@@ -1324,11 +1375,15 @@ mod tests {
BackingKind::Threshold,
);
assert!(Inclusion::process_candidates(
vec![backed_b, backed_a],
vec![chain_a_assignment.clone(), chain_b_assignment.clone()],
&group_validators,
).is_err());
// out-of-order manifests as unscheduled.
assert_eq!(
Inclusion::process_candidates(
vec![backed_b, backed_a],
vec![chain_a_assignment.clone(), chain_b_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::UnscheduledCandidate.into()),
);
}
// candidate not backed.
@@ -1337,6 +1392,7 @@ mod tests {
para_id: chain_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
collator_sign_candidate(
@@ -1352,11 +1408,14 @@ mod tests {
BackingKind::Lacking,
);
assert!(Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::InsufficientBacking.into()),
);
}
// candidate not in parent context.
@@ -1368,6 +1427,7 @@ mod tests {
para_id: chain_a,
relay_parent: wrong_parent_hash,
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
collator_sign_candidate(
@@ -1383,11 +1443,14 @@ mod tests {
BackingKind::Threshold,
);
assert!(Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::CandidateNotInParentContext.into()),
);
}
// candidate has wrong collator.
@@ -1396,6 +1459,7 @@ mod tests {
para_id: thread_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(thread_a).unwrap(),
..Default::default()
}.build();
@@ -1413,15 +1477,18 @@ mod tests {
BackingKind::Threshold,
);
assert!(Inclusion::process_candidates(
vec![backed],
vec![
chain_a_assignment.clone(),
chain_b_assignment.clone(),
thread_a_assignment.clone(),
],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![
chain_a_assignment.clone(),
chain_b_assignment.clone(),
thread_a_assignment.clone(),
],
&group_validators,
),
Err(Error::<Test>::WrongCollator.into()),
);
}
// candidate not well-signed by collator.
@@ -1430,6 +1497,7 @@ mod tests {
para_id: thread_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(thread_a).unwrap(),
..Default::default()
}.build();
@@ -1450,11 +1518,14 @@ mod tests {
BackingKind::Threshold,
);
assert!(Inclusion::process_candidates(
vec![backed],
vec![thread_a_assignment.clone()],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![thread_a_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::NotCollatorSigned.into()),
);
}
// para occupied - reject.
@@ -1463,6 +1534,7 @@ mod tests {
para_id: chain_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
@@ -1489,11 +1561,14 @@ mod tests {
});
<PendingAvailabilityCommitments>::insert(&chain_a, candidate.commitments);
assert!(Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::CandidateScheduledBeforeParaFree.into()),
);
<PendingAvailability<Test>>::remove(&chain_a);
<PendingAvailabilityCommitments>::remove(&chain_a);
@@ -1505,6 +1580,7 @@ mod tests {
para_id: chain_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
@@ -1524,11 +1600,14 @@ mod tests {
BackingKind::Threshold,
);
assert!(Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&group_validators,
),
Err(Error::<Test>::CandidateScheduledBeforeParaFree.into()),
);
<PendingAvailabilityCommitments>::remove(&chain_a);
}
@@ -1540,6 +1619,7 @@ mod tests {
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
new_validation_code: Some(vec![5, 6, 7, 8].into()),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
@@ -1564,11 +1644,47 @@ mod tests {
assert_eq!(Paras::last_code_upgrade(chain_a, true), Some(10));
assert!(Inclusion::process_candidates(
vec![backed],
vec![thread_a_assignment.clone()],
&group_validators,
).is_err());
assert_eq!(
Inclusion::process_candidates(
vec![backed],
vec![chain_a_assignment.clone()],
&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,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([1; 32]),
validation_data_hash: make_vdata_hash(chain_a).unwrap(),
..Default::default()
}.build();
collator_sign_candidate(
@@ -1645,6 +1762,7 @@ mod tests {
para_id: chain_b,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([2; 32]),
validation_data_hash: make_vdata_hash(chain_b).unwrap(),
..Default::default()
}.build();
collator_sign_candidate(
@@ -1656,6 +1774,7 @@ mod tests {
para_id: thread_a,
relay_parent: System::parent_hash(),
pov_hash: Hash::from([3; 32]),
validation_data_hash: make_vdata_hash(thread_a).unwrap(),
..Default::default()
}.build();
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]
fn session_change_wipes_and_updates_session_info() {
let chain_a = ParaId::from(1);
+33 -2
View File
@@ -25,9 +25,9 @@
use sp_std::prelude::*;
use sp_std::marker::PhantomData;
use sp_runtime::traits::One;
use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating};
use primitives::v1::{
Id as ParaId, ValidationCode, HeadData,
Id as ParaId, ValidationCode, HeadData, LocalValidationData,
};
use frame_support::{
decl_storage, decl_module, decl_error,
@@ -536,6 +536,37 @@ impl<T: Trait> Module<T> {
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)]
@@ -18,12 +18,12 @@
//! functions.
use primitives::v1::{
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationSchedule,
ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, GlobalValidationData,
Id as ParaId, OccupiedCoreAssumption, LocalValidationData, SessionIndex, ValidationCode,
CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex,
GroupIndex, CandidateEvent,
};
use sp_runtime::traits::{One, BlakeTwo256, Hash as HashT, Saturating, Zero};
use sp_runtime::traits::Zero;
use frame_support::debug;
use crate::{initializer, inclusion, scheduler, configuration, paras};
@@ -160,16 +160,11 @@ pub fn availability_cores<T: initializer::Trait>() -> Vec<CoreState<T::BlockNumb
core_states
}
/// Implementation for the `global_validation_schedule` function of the runtime API.
pub fn global_validation_schedule<T: initializer::Trait>()
-> GlobalValidationSchedule<T::BlockNumber>
/// Implementation for the `global_validation_data` function of the runtime API.
pub fn global_validation_data<T: initializer::Trait>()
-> GlobalValidationData<T::BlockNumber>
{
let config = <configuration::Module<T>>::config();
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(),
}
<configuration::Module<T>>::global_validation_data()
}
/// 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,
assumption: OccupiedCoreAssumption,
) -> 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 {
OccupiedCoreAssumption::Included => {
<inclusion::Module<T>>::force_enact(para_id);
construct()
<paras::Module<T>>::local_validation_data(para_id)
}
OccupiedCoreAssumption::TimedOut => {
construct()
<paras::Module<T>>::local_validation_data(para_id)
}
OccupiedCoreAssumption::Free => {
if <inclusion::Module<T>>::pending_availability(para_id).is_some() {
None
} else {
construct()
<paras::Module<T>>::local_validation_data(para_id)
}
}
}