mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 04:11:09 +00:00
Malus: improvements in dispute ancestor and suggest garbage candidate implementation (#5011)
* Implement fake validation results Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * refactor Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * cargo lock Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * spell check Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * spellcheck Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * typos Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Review feedback Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * move stuff around Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * chores Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Impl valid - still wip Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * fixes Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * fmt Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Pull Ladi's implementation: https://github.com/paritytech/polkadot/pull/4711 Co-authored-by: Lldenaurois <Ljdenaurois@gmail.com> Co-authored-by: Andrei Sandu <andrei-mihail@parity.io> Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Fix build Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Logs and comments Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * WIP: suggest garbage candidate + implement validation result caching Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * fix Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Do commitment hash checks in candidate validation Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Minor refactor in approval, backing, dispute-coord Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Working version of suggest garbage candidate Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Dedup Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * cleanup #1 Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Fix tests Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * remove debug leftovers Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * fmt Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Accidentally commited some local test Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * spellcheck Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * some more fixes Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Refactor and fix it Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * review feedback Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * typo Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * tests review feedback Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * refactor disputer Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * fix tests Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Fix zombienet disputes test Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * spellcheck Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * fix Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * Fix ui tests Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> * fix typo Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> Co-authored-by: Lldenaurois <Ljdenaurois@gmail.com>
This commit is contained in:
@@ -2273,7 +2273,7 @@ async fn launch_approval(
|
||||
CandidateValidationMessage::ValidateFromExhaustive(
|
||||
available_data.validation_data,
|
||||
validation_code,
|
||||
candidate.descriptor.clone(),
|
||||
candidate.clone(),
|
||||
available_data.pov,
|
||||
APPROVAL_EXECUTION_TIMEOUT,
|
||||
val_tx,
|
||||
|
||||
@@ -31,8 +31,8 @@ use futures::{
|
||||
};
|
||||
|
||||
use polkadot_node_primitives::{
|
||||
AvailableData, PoV, SignedDisputeStatement, SignedFullStatement, Statement, ValidationResult,
|
||||
BACKING_EXECUTION_TIMEOUT,
|
||||
AvailableData, InvalidCandidate, PoV, SignedDisputeStatement, SignedFullStatement, Statement,
|
||||
ValidationResult, BACKING_EXECUTION_TIMEOUT,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
self as util,
|
||||
@@ -41,8 +41,8 @@ use polkadot_node_subsystem_util::{
|
||||
request_validators, FromJobCommand, JobSender, Validator,
|
||||
};
|
||||
use polkadot_primitives::v2::{
|
||||
BackedCandidate, CandidateCommitments, CandidateDescriptor, CandidateHash, CandidateReceipt,
|
||||
CollatorId, CommittedCandidateReceipt, CoreIndex, CoreState, Hash, Id as ParaId, SessionIndex,
|
||||
BackedCandidate, CandidateCommitments, CandidateHash, CandidateReceipt, CollatorId,
|
||||
CommittedCandidateReceipt, CoreIndex, CoreState, Hash, Id as ParaId, SessionIndex,
|
||||
SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
@@ -378,14 +378,14 @@ async fn request_pov(
|
||||
|
||||
async fn request_candidate_validation(
|
||||
sender: &mut JobSender<impl SubsystemSender>,
|
||||
candidate: CandidateDescriptor,
|
||||
candidate_receipt: CandidateReceipt,
|
||||
pov: Arc<PoV>,
|
||||
) -> Result<ValidationResult, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
sender
|
||||
.send_message(CandidateValidationMessage::ValidateFromChainState(
|
||||
candidate,
|
||||
candidate_receipt,
|
||||
pov,
|
||||
BACKING_EXECUTION_TIMEOUT,
|
||||
tx,
|
||||
@@ -456,11 +456,9 @@ async fn validate_and_make_available(
|
||||
.with_pov(&pov)
|
||||
.with_para_id(candidate.descriptor().para_id)
|
||||
});
|
||||
request_candidate_validation(&mut sender, candidate.descriptor.clone(), pov.clone()).await?
|
||||
request_candidate_validation(&mut sender, candidate.clone(), pov.clone()).await?
|
||||
};
|
||||
|
||||
let expected_commitments_hash = candidate.commitments_hash;
|
||||
|
||||
let res = match v {
|
||||
ValidationResult::Valid(commitments, validation_data) => {
|
||||
gum::debug!(
|
||||
@@ -469,41 +467,39 @@ async fn validate_and_make_available(
|
||||
"Validation successful",
|
||||
);
|
||||
|
||||
// If validation produces a new set of commitments, we vote the candidate as invalid.
|
||||
if commitments.hash() != expected_commitments_hash {
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?candidate.hash(),
|
||||
actual_commitments = ?commitments,
|
||||
"Commitments obtained with validation don't match the announced by the candidate receipt",
|
||||
);
|
||||
Err(candidate)
|
||||
} else {
|
||||
let erasure_valid = make_pov_available(
|
||||
&mut sender,
|
||||
n_validators,
|
||||
pov.clone(),
|
||||
candidate.hash(),
|
||||
validation_data,
|
||||
candidate.descriptor.erasure_root,
|
||||
span.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
let erasure_valid = make_pov_available(
|
||||
&mut sender,
|
||||
n_validators,
|
||||
pov.clone(),
|
||||
candidate.hash(),
|
||||
validation_data,
|
||||
candidate.descriptor.erasure_root,
|
||||
span.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
match erasure_valid {
|
||||
Ok(()) => Ok((candidate, commitments, pov.clone())),
|
||||
Err(InvalidErasureRoot) => {
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?candidate.hash(),
|
||||
actual_commitments = ?commitments,
|
||||
"Erasure root doesn't match the announced by the candidate receipt",
|
||||
);
|
||||
Err(candidate)
|
||||
},
|
||||
}
|
||||
match erasure_valid {
|
||||
Ok(()) => Ok((candidate, commitments, pov.clone())),
|
||||
Err(InvalidErasureRoot) => {
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?candidate.hash(),
|
||||
actual_commitments = ?commitments,
|
||||
"Erasure root doesn't match the announced by the candidate receipt",
|
||||
);
|
||||
Err(candidate)
|
||||
},
|
||||
}
|
||||
},
|
||||
ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch) => {
|
||||
// If validation produces a new set of commitments, we vote the candidate as invalid.
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?candidate.hash(),
|
||||
"Validation yielded different commitments",
|
||||
);
|
||||
Err(candidate)
|
||||
},
|
||||
ValidationResult::Invalid(reason) => {
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
|
||||
@@ -24,7 +24,8 @@ use futures::{future, Future};
|
||||
use polkadot_node_primitives::{BlockData, InvalidCandidate};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_primitives::v2::{
|
||||
CollatorId, GroupRotationInfo, HeadData, PersistedValidationData, ScheduledCore,
|
||||
CandidateDescriptor, CollatorId, GroupRotationInfo, HeadData, PersistedValidationData,
|
||||
ScheduledCore,
|
||||
};
|
||||
use polkadot_subsystem::{
|
||||
messages::{
|
||||
@@ -332,12 +333,12 @@ fn backing_second_works() {
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::CandidateValidation(
|
||||
CandidateValidationMessage::ValidateFromChainState(
|
||||
c,
|
||||
candidate_receipt,
|
||||
pov,
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && &candidate_receipt.descriptor == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT && candidate.commitments.hash() == candidate_receipt.commitments_hash => {
|
||||
tx.send(Ok(
|
||||
ValidationResult::Valid(CandidateCommitments {
|
||||
head_data: expected_head_data.clone(),
|
||||
@@ -419,6 +420,8 @@ fn backing_works() {
|
||||
.build();
|
||||
|
||||
let candidate_a_hash = candidate_a.hash();
|
||||
let candidate_a_commitments_hash = candidate_a.commitments.hash();
|
||||
|
||||
let public1 = CryptoStore::sr25519_generate_new(
|
||||
&*test_state.keystore,
|
||||
ValidatorId::ID,
|
||||
@@ -497,7 +500,7 @@ fn backing_works() {
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && c.descriptor() == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT && c.commitments_hash == candidate_a_commitments_hash=> {
|
||||
tx.send(Ok(
|
||||
ValidationResult::Valid(CandidateCommitments {
|
||||
head_data: expected_head_data.clone(),
|
||||
@@ -594,6 +597,8 @@ fn backing_works_while_validation_ongoing() {
|
||||
.build();
|
||||
|
||||
let candidate_a_hash = candidate_a.hash();
|
||||
let candidate_a_commitments_hash = candidate_a.commitments.hash();
|
||||
|
||||
let public1 = CryptoStore::sr25519_generate_new(
|
||||
&*test_state.keystore,
|
||||
ValidatorId::ID,
|
||||
@@ -691,7 +696,7 @@ fn backing_works_while_validation_ongoing() {
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && c.descriptor() == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT && candidate_a_commitments_hash == c.commitments_hash => {
|
||||
// we never validate the candidate. our local node
|
||||
// shouldn't issue any statements.
|
||||
std::mem::forget(tx);
|
||||
@@ -799,6 +804,8 @@ fn backing_misbehavior_works() {
|
||||
.build();
|
||||
|
||||
let candidate_a_hash = candidate_a.hash();
|
||||
let candidate_a_commitments_hash = candidate_a.commitments.hash();
|
||||
|
||||
let public2 = CryptoStore::sr25519_generate_new(
|
||||
&*test_state.keystore,
|
||||
ValidatorId::ID,
|
||||
@@ -865,7 +872,7 @@ fn backing_misbehavior_works() {
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && c.descriptor() == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT && candidate_a_commitments_hash == c.commitments_hash => {
|
||||
tx.send(Ok(
|
||||
ValidationResult::Valid(CandidateCommitments {
|
||||
head_data: expected_head_data.clone(),
|
||||
@@ -1025,7 +1032,7 @@ fn backing_dont_second_invalid() {
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && c.descriptor() == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap();
|
||||
}
|
||||
);
|
||||
@@ -1054,7 +1061,7 @@ fn backing_dont_second_invalid() {
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate_b.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && c.descriptor() == candidate_b.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
tx.send(Ok(
|
||||
ValidationResult::Valid(CandidateCommitments {
|
||||
head_data: expected_head_data.clone(),
|
||||
@@ -1185,7 +1192,7 @@ fn backing_second_after_first_fails_works() {
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && c.descriptor() == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT && c.commitments_hash == candidate.commitments.hash() => {
|
||||
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap();
|
||||
}
|
||||
);
|
||||
@@ -1319,7 +1326,7 @@ fn backing_works_after_failed_validation() {
|
||||
timeout,
|
||||
tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
|
||||
) if pov == pov && c.descriptor() == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT && c.commitments_hash == candidate.commitments.hash() => {
|
||||
tx.send(Err(ValidationFailed("Internal test error".into()))).unwrap();
|
||||
}
|
||||
);
|
||||
@@ -1696,7 +1703,7 @@ fn retry_works() {
|
||||
timeout,
|
||||
_tx,
|
||||
)
|
||||
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT
|
||||
) if pov == pov && c.descriptor() == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT && c.commitments_hash == candidate.commitments.hash()
|
||||
);
|
||||
virtual_overseer
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ use polkadot_node_subsystem::{
|
||||
use polkadot_node_subsystem_util::metrics::{self, prometheus};
|
||||
use polkadot_parachain::primitives::{ValidationParams, ValidationResult as WasmValidationResult};
|
||||
use polkadot_primitives::v2::{
|
||||
CandidateCommitments, CandidateDescriptor, Hash, OccupiedCoreAssumption,
|
||||
CandidateCommitments, CandidateDescriptor, CandidateReceipt, Hash, OccupiedCoreAssumption,
|
||||
PersistedValidationData, ValidationCode, ValidationCodeHash,
|
||||
};
|
||||
|
||||
@@ -134,7 +134,7 @@ where
|
||||
FromOverseer::Signal(OverseerSignal::Conclude) => return Ok(()),
|
||||
FromOverseer::Communication { msg } => match msg {
|
||||
CandidateValidationMessage::ValidateFromChainState(
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
pov,
|
||||
timeout,
|
||||
response_sender,
|
||||
@@ -149,7 +149,7 @@ where
|
||||
let res = validate_from_chain_state(
|
||||
&mut sender,
|
||||
validation_host,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
pov,
|
||||
timeout,
|
||||
&metrics,
|
||||
@@ -166,7 +166,7 @@ where
|
||||
CandidateValidationMessage::ValidateFromExhaustive(
|
||||
persisted_validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
pov,
|
||||
timeout,
|
||||
response_sender,
|
||||
@@ -181,7 +181,7 @@ where
|
||||
validation_host,
|
||||
persisted_validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
pov,
|
||||
timeout,
|
||||
&metrics,
|
||||
@@ -413,10 +413,32 @@ where
|
||||
AssumptionCheckOutcome::DoesNotMatch
|
||||
}
|
||||
|
||||
/// Returns validation data for a given candidate.
|
||||
pub async fn find_validation_data<Sender>(
|
||||
sender: &mut Sender,
|
||||
descriptor: &CandidateDescriptor,
|
||||
) -> Result<Option<(PersistedValidationData, ValidationCode)>, ValidationFailed>
|
||||
where
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
match find_assumed_validation_data(sender, &descriptor).await {
|
||||
AssumptionCheckOutcome::Matches(validation_data, validation_code) =>
|
||||
Ok(Some((validation_data, validation_code))),
|
||||
AssumptionCheckOutcome::DoesNotMatch => {
|
||||
// If neither the assumption of the occupied core having the para included or the assumption
|
||||
// of the occupied core timing out are valid, then the persisted_validation_data_hash in the descriptor
|
||||
// is not based on the relay parent and is thus invalid.
|
||||
Ok(None)
|
||||
},
|
||||
AssumptionCheckOutcome::BadRequest =>
|
||||
Err(ValidationFailed("Assumption Check: Bad request".into())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_from_chain_state<Sender>(
|
||||
sender: &mut Sender,
|
||||
validation_host: ValidationHost,
|
||||
descriptor: CandidateDescriptor,
|
||||
candidate_receipt: CandidateReceipt,
|
||||
pov: Arc<PoV>,
|
||||
timeout: Duration,
|
||||
metrics: &Metrics,
|
||||
@@ -424,25 +446,18 @@ async fn validate_from_chain_state<Sender>(
|
||||
where
|
||||
Sender: SubsystemSender,
|
||||
{
|
||||
let mut new_sender = sender.clone();
|
||||
let (validation_data, validation_code) =
|
||||
match find_assumed_validation_data(sender, &descriptor).await {
|
||||
AssumptionCheckOutcome::Matches(validation_data, validation_code) =>
|
||||
(validation_data, validation_code),
|
||||
AssumptionCheckOutcome::DoesNotMatch => {
|
||||
// If neither the assumption of the occupied core having the para included or the assumption
|
||||
// of the occupied core timing out are valid, then the persisted_validation_data_hash in the descriptor
|
||||
// is not based on the relay parent and is thus invalid.
|
||||
return Ok(ValidationResult::Invalid(InvalidCandidate::BadParent))
|
||||
},
|
||||
AssumptionCheckOutcome::BadRequest =>
|
||||
return Err(ValidationFailed("Assumption Check: Bad request".into())),
|
||||
match find_validation_data(&mut new_sender, &candidate_receipt.descriptor).await? {
|
||||
Some((validation_data, validation_code)) => (validation_data, validation_code),
|
||||
None => return Ok(ValidationResult::Invalid(InvalidCandidate::BadParent)),
|
||||
};
|
||||
|
||||
let validation_result = validate_candidate_exhaustive(
|
||||
validation_host,
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor.clone(),
|
||||
candidate_receipt.clone(),
|
||||
pov,
|
||||
timeout,
|
||||
metrics,
|
||||
@@ -450,11 +465,20 @@ where
|
||||
.await;
|
||||
|
||||
if let Ok(ValidationResult::Valid(ref outputs, _)) = validation_result {
|
||||
// If validation produces new commitments we consider the candidate invalid.
|
||||
if candidate_receipt.commitments_hash != outputs.hash() {
|
||||
return Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))
|
||||
}
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
match runtime_api_request(
|
||||
sender,
|
||||
descriptor.relay_parent,
|
||||
RuntimeApiRequest::CheckValidationOutputs(descriptor.para_id, outputs.clone(), tx),
|
||||
candidate_receipt.descriptor.relay_parent,
|
||||
RuntimeApiRequest::CheckValidationOutputs(
|
||||
candidate_receipt.descriptor.para_id,
|
||||
outputs.clone(),
|
||||
tx,
|
||||
),
|
||||
rx,
|
||||
)
|
||||
.await
|
||||
@@ -473,7 +497,7 @@ async fn validate_candidate_exhaustive(
|
||||
mut validation_backend: impl ValidationBackend,
|
||||
persisted_validation_data: PersistedValidationData,
|
||||
validation_code: ValidationCode,
|
||||
descriptor: CandidateDescriptor,
|
||||
candidate_receipt: CandidateReceipt,
|
||||
pov: Arc<PoV>,
|
||||
timeout: Duration,
|
||||
metrics: &Metrics,
|
||||
@@ -484,12 +508,12 @@ async fn validate_candidate_exhaustive(
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
?validation_code_hash,
|
||||
para_id = ?descriptor.para_id,
|
||||
para_id = ?candidate_receipt.descriptor.para_id,
|
||||
"About to validate a candidate.",
|
||||
);
|
||||
|
||||
if let Err(e) = perform_basic_checks(
|
||||
&descriptor,
|
||||
&candidate_receipt.descriptor,
|
||||
persisted_validation_data.max_pov_size,
|
||||
&*pov,
|
||||
&validation_code_hash,
|
||||
@@ -555,7 +579,7 @@ async fn validate_candidate_exhaustive(
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e))),
|
||||
|
||||
Ok(res) =>
|
||||
if res.head_data.hash() != descriptor.para_head {
|
||||
if res.head_data.hash() != candidate_receipt.descriptor.para_head {
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::ParaHeadHashMismatch))
|
||||
} else {
|
||||
let outputs = CandidateCommitments {
|
||||
@@ -566,7 +590,12 @@ async fn validate_candidate_exhaustive(
|
||||
processed_downward_messages: res.processed_downward_messages,
|
||||
hrmp_watermark: res.hrmp_watermark,
|
||||
};
|
||||
Ok(ValidationResult::Valid(outputs, persisted_validation_data))
|
||||
if candidate_receipt.commitments_hash != outputs.hash() {
|
||||
// If validation produced a new set of commitments, we treat the candidate as invalid.
|
||||
Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))
|
||||
} else {
|
||||
Ok(ValidationResult::Valid(outputs, persisted_validation_data))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,11 +406,22 @@ fn candidate_validation_ok_is_ok() {
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let commitments = CandidateCommitments {
|
||||
head_data: validation_result.head_data.clone(),
|
||||
upward_messages: validation_result.upward_messages.clone(),
|
||||
horizontal_messages: validation_result.horizontal_messages.clone(),
|
||||
new_validation_code: validation_result.new_validation_code.clone(),
|
||||
processed_downward_messages: validation_result.processed_downward_messages,
|
||||
hrmp_watermark: validation_result.hrmp_watermark,
|
||||
};
|
||||
|
||||
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() };
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data.clone(),
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
@@ -453,13 +464,15 @@ fn candidate_validation_bad_return_is_invalid() {
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() };
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Err(
|
||||
ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbiguousWorkerDeath),
|
||||
)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
@@ -495,13 +508,15 @@ fn candidate_validation_timeout_is_internal_error() {
|
||||
);
|
||||
assert!(check.is_ok());
|
||||
|
||||
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() };
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Err(
|
||||
ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout),
|
||||
)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
@@ -510,6 +525,52 @@ fn candidate_validation_timeout_is_internal_error() {
|
||||
assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_commitment_hash_mismatch_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
let pov = PoV { block_data: BlockData(vec![0xff; 32]) };
|
||||
let validation_code = ValidationCode(vec![0xff; 16]);
|
||||
let head_data = HeadData(vec![1, 1, 1]);
|
||||
|
||||
let candidate_receipt = CandidateReceipt {
|
||||
descriptor: make_valid_candidate_descriptor(
|
||||
1.into(),
|
||||
validation_data.parent_head.hash(),
|
||||
validation_data.hash(),
|
||||
pov.hash(),
|
||||
validation_code.hash(),
|
||||
head_data.hash(),
|
||||
dummy_hash(),
|
||||
Sr25519Keyring::Alice,
|
||||
),
|
||||
commitments_hash: Hash::zero(),
|
||||
};
|
||||
|
||||
// This will result in different commitments for this candidate.
|
||||
let validation_result = WasmValidationResult {
|
||||
head_data,
|
||||
new_validation_code: None,
|
||||
upward_messages: Vec::new(),
|
||||
horizontal_messages: Vec::new(),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: 12345,
|
||||
};
|
||||
|
||||
let result = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
// Ensure `post validation` check on the commitments hash works as expected.
|
||||
assert_matches!(result, ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_validation_code_mismatch_is_invalid() {
|
||||
let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() };
|
||||
@@ -536,13 +597,15 @@ fn candidate_validation_code_mismatch_is_invalid() {
|
||||
);
|
||||
assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch));
|
||||
|
||||
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() };
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Err(
|
||||
ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout),
|
||||
)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
@@ -583,11 +646,22 @@ fn compressed_code_works() {
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let commitments = CandidateCommitments {
|
||||
head_data: validation_result.head_data.clone(),
|
||||
upward_messages: validation_result.upward_messages.clone(),
|
||||
horizontal_messages: validation_result.horizontal_messages.clone(),
|
||||
new_validation_code: validation_result.new_validation_code.clone(),
|
||||
processed_downward_messages: validation_result.processed_downward_messages,
|
||||
hrmp_watermark: validation_result.hrmp_watermark,
|
||||
};
|
||||
|
||||
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() };
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
@@ -628,11 +702,13 @@ fn code_decompression_failure_is_invalid() {
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() };
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
@@ -674,11 +750,13 @@ fn pov_decompression_failure_is_invalid() {
|
||||
hrmp_watermark: 0,
|
||||
};
|
||||
|
||||
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: Hash::zero() };
|
||||
|
||||
let v = executor::block_on(validate_candidate_exhaustive(
|
||||
MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result)),
|
||||
validation_data,
|
||||
validation_code,
|
||||
descriptor,
|
||||
candidate_receipt,
|
||||
Arc::new(pov),
|
||||
Duration::from_secs(0),
|
||||
&Default::default(),
|
||||
|
||||
@@ -362,7 +362,7 @@ async fn participate(
|
||||
CandidateValidationMessage::ValidateFromExhaustive(
|
||||
available_data.validation_data,
|
||||
validation_code,
|
||||
req.candidate_receipt().descriptor.clone(),
|
||||
req.candidate_receipt().clone(),
|
||||
available_data.pov,
|
||||
APPROVAL_EXECUTION_TIMEOUT,
|
||||
validation_tx,
|
||||
@@ -393,6 +393,7 @@ async fn participate(
|
||||
|
||||
send_result(&mut result_sender, req, ParticipationOutcome::Invalid).await;
|
||||
},
|
||||
|
||||
Ok(Ok(ValidationResult::Invalid(invalid))) => {
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
@@ -403,19 +404,8 @@ async fn participate(
|
||||
|
||||
send_result(&mut result_sender, req, ParticipationOutcome::Invalid).await;
|
||||
},
|
||||
Ok(Ok(ValidationResult::Valid(commitments, _))) => {
|
||||
if commitments.hash() != req.candidate_receipt().commitments_hash {
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
expected = ?req.candidate_receipt().commitments_hash,
|
||||
got = ?commitments.hash(),
|
||||
"Candidate is valid but commitments hash doesn't match",
|
||||
);
|
||||
|
||||
send_result(&mut result_sender, req, ParticipationOutcome::Invalid).await;
|
||||
} else {
|
||||
send_result(&mut result_sender, req, ParticipationOutcome::Valid).await;
|
||||
}
|
||||
Ok(Ok(ValidationResult::Valid(_, _))) => {
|
||||
send_result(&mut result_sender, req, ParticipationOutcome::Valid).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,10 @@ async fn activate_leaf(
|
||||
}
|
||||
|
||||
/// Full participation happy path as seen via the overseer.
|
||||
pub async fn participation_full_happy_path(ctx_handle: &mut VirtualOverseer) {
|
||||
pub async fn participation_full_happy_path(
|
||||
ctx_handle: &mut VirtualOverseer,
|
||||
expected_commitments_hash: Hash,
|
||||
) {
|
||||
recover_available_data(ctx_handle).await;
|
||||
fetch_validation_code(ctx_handle).await;
|
||||
store_available_data(ctx_handle, true).await;
|
||||
@@ -116,9 +119,13 @@ pub async fn participation_full_happy_path(ctx_handle: &mut VirtualOverseer) {
|
||||
assert_matches!(
|
||||
ctx_handle.recv().await,
|
||||
AllMessages::CandidateValidation(
|
||||
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, timeout, tx)
|
||||
CandidateValidationMessage::ValidateFromExhaustive(_, _, candidate_receipt, _, timeout, tx)
|
||||
) if timeout == APPROVAL_EXECUTION_TIMEOUT => {
|
||||
tx.send(Ok(ValidationResult::Valid(dummy_candidate_commitments(None), PersistedValidationData::default()))).unwrap();
|
||||
if expected_commitments_hash != candidate_receipt.commitments_hash {
|
||||
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap();
|
||||
} else {
|
||||
tx.send(Ok(ValidationResult::Valid(dummy_candidate_commitments(None), PersistedValidationData::default()))).unwrap();
|
||||
}
|
||||
},
|
||||
"overseer did not receive candidate validation message",
|
||||
);
|
||||
@@ -438,7 +445,7 @@ fn cast_invalid_vote_if_validation_fails_or_is_invalid() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cast_invalid_vote_if_validation_passes_but_commitments_dont_match() {
|
||||
fn cast_invalid_vote_if_commitments_dont_match() {
|
||||
futures::executor::block_on(async {
|
||||
let (mut ctx, mut ctx_handle) = make_our_subsystem_context(TaskExecutor::new());
|
||||
|
||||
@@ -459,11 +466,7 @@ fn cast_invalid_vote_if_validation_passes_but_commitments_dont_match() {
|
||||
AllMessages::CandidateValidation(
|
||||
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, timeout, tx)
|
||||
) if timeout == APPROVAL_EXECUTION_TIMEOUT => {
|
||||
let mut commitments = CandidateCommitments::default();
|
||||
// this should lead to a commitments hash mismatch
|
||||
commitments.processed_downward_messages = 42;
|
||||
|
||||
tx.send(Ok(ValidationResult::Valid(commitments, PersistedValidationData::default()))).unwrap();
|
||||
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap();
|
||||
},
|
||||
"overseer did not receive candidate validation message",
|
||||
);
|
||||
|
||||
@@ -407,8 +407,9 @@ where
|
||||
async fn participation_with_distribution(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
candidate_hash: &CandidateHash,
|
||||
expected_commitments_hash: Hash,
|
||||
) {
|
||||
participation_full_happy_path(virtual_overseer).await;
|
||||
participation_full_happy_path(virtual_overseer, expected_commitments_hash).await;
|
||||
assert_matches!(
|
||||
virtual_overseer.recv().await,
|
||||
AllMessages::DisputeDistribution(
|
||||
@@ -426,7 +427,6 @@ fn make_valid_candidate_receipt() -> CandidateReceipt {
|
||||
}
|
||||
|
||||
fn make_invalid_candidate_receipt() -> CandidateReceipt {
|
||||
// Commitments hash will be 0, which is not correct:
|
||||
dummy_candidate_receipt_bad_sig(Default::default(), Some(Default::default()))
|
||||
}
|
||||
|
||||
@@ -593,7 +593,12 @@ fn dispute_gets_confirmed_via_participation() {
|
||||
})
|
||||
.await;
|
||||
|
||||
participation_with_distribution(&mut virtual_overseer, &candidate_hash1).await;
|
||||
participation_with_distribution(
|
||||
&mut virtual_overseer,
|
||||
&candidate_hash1,
|
||||
candidate_receipt1.commitments_hash,
|
||||
)
|
||||
.await;
|
||||
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@@ -942,7 +947,12 @@ fn conflicting_votes_lead_to_dispute_participation() {
|
||||
})
|
||||
.await;
|
||||
|
||||
participation_with_distribution(&mut virtual_overseer, &candidate_hash).await;
|
||||
participation_with_distribution(
|
||||
&mut virtual_overseer,
|
||||
&candidate_hash,
|
||||
candidate_receipt.commitments_hash,
|
||||
)
|
||||
.await;
|
||||
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@@ -1224,7 +1234,12 @@ fn finality_votes_ignore_disputed_candidates() {
|
||||
})
|
||||
.await;
|
||||
|
||||
participation_with_distribution(&mut virtual_overseer, &candidate_hash).await;
|
||||
participation_with_distribution(
|
||||
&mut virtual_overseer,
|
||||
&candidate_hash,
|
||||
candidate_receipt.commitments_hash,
|
||||
)
|
||||
.await;
|
||||
|
||||
{
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@@ -1322,7 +1337,12 @@ fn supermajority_valid_dispute_may_be_finalized() {
|
||||
})
|
||||
.await;
|
||||
|
||||
participation_with_distribution(&mut virtual_overseer, &candidate_hash).await;
|
||||
participation_with_distribution(
|
||||
&mut virtual_overseer,
|
||||
&candidate_hash,
|
||||
candidate_receipt.commitments_hash,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut statements = Vec::new();
|
||||
for i in (0..supermajority_threshold - 1).map(|i| i + 3) {
|
||||
@@ -1442,7 +1462,12 @@ fn concluded_supermajority_for_non_active_after_time() {
|
||||
})
|
||||
.await;
|
||||
|
||||
participation_with_distribution(&mut virtual_overseer, &candidate_hash).await;
|
||||
participation_with_distribution(
|
||||
&mut virtual_overseer,
|
||||
&candidate_hash,
|
||||
candidate_receipt.commitments_hash,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut statements = Vec::new();
|
||||
// -2: 1 for already imported vote and one for local vote (which is valid).
|
||||
@@ -1543,7 +1568,13 @@ fn concluded_supermajority_against_non_active_after_time() {
|
||||
ImportStatementsResult::ValidImport => {}
|
||||
);
|
||||
|
||||
participation_with_distribution(&mut virtual_overseer, &candidate_hash).await;
|
||||
// Use a different expected commitments hash to ensure the candidate validation returns invalid.
|
||||
participation_with_distribution(
|
||||
&mut virtual_overseer,
|
||||
&candidate_hash,
|
||||
CandidateCommitments::default().hash(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut statements = Vec::new();
|
||||
// minus 2, because of local vote and one previously imported invalid vote.
|
||||
@@ -1580,7 +1611,6 @@ fn concluded_supermajority_against_non_active_after_time() {
|
||||
.await;
|
||||
|
||||
assert!(rx.await.unwrap().is_empty());
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
virtual_overseer
|
||||
@@ -1672,7 +1702,12 @@ fn resume_dispute_without_local_statement() {
|
||||
let candidate_receipt = make_valid_candidate_receipt();
|
||||
let candidate_hash = candidate_receipt.hash();
|
||||
|
||||
participation_with_distribution(&mut virtual_overseer, &candidate_hash).await;
|
||||
participation_with_distribution(
|
||||
&mut virtual_overseer,
|
||||
&candidate_hash,
|
||||
candidate_receipt.commitments_hash,
|
||||
)
|
||||
.await;
|
||||
|
||||
let valid_vote0 = test_state
|
||||
.issue_explicit_statement_with_index(0, candidate_hash, session, true)
|
||||
|
||||
Reference in New Issue
Block a user