mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-21 01:41:03 +00:00
Ease parachain candidate code fetching (#2593)
* code stored in para + modify CandidateDescriptor. * WIP: digest + some more impl * validation_code_hash in payload + check in inclusion * check in client + refator * tests * fix encoding indices * remove old todos * fix test * fix test * add test * fetch validation code inside collation-generation from the relay-chain * HashMismatch -> PoVHashMismatch + miscompilation * refactor, store hash when needed * storage rename: more specific but slightly too verbose * do not hash on candidate validation, fetch hash instead * better test * fix test * guide updates * don't panic in runtime Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
This commit is contained in:
committed by
GitHub
parent
98082c5326
commit
beca01f118
@@ -33,7 +33,7 @@ use polkadot_node_subsystem::{
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
request_availability_cores_ctx, request_persisted_validation_data_ctx,
|
||||
request_validators_ctx,
|
||||
request_validators_ctx, request_validation_code_ctx,
|
||||
metrics::{self, prometheus},
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
@@ -244,9 +244,10 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
continue;
|
||||
}
|
||||
|
||||
// we get validation data synchronously for each core instead of
|
||||
// we get validation data and validation code synchronously for each core instead of
|
||||
// within the subtask loop, because we have only a single mutable handle to the
|
||||
// context, so the work can't really be distributed
|
||||
|
||||
let validation_data = match request_persisted_validation_data_ctx(
|
||||
relay_parent,
|
||||
scheduled_core.para_id,
|
||||
@@ -270,6 +271,30 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
}
|
||||
};
|
||||
|
||||
let validation_code = match request_validation_code_ctx(
|
||||
relay_parent,
|
||||
scheduled_core.para_id,
|
||||
assumption,
|
||||
ctx,
|
||||
)
|
||||
.await?
|
||||
.await??
|
||||
{
|
||||
Some(v) => v,
|
||||
None => {
|
||||
tracing::trace!(
|
||||
target: LOG_TARGET,
|
||||
core_idx = %core_idx,
|
||||
relay_parent = ?relay_parent,
|
||||
our_para = %config.para_id,
|
||||
their_para = %scheduled_core.para_id,
|
||||
"validation code is not available",
|
||||
);
|
||||
continue
|
||||
}
|
||||
};
|
||||
let validation_code_hash = validation_code.hash();
|
||||
|
||||
let task_config = config.clone();
|
||||
let mut task_sender = sender.clone();
|
||||
let metrics = metrics.clone();
|
||||
@@ -295,6 +320,7 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
&scheduled_core.para_id,
|
||||
&persisted_validation_data_hash,
|
||||
&pov_hash,
|
||||
&validation_code_hash,
|
||||
);
|
||||
|
||||
let erasure_root = match erasure_root(
|
||||
@@ -334,6 +360,7 @@ async fn handle_new_activations<Context: SubsystemContext>(
|
||||
pov_hash,
|
||||
erasure_root,
|
||||
para_head: commitments.head_data.hash(),
|
||||
validation_code_hash: validation_code_hash,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -474,7 +501,7 @@ mod tests {
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
BlockNumber, CollatorPair, Id as ParaId,
|
||||
PersistedValidationData, ScheduledCore,
|
||||
PersistedValidationData, ScheduledCore, ValidationCode,
|
||||
};
|
||||
use std::pin::Pin;
|
||||
|
||||
@@ -696,6 +723,16 @@ mod tests {
|
||||
))) => {
|
||||
tx.send(Ok(vec![Default::default(); 3])).unwrap();
|
||||
}
|
||||
Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
_hash,
|
||||
RuntimeApiRequest::ValidationCode(
|
||||
_para_id,
|
||||
OccupiedCoreAssumption::Free,
|
||||
tx,
|
||||
),
|
||||
))) => {
|
||||
tx.send(Ok(Some(ValidationCode(vec![1, 2, 3])))).unwrap();
|
||||
}
|
||||
Some(msg) => {
|
||||
panic!("didn't expect any other overseer requests; got {:?}", msg)
|
||||
}
|
||||
@@ -733,11 +770,13 @@ mod tests {
|
||||
let expect_validation_data_hash
|
||||
= PersistedValidationData::<Hash, BlockNumber>::default().hash();
|
||||
let expect_relay_parent = Hash::repeat_byte(4);
|
||||
let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash();
|
||||
let expect_payload = collator_signature_payload(
|
||||
&expect_relay_parent,
|
||||
&config.para_id,
|
||||
&expect_validation_data_hash,
|
||||
&expect_pov_hash,
|
||||
&expect_validation_code_hash,
|
||||
);
|
||||
let expect_descriptor = CandidateDescriptor {
|
||||
signature: config.key.sign(&expect_payload),
|
||||
@@ -748,6 +787,7 @@ mod tests {
|
||||
pov_hash: expect_pov_hash,
|
||||
erasure_root: Default::default(), // this isn't something we're checking right now
|
||||
para_head: test_collation().head_data.hash(),
|
||||
validation_code_hash: expect_validation_code_hash,
|
||||
};
|
||||
|
||||
assert_eq!(sent_messages.len(), 1);
|
||||
@@ -768,6 +808,7 @@ mod tests {
|
||||
&descriptor.para_id,
|
||||
&descriptor.persisted_validation_data_hash,
|
||||
&descriptor.pov_hash,
|
||||
&descriptor.validation_code_hash,
|
||||
)
|
||||
.as_ref(),
|
||||
&descriptor.collator,
|
||||
|
||||
@@ -361,21 +361,27 @@ async fn spawn_validate_exhaustive(
|
||||
|
||||
/// Does basic checks of a candidate. Provide the encoded PoV-block. Returns `Ok` if basic checks
|
||||
/// are passed, `Err` otherwise.
|
||||
#[tracing::instrument(level = "trace", skip(pov), fields(subsystem = LOG_TARGET))]
|
||||
#[tracing::instrument(level = "trace", skip(pov, validation_code), fields(subsystem = LOG_TARGET))]
|
||||
fn perform_basic_checks(
|
||||
candidate: &CandidateDescriptor,
|
||||
max_pov_size: u32,
|
||||
pov: &PoV,
|
||||
validation_code: &ValidationCode,
|
||||
) -> Result<(), InvalidCandidate> {
|
||||
let encoded_pov = pov.encode();
|
||||
let hash = pov.hash();
|
||||
let pov_hash = pov.hash();
|
||||
let validation_code_hash = validation_code.hash();
|
||||
|
||||
if encoded_pov.len() > max_pov_size as usize {
|
||||
return Err(InvalidCandidate::ParamsTooLarge(encoded_pov.len() as u64));
|
||||
}
|
||||
|
||||
if hash != candidate.pov_hash {
|
||||
return Err(InvalidCandidate::HashMismatch);
|
||||
if pov_hash != candidate.pov_hash {
|
||||
return Err(InvalidCandidate::PoVHashMismatch);
|
||||
}
|
||||
|
||||
if validation_code_hash != candidate.validation_code_hash {
|
||||
return Err(InvalidCandidate::CodeHashMismatch);
|
||||
}
|
||||
|
||||
if let Err(()) = candidate.check_collator_signature() {
|
||||
@@ -431,7 +437,12 @@ fn validate_candidate_exhaustive<B: ValidationBackend, S: SpawnNamed + 'static>(
|
||||
) -> Result<ValidationResult, ValidationFailed> {
|
||||
let _timer = metrics.time_validate_candidate_exhaustive();
|
||||
|
||||
if let Err(e) = perform_basic_checks(&descriptor, persisted_validation_data.max_pov_size, &*pov) {
|
||||
if let Err(e) = perform_basic_checks(
|
||||
&descriptor,
|
||||
persisted_validation_data.max_pov_size,
|
||||
&*pov,
|
||||
&validation_code,
|
||||
) {
|
||||
return Ok(ValidationResult::Invalid(e))
|
||||
}
|
||||
|
||||
@@ -601,6 +612,7 @@ mod tests {
|
||||
&descriptor.para_id,
|
||||
&descriptor.persisted_validation_data_hash,
|
||||
&descriptor.pov_hash,
|
||||
&descriptor.validation_code_hash,
|
||||
);
|
||||
|
||||
descriptor.signature = collator.sign(&payload[..]).into();
|
||||
@@ -891,13 +903,21 @@ mod tests {
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.para_head = head_data.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
assert!(perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov).is_ok());
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
@@ -911,7 +931,7 @@ mod tests {
|
||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||
MockValidationArg { result: Ok(validation_result) },
|
||||
validation_data.clone(),
|
||||
vec![1, 2, 3].into(),
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
TaskExecutor::new(),
|
||||
@@ -933,12 +953,20 @@ mod tests {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
assert!(perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov).is_ok());
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||
MockValidationArg {
|
||||
@@ -947,7 +975,7 @@ mod tests {
|
||||
))
|
||||
},
|
||||
validation_data,
|
||||
vec![1, 2, 3].into(),
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
TaskExecutor::new(),
|
||||
@@ -962,12 +990,20 @@ mod tests {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = validation_code.hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
assert!(perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov).is_ok());
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||
MockValidationArg {
|
||||
@@ -976,7 +1012,7 @@ mod tests {
|
||||
))
|
||||
},
|
||||
validation_data,
|
||||
vec![1, 2, 3].into(),
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
TaskExecutor::new(),
|
||||
@@ -985,4 +1021,42 @@ mod tests {
|
||||
|
||||
assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_code_mismatch_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1; 32]) };
|
||||
let validation_code = ValidationCode(vec![2; 16]);
|
||||
|
||||
let mut descriptor = CandidateDescriptor::default();
|
||||
descriptor.pov_hash = pov.hash();
|
||||
descriptor.validation_code_hash = ValidationCode(vec![1; 16]).hash();
|
||||
collator_sign(&mut descriptor, Sr25519Keyring::Alice);
|
||||
|
||||
let check = perform_basic_checks(
|
||||
&descriptor,
|
||||
validation_data.max_pov_size,
|
||||
&pov,
|
||||
&validation_code,
|
||||
);
|
||||
assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch));
|
||||
|
||||
let v = validate_candidate_exhaustive::<MockValidationBackend, _>(
|
||||
MockValidationArg {
|
||||
result: Err(ValidationError::InvalidCandidate(
|
||||
WasmInvalidCandidate::BadReturn
|
||||
))
|
||||
},
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
Arc::new(pov),
|
||||
TaskExecutor::new(),
|
||||
&Default::default(),
|
||||
).unwrap();
|
||||
|
||||
assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::CodeHashMismatch));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -556,6 +556,13 @@ mod tests {
|
||||
) -> BTreeMap<ParaId, Vec<InboundHrmpMessage>> {
|
||||
self.hrmp_channels.get(&recipient).map(|q| q.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn validation_code_by_hash(
|
||||
&self,
|
||||
_hash: Hash,
|
||||
) -> Option<ValidationCode> {
|
||||
unreachable!("not used in tests");
|
||||
}
|
||||
}
|
||||
|
||||
impl BabeApi<Block> for MockRuntimeApi {
|
||||
|
||||
@@ -124,11 +124,13 @@ pub enum InvalidCandidate {
|
||||
/// Invalid relay chain parent.
|
||||
BadParent,
|
||||
/// POV hash does not match.
|
||||
HashMismatch,
|
||||
PoVHashMismatch,
|
||||
/// Bad collator signature.
|
||||
BadSignature,
|
||||
/// Para head hash does not match.
|
||||
ParaHeadHashMismatch,
|
||||
/// Validation code hash does not match.
|
||||
CodeHashMismatch,
|
||||
}
|
||||
|
||||
/// Result of the validation of the candidate.
|
||||
|
||||
Reference in New Issue
Block a user