lib.rs 18.5 KB
Newer Older
1
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

//! The Candidate Validation subsystem.
//!
//! This handles incoming requests from other subsystems to validate candidates
//! according to a validation function. This delegates validation to an underlying
//! pool of processes used for execution of the Wasm.

23
24
25
#![deny(unused_crate_dependencies, unused_results)]
#![warn(missing_docs)]

Shawn Tabrizi's avatar
Shawn Tabrizi committed
26
27
28
29
30
31
use polkadot_node_core_pvf::{
	InvalidCandidate as WasmInvalidCandidate, Pvf, ValidationError, ValidationHost,
};
use polkadot_node_primitives::{
	BlockData, InvalidCandidate, PoV, ValidationResult, POV_BOMB_LIMIT, VALIDATION_CODE_BOMB_LIMIT,
};
32
use polkadot_node_subsystem::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
33
	errors::RuntimeApiError,
34
	messages::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
35
		CandidateValidationMessage, RuntimeApiMessage, RuntimeApiRequest, ValidationFailed,
36
	},
Shawn Tabrizi's avatar
Shawn Tabrizi committed
37
38
	overseer, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError,
	SubsystemResult,
39
};
40
use polkadot_node_subsystem_util::metrics::{self, prometheus};
Shawn Tabrizi's avatar
Shawn Tabrizi committed
41
use polkadot_parachain::primitives::{ValidationParams, ValidationResult as WasmValidationResult};
42
use polkadot_primitives::v1::{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
43
	CandidateCommitments, CandidateDescriptor, Hash, OccupiedCoreAssumption,
44
	PersistedValidationData, ValidationCode, ValidationCodeHash,
45
46
47
48
};

use parity_scale_codec::Encode;

Shawn Tabrizi's avatar
Shawn Tabrizi committed
49
use futures::{channel::oneshot, prelude::*};
50

Shawn Tabrizi's avatar
Shawn Tabrizi committed
51
use std::{path::PathBuf, sync::Arc};
52
53

use async_trait::async_trait;
54

55
56
57
#[cfg(test)]
mod tests;

58
const LOG_TARGET: &'static str = "parachain::candidate-validation";
59

60
/// Configuration for the candidate validation subsystem
61
#[derive(Clone)]
62
63
64
65
66
67
68
69
pub struct Config {
	/// The path where candidate validation can store compiled artifacts for PVFs.
	pub artifacts_cache_path: PathBuf,
	/// The path to the executable which can be used for spawning PVF compilation & validation
	/// workers.
	pub program_path: PathBuf,
}

70
/// The candidate validation subsystem.
71
pub struct CandidateValidationSubsystem {
72
	metrics: Metrics,
73
	pvf_metrics: polkadot_node_core_pvf::Metrics,
74
	config: Config,
75
}
76

77
impl CandidateValidationSubsystem {
78
79
80
81
	/// Create a new `CandidateValidationSubsystem` with the given task spawner and isolation
	/// strategy.
	///
	/// Check out [`IsolationStrategy`] to get more details.
82
83
84
85
86
87
	pub fn with_config(
		config: Config,
		metrics: Metrics,
		pvf_metrics: polkadot_node_core_pvf::Metrics,
	) -> Self {
		CandidateValidationSubsystem { config, metrics, pvf_metrics }
88
89
90
	}
}

91
92
93
94
impl<Context> overseer::Subsystem<Context, SubsystemError> for CandidateValidationSubsystem
where
	Context: SubsystemContext<Message = CandidateValidationMessage>,
	Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
95
{
96
	fn start(self, ctx: Context) -> SpawnedSubsystem {
97
98
99
100
101
102
103
104
105
		let future = run(
			ctx,
			self.metrics,
			self.pvf_metrics,
			self.config.artifacts_cache_path,
			self.config.program_path,
		)
		.map_err(|e| SubsystemError::with_origin("candidate-validation", e))
		.boxed();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
106
		SpawnedSubsystem { name: "candidate-validation-subsystem", future }
107
108
109
	}
}

110
111
async fn run<Context>(
	mut ctx: Context,
112
	metrics: Metrics,
113
	pvf_metrics: polkadot_node_core_pvf::Metrics,
114
115
	cache_path: PathBuf,
	program_path: PathBuf,
116
117
118
119
120
) -> SubsystemResult<()>
where
	Context: SubsystemContext<Message = CandidateValidationMessage>,
	Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
{
121
122
	let (mut validation_host, task) = polkadot_node_core_pvf::start(
		polkadot_node_core_pvf::Config::new(cache_path, program_path),
123
		pvf_metrics,
124
	);
125
	ctx.spawn_blocking("pvf-validation-host", task.boxed())?;
126

127
128
	loop {
		match ctx.recv().await? {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
129
130
			FromOverseer::Signal(OverseerSignal::ActiveLeaves(_)) => {},
			FromOverseer::Signal(OverseerSignal::BlockFinalized(..)) => {},
131
132
133
134
135
136
137
			FromOverseer::Signal(OverseerSignal::Conclude) => return Ok(()),
			FromOverseer::Communication { msg } => match msg {
				CandidateValidationMessage::ValidateFromChainState(
					descriptor,
					pov,
					response_sender,
				) => {
138
139
					let _timer = metrics.time_validate_from_chain_state();

140
141
					let res = spawn_validate_from_chain_state(
						&mut ctx,
142
						&mut validation_host,
143
144
						descriptor,
						pov,
145
						&metrics,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
146
147
					)
					.await;
148
149

					match res {
150
151
152
						Ok(x) => {
							metrics.on_validation_event(&x);
							let _ = response_sender.send(x);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
153
						},
154
						Err(e) => return Err(e),
155
					}
Shawn Tabrizi's avatar
Shawn Tabrizi committed
156
				},
157
				CandidateValidationMessage::ValidateFromExhaustive(
158
					persisted_validation_data,
159
160
161
162
163
					validation_code,
					descriptor,
					pov,
					response_sender,
				) => {
164
165
					let _timer = metrics.time_validate_from_exhaustive();

166
167
					let res = validate_candidate_exhaustive(
						&mut validation_host,
168
						persisted_validation_data,
169
170
171
						validation_code,
						descriptor,
						pov,
172
						&metrics,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
173
174
					)
					.await;
175
176

					match res {
177
178
						Ok(x) => {
							metrics.on_validation_event(&x);
179

180
							if let Err(_e) = response_sender.send(x) {
181
								tracing::warn!(
182
183
184
185
									target: LOG_TARGET,
									"Requester of candidate validation dropped",
								)
							}
186
						},
187
						Err(e) => return Err(e),
188
					}
Shawn Tabrizi's avatar
Shawn Tabrizi committed
189
190
				},
			},
191
192
193
194
		}
	}
}

195
196
async fn runtime_api_request<T, Context>(
	ctx: &mut Context,
197
198
199
	relay_parent: Hash,
	request: RuntimeApiRequest,
	receiver: oneshot::Receiver<Result<T, RuntimeApiError>>,
200
201
202
203
204
) -> SubsystemResult<Result<T, RuntimeApiError>>
where
	Context: SubsystemContext<Message = CandidateValidationMessage>,
	Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
205
	ctx.send_message(RuntimeApiMessage::Request(relay_parent, request)).await;
206
207
208
209
210
211

	receiver.await.map_err(Into::into)
}

#[derive(Debug)]
enum AssumptionCheckOutcome {
212
	Matches(PersistedValidationData, ValidationCode),
213
214
215
216
	DoesNotMatch,
	BadRequest,
}

217
218
async fn check_assumption_validation_data<Context>(
	ctx: &mut Context,
219
220
	descriptor: &CandidateDescriptor,
	assumption: OccupiedCoreAssumption,
221
222
223
224
225
) -> SubsystemResult<AssumptionCheckOutcome>
where
	Context: SubsystemContext<Message = CandidateValidationMessage>,
	Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
{
226
	let validation_data = {
227
228
229
230
		let (tx, rx) = oneshot::channel();
		let d = runtime_api_request(
			ctx,
			descriptor.relay_parent,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
231
			RuntimeApiRequest::PersistedValidationData(descriptor.para_id, assumption, tx),
232
			rx,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
233
234
		)
		.await?;
235
236

		match d {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
237
			Ok(None) | Err(_) => return Ok(AssumptionCheckOutcome::BadRequest),
238
239
240
241
			Ok(Some(d)) => d,
		}
	};

242
	let persisted_validation_data_hash = validation_data.hash();
243

Shawn Tabrizi's avatar
Shawn Tabrizi committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
	SubsystemResult::Ok(
		if descriptor.persisted_validation_data_hash == persisted_validation_data_hash {
			let (code_tx, code_rx) = oneshot::channel();
			let validation_code = runtime_api_request(
				ctx,
				descriptor.relay_parent,
				RuntimeApiRequest::ValidationCode(descriptor.para_id, assumption, code_tx),
				code_rx,
			)
			.await?;

			match validation_code {
				Ok(None) | Err(_) => AssumptionCheckOutcome::BadRequest,
				Ok(Some(v)) => AssumptionCheckOutcome::Matches(validation_data, v),
			}
		} else {
			AssumptionCheckOutcome::DoesNotMatch
		},
	)
263
264
}

265
266
async fn find_assumed_validation_data<Context>(
	ctx: &mut Context,
267
	descriptor: &CandidateDescriptor,
268
269
270
271
272
) -> SubsystemResult<AssumptionCheckOutcome>
where
	Context: SubsystemContext<Message = CandidateValidationMessage>,
	Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
{
273
	// The candidate descriptor has a `persisted_validation_data_hash` which corresponds to
274
	// one of up to two possible values that we can derive from the state of the
275
276
	// relay-parent. We can fetch these values by getting the persisted validation data
	// based on the different `OccupiedCoreAssumption`s.
277
278

	const ASSUMPTIONS: &[OccupiedCoreAssumption] = &[
279
		OccupiedCoreAssumption::Included,
280
		OccupiedCoreAssumption::TimedOut,
Sergey Pepyakin's avatar
Sergey Pepyakin committed
281
282
283
		// `TimedOut` and `Free` both don't perform any speculation and therefore should be the same
		// for our purposes here. In other words, if `TimedOut` matched then the `Free` must be
		// matched as well.
284
285
286
287
288
289
	];

	// Consider running these checks in parallel to reduce validation latency.
	for assumption in ASSUMPTIONS {
		let outcome = check_assumption_validation_data(ctx, descriptor, *assumption).await?;

290
		match outcome {
291
292
293
			AssumptionCheckOutcome::Matches(_, _) => return Ok(outcome),
			AssumptionCheckOutcome::BadRequest => return Ok(outcome),
			AssumptionCheckOutcome::DoesNotMatch => continue,
294
		}
295
296
	}

297
298
299
	Ok(AssumptionCheckOutcome::DoesNotMatch)
}

300
301
async fn spawn_validate_from_chain_state<Context>(
	ctx: &mut Context,
302
	validation_host: &mut ValidationHost,
303
304
	descriptor: CandidateDescriptor,
	pov: Arc<PoV>,
305
	metrics: &Metrics,
306
307
308
309
310
) -> SubsystemResult<Result<ValidationResult, ValidationFailed>>
where
	Context: SubsystemContext<Message = CandidateValidationMessage>,
	Context: overseer::SubsystemContext<Message = CandidateValidationMessage>,
{
311
312
	let (validation_data, validation_code) =
		match find_assumed_validation_data(ctx, &descriptor).await? {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
313
314
			AssumptionCheckOutcome::Matches(validation_data, validation_code) =>
				(validation_data, validation_code),
315
316
317
318
			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.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
319
320
321
322
				return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::BadParent)))
			},
			AssumptionCheckOutcome::BadRequest =>
				return Ok(Err(ValidationFailed("Assumption Check: Bad request".into()))),
323
324
		};

325
326
	let validation_result = validate_candidate_exhaustive(
		validation_host,
327
328
329
330
		validation_data,
		validation_code,
		descriptor.clone(),
		pov,
331
		metrics,
332
333
334
335
336
337
338
339
340
341
342
343
344
	)
	.await;

	if let Ok(Ok(ValidationResult::Valid(ref outputs, _))) = validation_result {
		let (tx, rx) = oneshot::channel();
		match runtime_api_request(
			ctx,
			descriptor.relay_parent,
			RuntimeApiRequest::CheckValidationOutputs(descriptor.para_id, outputs.clone(), tx),
			rx,
		)
		.await?
		{
Shawn Tabrizi's avatar
Shawn Tabrizi committed
345
346
347
348
			Ok(true) => {},
			Ok(false) => return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::InvalidOutputs))),
			Err(_) =>
				return Ok(Err(ValidationFailed("Check Validation Outputs: Bad request".into()))),
349
350
351
		}
	}

352
	validation_result
353
354
}

355
356
async fn validate_candidate_exhaustive(
	mut validation_backend: impl ValidationBackend,
357
	persisted_validation_data: PersistedValidationData,
358
359
360
	validation_code: ValidationCode,
	descriptor: CandidateDescriptor,
	pov: Arc<PoV>,
361
	metrics: &Metrics,
362
) -> SubsystemResult<Result<ValidationResult, ValidationFailed>> {
363
364
	let _timer = metrics.time_validate_candidate_exhaustive();

365
366
367
368
369
370
371
372
	let validation_code_hash = validation_code.hash();
	tracing::debug!(
		target: LOG_TARGET,
		?validation_code_hash,
		para_id = ?descriptor.para_id,
		"About to validate a candidate.",
	);

373
374
375
376
	if let Err(e) = perform_basic_checks(
		&descriptor,
		persisted_validation_data.max_pov_size,
		&*pov,
377
		&validation_code_hash,
378
	) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
379
		return Ok(Ok(ValidationResult::Invalid(e)))
380
381
	}

382
383
384
385
386
387
388
389
390
	let raw_validation_code = match sp_maybe_compressed_blob::decompress(
		&validation_code.0,
		VALIDATION_CODE_BOMB_LIMIT,
	) {
		Ok(code) => code,
		Err(e) => {
			tracing::debug!(target: LOG_TARGET, err=?e, "Invalid validation code");

			// If the validation code is invalid, the candidate certainly is.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
391
392
			return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::CodeDecompressionFailure)))
		},
393
394
	};

Shawn Tabrizi's avatar
Shawn Tabrizi committed
395
396
397
398
399
	let raw_block_data =
		match sp_maybe_compressed_blob::decompress(&pov.block_data.0, POV_BOMB_LIMIT) {
			Ok(block_data) => BlockData(block_data.to_vec()),
			Err(e) => {
				tracing::debug!(target: LOG_TARGET, err=?e, "Invalid PoV code");
400

Shawn Tabrizi's avatar
Shawn Tabrizi committed
401
402
403
404
				// If the PoV is invalid, the candidate certainly is.
				return Ok(Ok(ValidationResult::Invalid(InvalidCandidate::PoVDecompressionFailure)))
			},
		};
405

406
	let params = ValidationParams {
407
		parent_head: persisted_validation_data.parent_head.clone(),
408
		block_data: raw_block_data,
409
410
		relay_parent_number: persisted_validation_data.relay_parent_number,
		relay_parent_storage_root: persisted_validation_data.relay_parent_storage_root,
411
412
	};

Shawn Tabrizi's avatar
Shawn Tabrizi committed
413
414
	let result = validation_backend
		.validate_candidate(raw_validation_code.to_vec(), params)
415
416
		.await;

417
418
419
420
421
422
423
424
	if let Err(ref e) = result {
		tracing::debug!(
			target: LOG_TARGET,
			error = ?e,
			"Failed to validate candidate",
		);
	}

425
426
427
428
	let result = match result {
		Err(ValidationError::InternalError(e)) => Err(ValidationFailed(e)),

		Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::HardTimeout)) =>
429
			Ok(ValidationResult::Invalid(InvalidCandidate::Timeout)),
430
431
432
		Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::WorkerReportedError(e))) =>
			Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(e))),
		Err(ValidationError::InvalidCandidate(WasmInvalidCandidate::AmbigiousWorkerDeath)) =>
Shawn Tabrizi's avatar
Shawn Tabrizi committed
433
434
435
			Ok(ValidationResult::Invalid(InvalidCandidate::ExecutionError(
				"ambigious worker death".to_string(),
			))),
436

Shawn Tabrizi's avatar
Shawn Tabrizi committed
437
		Ok(res) =>
438
			if res.head_data.hash() != descriptor.para_head {
439
440
441
442
443
444
445
446
447
448
449
				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,
				};
				Ok(ValidationResult::Valid(outputs, persisted_validation_data))
Shawn Tabrizi's avatar
Shawn Tabrizi committed
450
			},
451
452
453
454
455
456
457
458
459
460
	};

	Ok(result)
}

#[async_trait]
trait ValidationBackend {
	async fn validate_candidate(
		&mut self,
		raw_validation_code: Vec<u8>,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
461
		params: ValidationParams,
462
463
	) -> Result<WasmValidationResult, ValidationError>;
}
464

465
466
467
468
469
#[async_trait]
impl ValidationBackend for &'_ mut ValidationHost {
	async fn validate_candidate(
		&mut self,
		raw_validation_code: Vec<u8>,
Shawn Tabrizi's avatar
Shawn Tabrizi committed
470
		params: ValidationParams,
471
472
	) -> Result<WasmValidationResult, ValidationError> {
		let (tx, rx) = oneshot::channel();
Shawn Tabrizi's avatar
Shawn Tabrizi committed
473
474
475
476
477
478
479
480
481
482
483
484
485
		if let Err(err) = self
			.execute_pvf(
				Pvf::from_code(raw_validation_code),
				params.encode(),
				polkadot_node_core_pvf::Priority::Normal,
				tx,
			)
			.await
		{
			return Err(ValidationError::InternalError(format!(
				"cannot send pvf to the validation host: {:?}",
				err
			)))
486
		}
487
488
489
490
491
492

		let validation_result = rx
			.await
			.map_err(|_| ValidationError::InternalError("validation was cancelled".into()))?;

		validation_result
493
494
495
	}
}

496
497
498
499
500
501
/// Does basic checks of a candidate. Provide the encoded PoV-block. Returns `Ok` if basic checks
/// are passed, `Err` otherwise.
fn perform_basic_checks(
	candidate: &CandidateDescriptor,
	max_pov_size: u32,
	pov: &PoV,
502
	validation_code_hash: &ValidationCodeHash,
503
504
505
506
507
) -> Result<(), InvalidCandidate> {
	let pov_hash = pov.hash();

	let encoded_pov_size = pov.encoded_size();
	if encoded_pov_size > max_pov_size as usize {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
508
		return Err(InvalidCandidate::ParamsTooLarge(encoded_pov_size as u64))
509
510
511
	}

	if pov_hash != candidate.pov_hash {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
512
		return Err(InvalidCandidate::PoVHashMismatch)
513
514
	}

515
	if *validation_code_hash != candidate.validation_code_hash {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
516
		return Err(InvalidCandidate::CodeHashMismatch)
517
518
519
	}

	if let Err(()) = candidate.check_collator_signature() {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
520
		return Err(InvalidCandidate::BadSignature)
521
522
523
524
525
	}

	Ok(())
}

526
527
528
#[derive(Clone)]
struct MetricsInner {
	validation_requests: prometheus::CounterVec<prometheus::U64>,
529
530
531
	validate_from_chain_state: prometheus::Histogram,
	validate_from_exhaustive: prometheus::Histogram,
	validate_candidate_exhaustive: prometheus::Histogram,
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
}

/// Candidate validation metrics.
#[derive(Default, Clone)]
pub struct Metrics(Option<MetricsInner>);

impl Metrics {
	fn on_validation_event(&self, event: &Result<ValidationResult, ValidationFailed>) {
		if let Some(metrics) = &self.0 {
			match event {
				Ok(ValidationResult::Valid(_, _)) => {
					metrics.validation_requests.with_label_values(&["valid"]).inc();
				},
				Ok(ValidationResult::Invalid(_)) => {
					metrics.validation_requests.with_label_values(&["invalid"]).inc();
				},
				Err(_) => {
					metrics.validation_requests.with_label_values(&["validation failure"]).inc();
				},
			}
		}
	}
554
555

	/// Provide a timer for `validate_from_chain_state` which observes on drop.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
556
557
558
	fn time_validate_from_chain_state(
		&self,
	) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
559
560
561
562
		self.0.as_ref().map(|metrics| metrics.validate_from_chain_state.start_timer())
	}

	/// Provide a timer for `validate_from_exhaustive` which observes on drop.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
563
564
565
	fn time_validate_from_exhaustive(
		&self,
	) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
566
567
568
569
		self.0.as_ref().map(|metrics| metrics.validate_from_exhaustive.start_timer())
	}

	/// Provide a timer for `validate_candidate_exhaustive` which observes on drop.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
570
571
572
573
574
575
	fn time_validate_candidate_exhaustive(
		&self,
	) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
		self.0
			.as_ref()
			.map(|metrics| metrics.validate_candidate_exhaustive.start_timer())
576
	}
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
}

impl metrics::Metrics for Metrics {
	fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError> {
		let metrics = MetricsInner {
			validation_requests: prometheus::register(
				prometheus::CounterVec::new(
					prometheus::Opts::new(
						"parachain_validation_requests_total",
						"Number of validation requests served.",
					),
					&["validity"],
				)?,
				registry,
			)?,
592
			validate_from_chain_state: prometheus::register(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
593
594
595
596
				prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
					"parachain_candidate_validation_validate_from_chain_state",
					"Time spent within `candidate_validation::validate_from_chain_state`",
				))?,
597
598
599
				registry,
			)?,
			validate_from_exhaustive: prometheus::register(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
600
601
602
603
				prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
					"parachain_candidate_validation_validate_from_exhaustive",
					"Time spent within `candidate_validation::validate_from_exhaustive`",
				))?,
604
605
606
				registry,
			)?,
			validate_candidate_exhaustive: prometheus::register(
Shawn Tabrizi's avatar
Shawn Tabrizi committed
607
608
609
610
				prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
					"parachain_candidate_validation_validate_candidate_exhaustive",
					"Time spent within `candidate_validation::validate_candidate_exhaustive`",
				))?,
611
612
				registry,
			)?,
613
614
615
616
		};
		Ok(Metrics(Some(metrics)))
	}
}