|
24 | 24 | #![warn(missing_docs)] |
25 | 25 |
|
26 | 26 | use polkadot_node_core_pvf::{ |
27 | | - InvalidCandidate as WasmInvalidCandidate, Pvf, ValidationError, ValidationHost, |
| 27 | + InvalidCandidate as WasmInvalidCandidate, PrepareError, Pvf, ValidationError, ValidationHost, |
28 | 28 | }; |
29 | 29 | use polkadot_node_primitives::{ |
30 | 30 | BlockData, InvalidCandidate, PoV, ValidationResult, POV_BOMB_LIMIT, VALIDATION_CODE_BOMB_LIMIT, |
31 | 31 | }; |
32 | 32 | use polkadot_node_subsystem::{ |
33 | 33 | errors::RuntimeApiError, |
34 | 34 | messages::{ |
35 | | - CandidateValidationMessage, RuntimeApiMessage, RuntimeApiRequest, ValidationFailed, |
| 35 | + CandidateValidationMessage, PreCheckOutcome, RuntimeApiMessage, RuntimeApiRequest, |
| 36 | + ValidationFailed, |
36 | 37 | }, |
37 | 38 | overseer, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError, |
38 | 39 | SubsystemResult, SubsystemSender, |
@@ -194,6 +195,30 @@ where |
194 | 195 |
|
195 | 196 | ctx.spawn("validate-from-exhaustive", bg.boxed())?; |
196 | 197 | }, |
| 198 | + CandidateValidationMessage::PreCheck( |
| 199 | + relay_parent, |
| 200 | + validation_code_hash, |
| 201 | + response_sender, |
| 202 | + ) => { |
| 203 | + let bg = { |
| 204 | + let mut sender = ctx.sender().clone(); |
| 205 | + let validation_host = validation_host.clone(); |
| 206 | + |
| 207 | + async move { |
| 208 | + let precheck_result = precheck_pvf( |
| 209 | + &mut sender, |
| 210 | + validation_host, |
| 211 | + relay_parent, |
| 212 | + validation_code_hash, |
| 213 | + ) |
| 214 | + .await; |
| 215 | + |
| 216 | + let _ = response_sender.send(precheck_result); |
| 217 | + } |
| 218 | + }; |
| 219 | + |
| 220 | + ctx.spawn("candidate-validation-pre-check", bg.boxed())?; |
| 221 | + }, |
197 | 222 | }, |
198 | 223 | } |
199 | 224 | } |
@@ -235,6 +260,73 @@ where |
235 | 260 | }) |
236 | 261 | } |
237 | 262 |
|
| 263 | +async fn request_validation_code_by_hash<Sender>( |
| 264 | + sender: &mut Sender, |
| 265 | + relay_parent: Hash, |
| 266 | + validation_code_hash: ValidationCodeHash, |
| 267 | +) -> Result<Option<ValidationCode>, RuntimeRequestFailed> |
| 268 | +where |
| 269 | + Sender: SubsystemSender, |
| 270 | +{ |
| 271 | + let (tx, rx) = oneshot::channel(); |
| 272 | + runtime_api_request( |
| 273 | + sender, |
| 274 | + relay_parent, |
| 275 | + RuntimeApiRequest::ValidationCodeByHash(validation_code_hash, tx), |
| 276 | + rx, |
| 277 | + ) |
| 278 | + .await |
| 279 | +} |
| 280 | + |
| 281 | +async fn precheck_pvf<Sender>( |
| 282 | + sender: &mut Sender, |
| 283 | + mut validation_backend: impl ValidationBackend, |
| 284 | + relay_parent: Hash, |
| 285 | + validation_code_hash: ValidationCodeHash, |
| 286 | +) -> PreCheckOutcome |
| 287 | +where |
| 288 | + Sender: SubsystemSender, |
| 289 | +{ |
| 290 | + let validation_code = |
| 291 | + match request_validation_code_by_hash(sender, relay_parent, validation_code_hash).await { |
| 292 | + Ok(Some(code)) => code, |
| 293 | + _ => { |
| 294 | + // The reasoning why this is "failed" and not invalid is because we assume that |
| 295 | + // during pre-checking voting the relay-chain will pin the code. In case the code |
| 296 | + // actually is not there, we issue failed since this looks more like a bug. This |
| 297 | + // leads to us abstaining. |
| 298 | + tracing::warn!( |
| 299 | + target: LOG_TARGET, |
| 300 | + ?relay_parent, |
| 301 | + ?validation_code_hash, |
| 302 | + "precheck: requested validation code is not found on-chain!", |
| 303 | + ); |
| 304 | + return PreCheckOutcome::Failed |
| 305 | + }, |
| 306 | + }; |
| 307 | + |
| 308 | + let validation_code = match sp_maybe_compressed_blob::decompress( |
| 309 | + &validation_code.0, |
| 310 | + VALIDATION_CODE_BOMB_LIMIT, |
| 311 | + ) { |
| 312 | + Ok(code) => Pvf::from_code(code.into_owned()), |
| 313 | + Err(e) => { |
| 314 | + tracing::debug!(target: LOG_TARGET, err=?e, "precheck: cannot decompress validation code"); |
| 315 | + return PreCheckOutcome::Invalid |
| 316 | + }, |
| 317 | + }; |
| 318 | + |
| 319 | + match validation_backend.precheck_pvf(validation_code).await { |
| 320 | + Ok(_) => PreCheckOutcome::Valid, |
| 321 | + Err(prepare_err) => match prepare_err { |
| 322 | + PrepareError::Prevalidation(_) | |
| 323 | + PrepareError::Preparation(_) | |
| 324 | + PrepareError::Panic(_) => PreCheckOutcome::Invalid, |
| 325 | + PrepareError::TimedOut | PrepareError::DidNotMakeIt => PreCheckOutcome::Failed, |
| 326 | + }, |
| 327 | + } |
| 328 | +} |
| 329 | + |
238 | 330 | #[derive(Debug)] |
239 | 331 | enum AssumptionCheckOutcome { |
240 | 332 | Matches(PersistedValidationData, ValidationCode), |
@@ -487,6 +579,8 @@ trait ValidationBackend { |
487 | 579 | timeout: Duration, |
488 | 580 | params: ValidationParams, |
489 | 581 | ) -> Result<WasmValidationResult, ValidationError>; |
| 582 | + |
| 583 | + async fn precheck_pvf(&mut self, pvf: Pvf) -> Result<(), PrepareError>; |
490 | 584 | } |
491 | 585 |
|
492 | 586 | #[async_trait] |
@@ -520,6 +614,17 @@ impl ValidationBackend for ValidationHost { |
520 | 614 |
|
521 | 615 | validation_result |
522 | 616 | } |
| 617 | + |
| 618 | + async fn precheck_pvf(&mut self, pvf: Pvf) -> Result<(), PrepareError> { |
| 619 | + let (tx, rx) = oneshot::channel(); |
| 620 | + if let Err(_) = self.precheck_pvf(pvf, tx).await { |
| 621 | + return Err(PrepareError::DidNotMakeIt) |
| 622 | + } |
| 623 | + |
| 624 | + let precheck_result = rx.await.or(Err(PrepareError::DidNotMakeIt))?; |
| 625 | + |
| 626 | + precheck_result |
| 627 | + } |
523 | 628 | } |
524 | 629 |
|
525 | 630 | /// Does basic checks of a candidate. Provide the encoded PoV-block. Returns `Ok` if basic checks |
|
0 commit comments