Make candidate validation timeouts configurable (#4001)

* pvf: make execution timeout configurable

* guide: add timeouts to candidate validation params

* add timeouts to candidate validation messages

* fmt

* port backing to use the backing pvf timeout

* port approval-voting to use the execution timeout

* port dispute participation to use the correct timeout

* fmt

* address grumbles & test failure
This commit is contained in:
Robert Habermeier
2021-10-04 16:53:36 +02:00
committed by GitHub
parent 114e757988
commit 6002865874
19 changed files with 192 additions and 62 deletions
@@ -27,7 +27,7 @@ use polkadot_node_primitives::{
approval::{
BlockApprovalMeta, DelayTranche, IndirectAssignmentCert, IndirectSignedApprovalVote,
},
SignedDisputeStatement, ValidationResult,
SignedDisputeStatement, ValidationResult, APPROVAL_EXECUTION_TIMEOUT,
};
use polkadot_node_subsystem::{
errors::RecoveryError,
@@ -2235,6 +2235,7 @@ async fn launch_approval(
validation_code,
candidate.descriptor.clone(),
available_data.pov,
APPROVAL_EXECUTION_TIMEOUT,
val_tx,
)
.into(),
+7 -1
View File
@@ -32,6 +32,7 @@ use futures::{
use polkadot_node_primitives::{
AvailableData, PoV, SignedDisputeStatement, SignedFullStatement, Statement, ValidationResult,
BACKING_EXECUTION_TIMEOUT,
};
use polkadot_node_subsystem_util::{
self as util,
@@ -380,7 +381,12 @@ async fn request_candidate_validation(
let (tx, rx) = oneshot::channel();
sender
.send_message(CandidateValidationMessage::ValidateFromChainState(candidate, pov, tx))
.send_message(CandidateValidationMessage::ValidateFromChainState(
candidate,
pov,
BACKING_EXECUTION_TIMEOUT,
tx,
))
.await;
match rx.await {
+19 -9
View File
@@ -317,9 +317,10 @@ fn backing_second_works() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate.descriptor() => {
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
tx.send(Ok(
ValidationResult::Valid(CandidateCommitments {
head_data: expected_head_data.clone(),
@@ -476,9 +477,10 @@ fn backing_works() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate_a.descriptor() => {
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
tx.send(Ok(
ValidationResult::Valid(CandidateCommitments {
head_data: expected_head_data.clone(),
@@ -669,9 +671,10 @@ fn backing_works_while_validation_ongoing() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate_a.descriptor() => {
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
// we never validate the candidate. our local node
// shouldn't issue any statements.
std::mem::forget(tx);
@@ -834,9 +837,10 @@ fn backing_misbehavior_works() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate_a.descriptor() => {
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
tx.send(Ok(
ValidationResult::Valid(CandidateCommitments {
head_data: expected_head_data.clone(),
@@ -980,9 +984,10 @@ fn backing_dont_second_invalid() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate_a.descriptor() => {
) if pov == pov && &c == candidate_a.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap();
}
);
@@ -1008,9 +1013,10 @@ fn backing_dont_second_invalid() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate_b.descriptor() => {
) if pov == pov && &c == candidate_b.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
tx.send(Ok(
ValidationResult::Valid(CandidateCommitments {
head_data: expected_head_data.clone(),
@@ -1138,9 +1144,10 @@ fn backing_second_after_first_fails_works() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate.descriptor() => {
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap();
}
);
@@ -1186,6 +1193,7 @@ fn backing_second_after_first_fails_works() {
_,
pov,
_,
_,
)
) => {
assert_eq!(&*pov, &pov_to_second);
@@ -1270,9 +1278,10 @@ fn backing_works_after_failed_validation() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
tx,
)
) if pov == pov && &c == candidate.descriptor() => {
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT => {
tx.send(Err(ValidationFailed("Internal test error".into()))).unwrap();
}
);
@@ -1646,9 +1655,10 @@ fn retry_works() {
CandidateValidationMessage::ValidateFromChainState(
c,
pov,
timeout,
_tx,
)
) if pov == pov && &c == candidate.descriptor()
) if pov == pov && &c == candidate.descriptor() && timeout == BACKING_EXECUTION_TIMEOUT
);
virtual_overseer
});
@@ -48,7 +48,7 @@ use parity_scale_codec::Encode;
use futures::{channel::oneshot, prelude::*};
use std::{path::PathBuf, sync::Arc};
use std::{path::PathBuf, sync::Arc, time::Duration};
use async_trait::async_trait;
@@ -135,6 +135,7 @@ where
CandidateValidationMessage::ValidateFromChainState(
descriptor,
pov,
timeout,
response_sender,
) => {
let bg = {
@@ -149,6 +150,7 @@ where
validation_host,
descriptor,
pov,
timeout,
&metrics,
)
.await;
@@ -165,6 +167,7 @@ where
validation_code,
descriptor,
pov,
timeout,
response_sender,
) => {
let bg = {
@@ -179,6 +182,7 @@ where
validation_code,
descriptor,
pov,
timeout,
&metrics,
)
.await;
@@ -322,6 +326,7 @@ async fn validate_from_chain_state<Sender>(
validation_host: ValidationHost,
descriptor: CandidateDescriptor,
pov: Arc<PoV>,
timeout: Duration,
metrics: &Metrics,
) -> Result<ValidationResult, ValidationFailed>
where
@@ -347,6 +352,7 @@ where
validation_code,
descriptor.clone(),
pov,
timeout,
metrics,
)
.await;
@@ -377,6 +383,7 @@ async fn validate_candidate_exhaustive(
validation_code: ValidationCode,
descriptor: CandidateDescriptor,
pov: Arc<PoV>,
timeout: Duration,
metrics: &Metrics,
) -> Result<ValidationResult, ValidationFailed> {
let _timer = metrics.time_validate_candidate_exhaustive();
@@ -430,7 +437,7 @@ async fn validate_candidate_exhaustive(
};
let result = validation_backend
.validate_candidate(raw_validation_code.to_vec(), params)
.validate_candidate(raw_validation_code.to_vec(), timeout, params)
.await;
if let Err(ref e) = result {
@@ -475,6 +482,7 @@ trait ValidationBackend {
async fn validate_candidate(
&mut self,
raw_validation_code: Vec<u8>,
timeout: Duration,
params: ValidationParams,
) -> Result<WasmValidationResult, ValidationError>;
}
@@ -484,12 +492,14 @@ impl ValidationBackend for ValidationHost {
async fn validate_candidate(
&mut self,
raw_validation_code: Vec<u8>,
timeout: Duration,
params: ValidationParams,
) -> Result<WasmValidationResult, ValidationError> {
let (tx, rx) = oneshot::channel();
if let Err(err) = self
.execute_pvf(
Pvf::from_code(raw_validation_code),
timeout,
params.encode(),
polkadot_node_core_pvf::Priority::Normal,
tx,
@@ -341,6 +341,7 @@ impl ValidationBackend for MockValidatorBackend {
async fn validate_candidate(
&mut self,
_raw_validation_code: Vec<u8>,
_timeout: Duration,
_params: ValidationParams,
) -> Result<WasmValidationResult, ValidationError> {
self.result.clone()
@@ -384,6 +385,7 @@ fn candidate_validation_ok_is_ok() {
validation_code,
descriptor,
Arc::new(pov),
Duration::from_secs(0),
&Default::default(),
))
.unwrap();
@@ -426,6 +428,7 @@ fn candidate_validation_bad_return_is_invalid() {
validation_code,
descriptor,
Arc::new(pov),
Duration::from_secs(0),
&Default::default(),
))
.unwrap();
@@ -461,6 +464,7 @@ fn candidate_validation_timeout_is_internal_error() {
validation_code,
descriptor,
Arc::new(pov),
Duration::from_secs(0),
&Default::default(),
));
@@ -495,6 +499,7 @@ fn candidate_validation_code_mismatch_is_invalid() {
validation_code,
descriptor,
Arc::new(pov),
Duration::from_secs(0),
&Default::default(),
))
.unwrap();
@@ -534,6 +539,7 @@ fn compressed_code_works() {
validation_code,
descriptor,
Arc::new(pov),
Duration::from_secs(0),
&Default::default(),
));
@@ -573,6 +579,7 @@ fn code_decompression_failure_is_invalid() {
validation_code,
descriptor,
Arc::new(pov),
Duration::from_secs(0),
&Default::default(),
));
@@ -613,6 +620,7 @@ fn pov_decompression_failure_is_invalid() {
validation_code,
descriptor,
Arc::new(pov),
Duration::from_secs(0),
&Default::default(),
));
@@ -22,7 +22,7 @@
use futures::{channel::oneshot, prelude::*};
use polkadot_node_primitives::ValidationResult;
use polkadot_node_primitives::{ValidationResult, APPROVAL_EXECUTION_TIMEOUT};
use polkadot_node_subsystem::{
errors::{RecoveryError, RuntimeApiError},
messages::{
@@ -269,11 +269,16 @@ async fn participate(
// we issue a request to validate the candidate with the provided exhaustive
// parameters
//
// We use the approval execution timeout because this is intended to
// be run outside of backing and therefore should be subject to the
// same level of leeway.
ctx.send_message(CandidateValidationMessage::ValidateFromExhaustive(
available_data.validation_data,
validation_code,
candidate_receipt.descriptor.clone(),
available_data.pov,
APPROVAL_EXECUTION_TIMEOUT,
validation_tx,
))
.await;
@@ -295,8 +295,8 @@ fn cast_invalid_vote_if_validation_fails_or_is_invalid() {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::CandidateValidation(
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, tx)
) => {
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, timeout, tx)
) if timeout == APPROVAL_EXECUTION_TIMEOUT => {
tx.send(Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))).unwrap();
},
"overseer did not receive candidate validation message",
@@ -331,8 +331,8 @@ fn cast_invalid_vote_if_validation_passes_but_commitments_dont_match() {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::CandidateValidation(
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, tx)
) => {
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;
@@ -371,8 +371,8 @@ fn cast_valid_vote_if_validation_passes() {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::CandidateValidation(
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, tx)
) => {
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, timeout, tx)
) if timeout == APPROVAL_EXECUTION_TIMEOUT => {
tx.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))).unwrap();
},
"overseer did not receive candidate validation message",
@@ -408,8 +408,8 @@ fn failure_to_store_available_data_does_not_preclude_participation() {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::CandidateValidation(
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, tx)
) => {
CandidateValidationMessage::ValidateFromExhaustive(_, _, _, _, timeout, tx)
) if timeout == APPROVAL_EXECUTION_TIMEOUT => {
tx.send(Err(ValidationFailed("fail".to_string()))).unwrap();
},
"overseer did not receive candidate validation message",
+16 -4
View File
@@ -38,11 +38,17 @@ slotmap::new_key_type! { struct Worker; }
#[derive(Debug)]
pub enum ToQueue {
Enqueue { artifact: ArtifactPathId, params: Vec<u8>, result_tx: ResultSender },
Enqueue {
artifact: ArtifactPathId,
execution_timeout: Duration,
params: Vec<u8>,
result_tx: ResultSender,
},
}
struct ExecuteJob {
artifact: ArtifactPathId,
execution_timeout: Duration,
params: Vec<u8>,
result_tx: ResultSender,
}
@@ -167,14 +173,14 @@ async fn purge_dead(metrics: &Metrics, workers: &mut Workers) {
}
fn handle_to_queue(queue: &mut Queue, to_queue: ToQueue) {
let ToQueue::Enqueue { artifact, params, result_tx } = to_queue;
let ToQueue::Enqueue { artifact, execution_timeout, params, result_tx } = to_queue;
tracing::debug!(
target: LOG_TARGET,
validation_code_hash = ?artifact.id.code_hash,
"enqueueing an artifact for execution",
);
queue.metrics.execute_enqueued();
let job = ExecuteJob { artifact, params, result_tx };
let job = ExecuteJob { artifact, execution_timeout, params, result_tx };
if let Some(available) = queue.workers.find_available() {
assign(queue, available, job);
@@ -326,7 +332,13 @@ fn assign(queue: &mut Queue, worker: Worker, job: ExecuteJob) {
queue.mux.push(
async move {
let _timer = execution_timer;
let outcome = super::worker::start_work(idle, job.artifact.clone(), job.params).await;
let outcome = super::worker::start_work(
idle,
job.artifact.clone(),
job.execution_timeout,
job.params,
)
.await;
QueueEvent::StartWork(worker, outcome, job.artifact.id, job.result_tx)
}
.boxed(),
+2 -3
View File
@@ -34,8 +34,6 @@ use parity_scale_codec::{Decode, Encode};
use polkadot_parachain::primitives::ValidationResult;
use std::time::{Duration, Instant};
const EXECUTION_TIMEOUT: Duration = Duration::from_secs(3);
/// Spawns a new worker with the given program path that acts as the worker and the spawn timeout.
///
/// The program should be able to handle `<program-path> execute-worker <socket-path>` invocation.
@@ -69,6 +67,7 @@ pub enum Outcome {
pub async fn start_work(
worker: IdleWorker,
artifact: ArtifactPathId,
execution_timeout: Duration,
validation_params: Vec<u8>,
) -> Outcome {
let IdleWorker { mut stream, pid } = worker;
@@ -108,7 +107,7 @@ pub async fn start_work(
Ok(response) => response,
}
},
_ = Delay::new(EXECUTION_TIMEOUT).fuse() => {
_ = Delay::new(execution_timeout).fuse() => {
tracing::warn!(
target: LOG_TARGET,
worker_pid = %pid,
+74 -26
View File
@@ -48,8 +48,8 @@ pub struct ValidationHost {
}
impl ValidationHost {
/// Execute PVF with the given code, parameters and priority. The result of execution will be sent
/// to the provided result sender.
/// Execute PVF with the given code, execution timeout, parameters and priority.
/// The result of execution will be sent to the provided result sender.
///
/// This is async to accommodate the fact a possibility of back-pressure. In the vast majority of
/// situations this function should return immediately.
@@ -58,12 +58,13 @@ impl ValidationHost {
pub async fn execute_pvf(
&mut self,
pvf: Pvf,
execution_timeout: Duration,
params: Vec<u8>,
priority: Priority,
result_tx: ResultSender,
) -> Result<(), String> {
self.to_host_tx
.send(ToHost::ExecutePvf { pvf, params, priority, result_tx })
.send(ToHost::ExecutePvf { pvf, execution_timeout, params, priority, result_tx })
.await
.map_err(|_| "the inner loop hung up".to_string())
}
@@ -83,8 +84,16 @@ impl ValidationHost {
}
enum ToHost {
ExecutePvf { pvf: Pvf, params: Vec<u8>, priority: Priority, result_tx: ResultSender },
HeadsUp { active_pvfs: Vec<Pvf> },
ExecutePvf {
pvf: Pvf,
execution_timeout: Duration,
params: Vec<u8>,
priority: Priority,
result_tx: ResultSender,
},
HeadsUp {
active_pvfs: Vec<Pvf>,
},
}
/// Configuration for the validation host.
@@ -200,6 +209,7 @@ pub fn start(config: Config, metrics: Metrics) -> (ValidationHost, impl Future<O
/// to the given result sender.
#[derive(Debug)]
struct PendingExecutionRequest {
execution_timeout: Duration,
params: Vec<u8>,
result_tx: ResultSender,
}
@@ -210,11 +220,18 @@ struct PendingExecutionRequest {
struct AwaitingPrepare(HashMap<ArtifactId, Vec<PendingExecutionRequest>>);
impl AwaitingPrepare {
fn add(&mut self, artifact_id: ArtifactId, params: Vec<u8>, result_tx: ResultSender) {
self.0
.entry(artifact_id)
.or_default()
.push(PendingExecutionRequest { params, result_tx });
fn add(
&mut self,
artifact_id: ArtifactId,
execution_timeout: Duration,
params: Vec<u8>,
result_tx: ResultSender,
) {
self.0.entry(artifact_id).or_default().push(PendingExecutionRequest {
execution_timeout,
params,
result_tx,
});
}
fn take(&mut self, artifact_id: &ArtifactId) -> Vec<PendingExecutionRequest> {
@@ -360,7 +377,7 @@ async fn handle_to_host(
to_host: ToHost,
) -> Result<(), Fatal> {
match to_host {
ToHost::ExecutePvf { pvf, params, priority, result_tx } => {
ToHost::ExecutePvf { pvf, execution_timeout, params, priority, result_tx } => {
handle_execute_pvf(
cache_path,
artifacts,
@@ -368,6 +385,7 @@ async fn handle_to_host(
execute_queue,
awaiting_prepare,
pvf,
execution_timeout,
params,
priority,
result_tx,
@@ -389,6 +407,7 @@ async fn handle_execute_pvf(
execute_queue: &mut mpsc::Sender<execute::ToQueue>,
awaiting_prepare: &mut AwaitingPrepare,
pvf: Pvf,
execution_timeout: Duration,
params: Vec<u8>,
priority: Priority,
result_tx: ResultSender,
@@ -404,6 +423,7 @@ async fn handle_execute_pvf(
execute_queue,
execute::ToQueue::Enqueue {
artifact: ArtifactPathId::new(artifact_id, cache_path),
execution_timeout,
params,
result_tx,
},
@@ -417,7 +437,7 @@ async fn handle_execute_pvf(
)
.await?;
awaiting_prepare.add(artifact_id, params, result_tx);
awaiting_prepare.add(artifact_id, execution_timeout, params, result_tx);
},
}
} else {
@@ -426,7 +446,7 @@ async fn handle_execute_pvf(
artifacts.insert_preparing(artifact_id.clone());
send_prepare(prepare_queue, prepare::ToQueue::Enqueue { priority, pvf }).await?;
awaiting_prepare.add(artifact_id, params, result_tx);
awaiting_prepare.add(artifact_id, execution_timeout, params, result_tx);
}
return Ok(())
@@ -499,7 +519,7 @@ async fn handle_prepare_done(
// It's finally time to dispatch all the execution requests that were waiting for this artifact
// to be prepared.
let pending_requests = awaiting_prepare.take(&artifact_id);
for PendingExecutionRequest { params, result_tx } in pending_requests {
for PendingExecutionRequest { execution_timeout, params, result_tx } in pending_requests {
if result_tx.is_canceled() {
// Preparation could've taken quite a bit of time and the requester may be not interested
// in execution anymore, in which case we just skip the request.
@@ -510,6 +530,7 @@ async fn handle_prepare_done(
execute_queue,
execute::ToQueue::Enqueue {
artifact: ArtifactPathId::new(artifact_id.clone(), cache_path),
execution_timeout,
params,
result_tx,
},
@@ -597,6 +618,8 @@ mod tests {
use assert_matches::assert_matches;
use futures::future::BoxFuture;
const TEST_EXECUTION_TIMEOUT: Duration = Duration::from_secs(3);
#[async_std::test]
async fn pulse_test() {
let pulse = pulse_every(Duration::from_millis(100));
@@ -840,9 +863,15 @@ mod tests {
.await;
let (result_tx, _result_rx) = oneshot::channel();
host.execute_pvf(Pvf::from_discriminator(1), vec![], Priority::Critical, result_tx)
.await
.unwrap();
host.execute_pvf(
Pvf::from_discriminator(1),
TEST_EXECUTION_TIMEOUT,
vec![],
Priority::Critical,
result_tx,
)
.await
.unwrap();
run_until(
&mut test.run,
@@ -862,13 +891,20 @@ mod tests {
let mut host = test.host_handle();
let (result_tx, result_rx_pvf_1_1) = oneshot::channel();
host.execute_pvf(Pvf::from_discriminator(1), b"pvf1".to_vec(), Priority::Normal, result_tx)
.await
.unwrap();
host.execute_pvf(
Pvf::from_discriminator(1),
TEST_EXECUTION_TIMEOUT,
b"pvf1".to_vec(),
Priority::Normal,
result_tx,
)
.await
.unwrap();
let (result_tx, result_rx_pvf_1_2) = oneshot::channel();
host.execute_pvf(
Pvf::from_discriminator(1),
TEST_EXECUTION_TIMEOUT,
b"pvf1".to_vec(),
Priority::Critical,
result_tx,
@@ -877,9 +913,15 @@ mod tests {
.unwrap();
let (result_tx, result_rx_pvf_2) = oneshot::channel();
host.execute_pvf(Pvf::from_discriminator(2), b"pvf2".to_vec(), Priority::Normal, result_tx)
.await
.unwrap();
host.execute_pvf(
Pvf::from_discriminator(2),
TEST_EXECUTION_TIMEOUT,
b"pvf2".to_vec(),
Priority::Normal,
result_tx,
)
.await
.unwrap();
assert_matches!(
test.poll_and_recv_to_prepare_queue().await,
@@ -947,9 +989,15 @@ mod tests {
let mut host = test.host_handle();
let (result_tx, result_rx) = oneshot::channel();
host.execute_pvf(Pvf::from_discriminator(1), b"pvf1".to_vec(), Priority::Normal, result_tx)
.await
.unwrap();
host.execute_pvf(
Pvf::from_discriminator(1),
TEST_EXECUTION_TIMEOUT,
b"pvf1".to_vec(),
Priority::Normal,
result_tx,
)
.await
.unwrap();
assert_matches!(
test.poll_and_recv_to_prepare_queue().await,
+3
View File
@@ -20,11 +20,13 @@ use polkadot_node_core_pvf::{
start, Config, InvalidCandidate, Metrics, Pvf, ValidationError, ValidationHost,
};
use polkadot_parachain::primitives::{BlockData, ValidationParams, ValidationResult};
use std::time::Duration;
mod adder;
mod worker_common;
const PUPPET_EXE: &str = env!("CARGO_BIN_EXE_puppet_worker");
const TEST_EXECUTION_TIMEOUT: Duration = Duration::from_secs(3);
struct TestHost {
_cache_dir: tempfile::TempDir,
@@ -64,6 +66,7 @@ impl TestHost {
.await
.execute_pvf(
Pvf::from_code(code.into()),
TEST_EXECUTION_TIMEOUT,
params.encode(),
polkadot_node_core_pvf::Priority::Normal,
result_tx,
@@ -75,6 +75,7 @@ impl Subsystem1 {
let msg = CandidateValidationMessage::ValidateFromChainState(
Default::default(),
PoV { block_data: BlockData(Vec::new()) }.into(),
Default::default(),
tx,
);
ctx.send_message(<Ctx as overseer::SubsystemContext>::AllMessages::from(msg))
+7 -1
View File
@@ -112,6 +112,7 @@ where
ctx.send_message(CandidateValidationMessage::ValidateFromChainState(
Default::default(),
PoV { block_data: BlockData(Vec::new()) }.into(),
Default::default(),
tx,
))
.await;
@@ -791,7 +792,12 @@ where
fn test_candidate_validation_msg() -> CandidateValidationMessage {
let (sender, _) = oneshot::channel();
let pov = Arc::new(PoV { block_data: BlockData(Vec::new()) });
CandidateValidationMessage::ValidateFromChainState(Default::default(), pov, sender)
CandidateValidationMessage::ValidateFromChainState(
Default::default(),
pov,
Default::default(),
sender,
)
}
fn test_candidate_backing_msg() -> CandidateBackingMessage {
+12 -1
View File
@@ -22,7 +22,7 @@
#![deny(missing_docs)]
use std::{convert::TryFrom, pin::Pin};
use std::{convert::TryFrom, pin::Pin, time::Duration};
use bounded_vec::BoundedVec;
use futures::Future;
@@ -71,6 +71,17 @@ pub const POV_BOMB_LIMIT: usize = (MAX_POV_SIZE * 4u32) as usize;
/// Number of sessions we want to consider in disputes.
pub const DISPUTE_WINDOW: SessionIndex = 6;
/// The amount of time to spend on execution during backing.
pub const BACKING_EXECUTION_TIMEOUT: Duration = Duration::from_secs(2);
/// The amount of time to spend on execution during approval or disputes.
///
/// This is deliberately much longer than the backing execution timeout to
/// ensure that in the absence of extremely large disparities between hardware,
/// blocks that pass backing are considerd executable by approval checkers or
/// dispute participants.
pub const APPROVAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(6);
/// The cumulative weight of a block in a fork-choice rule.
pub type BlockWeight = u32;
@@ -51,6 +51,7 @@ use polkadot_statement_table::v1::Misbehavior;
use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
time::Duration,
};
/// Network events as transmitted to other subsystems, wrapped in their message types.
@@ -114,6 +115,8 @@ pub enum CandidateValidationMessage {
ValidateFromChainState(
CandidateDescriptor,
Arc<PoV>,
/// Execution timeout
Duration,
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
),
/// Validate a candidate with provided, exhaustive parameters for validation.
@@ -130,6 +133,8 @@ pub enum CandidateValidationMessage {
ValidationCode,
CandidateDescriptor,
Arc<PoV>,
/// Execution timeout
Duration,
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
),
}
@@ -138,8 +143,8 @@ impl CandidateValidationMessage {
/// If the current variant contains the relay parent hash, return it.
pub fn relay_parent(&self) -> Option<Hash> {
match self {
Self::ValidateFromChainState(_, _, _) => None,
Self::ValidateFromExhaustive(_, _, _, _, _) => None,
Self::ValidateFromChainState(_, _, _, _) => None,
Self::ValidateFromExhaustive(_, _, _, _, _, _) => None,
}
}
}
@@ -281,7 +281,7 @@ On receiving an `ApprovedAncestor(Hash, BlockNumber, response_channel)`:
* Load the historical validation code of the parachain by dispatching a `RuntimeApiRequest::ValidationCodeByHash(descriptor.validation_code_hash)` against the state of `block_hash`.
* Spawn a background task with a clone of `background_tx`
* Wait for the available data
* Issue a `CandidateValidationMessage::ValidateFromExhaustive` message
* Issue a `CandidateValidationMessage::ValidateFromExhaustive` message with `APPROVAL_EXECUTION_TIMEOUT` as the timeout parameter.
* Wait for the result of validation
* Check that the result of validation, if valid, matches the commitments in the receipt.
* If valid, issue a message on `background_tx` detailing the request.
@@ -123,7 +123,7 @@ Dispatch a [`AvailabilityDistributionMessage`][ADM]`::FetchPoV{ validator_index,
### Validate PoV Block
Create a `(sender, receiver)` pair.
Dispatch a `CandidateValidationMessage::Validate(validation function, candidate, pov, sender)` and listen on the receiver for a response.
Dispatch a `CandidateValidationMessage::Validate(validation function, candidate, pov, BACKING_EXECUTION_TIMEOUT, sender)` and listen on the receiver for a response.
### Distribute Signed Statement
@@ -48,7 +48,7 @@ Conclude.
* If the data is recovered, dispatch a [`RuntimeApiMessage::ValidationCodeByHash`][RuntimeApiMessage] with the parameters `(candidate_receipt.descriptor.validation_code_hash)` at `state.recent_block.hash`.
* Dispatch a [`AvailabilityStoreMessage::StoreAvailableData`][AvailabilityStoreMessage] with the data.
* If the code is not fetched from the chain, return. This should be impossible with correct relay chain configuration, at least if chain synchronization is working correctly.
* Dispatch a [`CandidateValidationMessage::ValidateFromExhaustive`][CandidateValidationMessage] with the available data and the validation code.
* Dispatch a [`CandidateValidationMessage::ValidateFromExhaustive`][CandidateValidationMessage] with the available data and the validation code and `APPROVAL_EXECUTION_TIMEOUT` as the timeout parameter.
* If the validation result is `Invalid`, [cast invalid votes](#cast-votes) and return.
* If the validation fails, [cast invalid votes](#cast-votes) and return.
* If the validation succeeds, compute the `CandidateCommitments` based on the validation result and compare against the candidate receipt's `commitments_hash`. If they match, [cast valid votes](#cast-votes) and if not, [cast invalid votes](#cast-votes).
@@ -785,6 +785,9 @@ enum ValidationResult {
Invalid,
}
const BACKING_EXECUTION_TIMEOUT: Duration = 2 seconds;
const APPROVAL_EXECUTION_TIMEOUT: Duration = 6 seconds;
/// Messages received by the Validation subsystem.
///
/// ## Validation Requests
@@ -807,6 +810,7 @@ pub enum CandidateValidationMessage {
ValidateFromChainState(
CandidateDescriptor,
Arc<PoV>,
Duration, // Execution timeout.
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
),
/// Validate a candidate with provided, exhaustive parameters for validation.
@@ -823,6 +827,7 @@ pub enum CandidateValidationMessage {
ValidationCode,
CandidateDescriptor,
Arc<PoV>,
Duration, // Execution timeout.
oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
),
}