Skip to content

Commit

Permalink
candidate-validation: RFC103 implementation (paritytech#5847)
Browse files Browse the repository at this point in the history
Part of paritytech#5047
On top of paritytech#5679

---------

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
sandreim and actions-user authored Oct 31, 2024
1 parent dd9924f commit 2700dbf
Show file tree
Hide file tree
Showing 7 changed files with 571 additions and 40 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions polkadot/node/core/candidate-validation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ polkadot-node-subsystem-test-helpers = { workspace = true }
sp-maybe-compressed-blob = { workspace = true, default-features = true }
sp-core = { workspace = true, default-features = true }
polkadot-primitives-test-helpers = { workspace = true }
rstest = { workspace = true }
polkadot-primitives = { workspace = true, features = ["test"] }
166 changes: 137 additions & 29 deletions polkadot/node/core/candidate-validation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use polkadot_node_subsystem::{
overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, SubsystemResult,
SubsystemSender,
};
use polkadot_node_subsystem_util as util;
use polkadot_node_subsystem_util::{self as util, runtime::ClaimQueueSnapshot};
use polkadot_overseer::ActiveLeavesUpdate;
use polkadot_parachain_primitives::primitives::ValidationResult as WasmValidationResult;
use polkadot_primitives::{
Expand All @@ -46,8 +46,9 @@ use polkadot_primitives::{
DEFAULT_LENIENT_PREPARATION_TIMEOUT, DEFAULT_PRECHECK_PREPARATION_TIMEOUT,
},
vstaging::{
CandidateDescriptorV2 as CandidateDescriptor, CandidateEvent,
transpose_claim_queue, CandidateDescriptorV2 as CandidateDescriptor, CandidateEvent,
CandidateReceiptV2 as CandidateReceipt,
CommittedCandidateReceiptV2 as CommittedCandidateReceipt,
},
AuthorityDiscoveryId, CandidateCommitments, ExecutorParams, Hash, PersistedValidationData,
PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, ValidationCode,
Expand Down Expand Up @@ -148,6 +149,25 @@ impl<Context> CandidateValidationSubsystem {
}
}

// Returns the claim queue at relay parent and logs a warning if it is not available.
async fn claim_queue<Sender>(relay_parent: Hash, sender: &mut Sender) -> Option<ClaimQueueSnapshot>
where
Sender: SubsystemSender<RuntimeApiMessage>,
{
match util::runtime::fetch_claim_queue(sender, relay_parent).await {
Ok(maybe_cq) => maybe_cq,
Err(err) => {
gum::warn!(
target: LOG_TARGET,
?relay_parent,
?err,
"Claim queue not available"
);
None
},
}
}

fn handle_validation_message<S>(
mut sender: S,
validation_host: ValidationHost,
Expand All @@ -167,24 +187,40 @@ where
exec_kind,
response_sender,
..
} => async move {
let _timer = metrics.time_validate_from_exhaustive();
let res = validate_candidate_exhaustive(
validation_host,
validation_data,
validation_code,
candidate_receipt,
pov,
executor_params,
exec_kind,
&metrics,
)
.await;
} =>
async move {
let _timer = metrics.time_validate_from_exhaustive();
let relay_parent = candidate_receipt.descriptor.relay_parent();

let maybe_claim_queue = claim_queue(relay_parent, &mut sender).await;

let maybe_expected_session_index =
match util::request_session_index_for_child(relay_parent, &mut sender)
.await
.await
{
Ok(Ok(expected_session_index)) => Some(expected_session_index),
_ => None,
};

let res = validate_candidate_exhaustive(
maybe_expected_session_index,
validation_host,
validation_data,
validation_code,
candidate_receipt,
pov,
executor_params,
exec_kind,
&metrics,
maybe_claim_queue,
)
.await;

metrics.on_validation_event(&res);
let _ = response_sender.send(res);
}
.boxed(),
metrics.on_validation_event(&res);
let _ = response_sender.send(res);
}
.boxed(),
CandidateValidationMessage::PreCheck {
relay_parent,
validation_code_hash,
Expand Down Expand Up @@ -637,6 +673,7 @@ where
}

async fn validate_candidate_exhaustive(
maybe_expected_session_index: Option<SessionIndex>,
mut validation_backend: impl ValidationBackend + Send,
persisted_validation_data: PersistedValidationData,
validation_code: ValidationCode,
Expand All @@ -645,18 +682,41 @@ async fn validate_candidate_exhaustive(
executor_params: ExecutorParams,
exec_kind: PvfExecKind,
metrics: &Metrics,
maybe_claim_queue: Option<ClaimQueueSnapshot>,
) -> Result<ValidationResult, ValidationFailed> {
let _timer = metrics.time_validate_candidate_exhaustive();

let validation_code_hash = validation_code.hash();
let relay_parent = candidate_receipt.descriptor.relay_parent();
let para_id = candidate_receipt.descriptor.para_id();

gum::debug!(
target: LOG_TARGET,
?validation_code_hash,
?para_id,
"About to validate a candidate.",
);

// We only check the session index for backing.
match (exec_kind, candidate_receipt.descriptor.session_index()) {
(PvfExecKind::Backing | PvfExecKind::BackingSystemParas, Some(session_index)) => {
let Some(expected_session_index) = maybe_expected_session_index else {
let error = "cannot fetch session index from the runtime";
gum::warn!(
target: LOG_TARGET,
?relay_parent,
error,
);

return Err(ValidationFailed(error.into()))
};

if session_index != expected_session_index {
return Ok(ValidationResult::Invalid(InvalidCandidate::InvalidSessionIndex))
}
},
(_, _) => {},
};

if let Err(e) = perform_basic_checks(
&candidate_receipt.descriptor,
persisted_validation_data.max_pov_size,
Expand Down Expand Up @@ -754,15 +814,21 @@ async fn validate_candidate_exhaustive(
gum::info!(target: LOG_TARGET, ?para_id, "Invalid candidate (para_head)");
Ok(ValidationResult::Invalid(InvalidCandidate::ParaHeadHashMismatch))
} else {
let outputs = CandidateCommitments {
head_data: res.head_data,
upward_messages: res.upward_messages,
horizontal_messages: res.horizontal_messages,
new_validation_code: res.new_validation_code,
processed_downward_messages: res.processed_downward_messages,
hrmp_watermark: res.hrmp_watermark,
let committed_candidate_receipt = CommittedCandidateReceipt {
descriptor: candidate_receipt.descriptor.clone(),
commitments: CandidateCommitments {
head_data: res.head_data,
upward_messages: res.upward_messages,
horizontal_messages: res.horizontal_messages,
new_validation_code: res.new_validation_code,
processed_downward_messages: res.processed_downward_messages,
hrmp_watermark: res.hrmp_watermark,
},
};
if candidate_receipt.commitments_hash != outputs.hash() {

if candidate_receipt.commitments_hash !=
committed_candidate_receipt.commitments.hash()
{
gum::info!(
target: LOG_TARGET,
?para_id,
Expand All @@ -773,7 +839,48 @@ async fn validate_candidate_exhaustive(
// invalid.
Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))
} else {
Ok(ValidationResult::Valid(outputs, (*persisted_validation_data).clone()))
let core_index = candidate_receipt.descriptor.core_index();

match (core_index, exec_kind) {
// Core selectors are optional for V2 descriptors, but we still check the
// descriptor core index.
(
Some(_core_index),
PvfExecKind::Backing | PvfExecKind::BackingSystemParas,
) => {
let Some(claim_queue) = maybe_claim_queue else {
let error = "cannot fetch the claim queue from the runtime";
gum::warn!(
target: LOG_TARGET,
?relay_parent,
error
);

return Err(ValidationFailed(error.into()))
};

if let Err(err) = committed_candidate_receipt
.check_core_index(&transpose_claim_queue(claim_queue.0))
{
gum::warn!(
target: LOG_TARGET,
?err,
candidate_hash = ?candidate_receipt.hash(),
"Candidate core index is invalid",
);
return Ok(ValidationResult::Invalid(
InvalidCandidate::InvalidCoreIndex,
))
}
},
// No checks for approvals and disputes
(_, _) => {},
}

Ok(ValidationResult::Valid(
committed_candidate_receipt.commitments,
(*persisted_validation_data).clone(),
))
}
},
}
Expand Down Expand Up @@ -1003,6 +1110,7 @@ fn perform_basic_checks(
return Err(InvalidCandidate::CodeHashMismatch)
}

// No-op for `v2` receipts.
if let Err(()) = candidate.check_collator_signature() {
return Err(InvalidCandidate::BadSignature)
}
Expand Down
Loading

0 comments on commit 2700dbf

Please sign in to comment.