1use crate::{
20 interceptor::*,
21 shared::{MALICIOUS_POV, MALUS},
22};
23
24use polkadot_node_primitives::{InvalidCandidate, ValidationResult};
25
26use polkadot_primitives::{
27 CandidateCommitments, CandidateDescriptorV2 as CandidateDescriptor,
28 CandidateReceiptV2 as CandidateReceipt, PersistedValidationData, PvfExecKind,
29};
30
31use futures::channel::oneshot;
32use rand::distributions::{Bernoulli, Distribution};
33
34#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)]
35#[value(rename_all = "kebab-case")]
36#[non_exhaustive]
37pub enum FakeCandidateValidation {
38 Disabled,
39 BackingInvalid,
40 ApprovalInvalid,
41 BackingAndApprovalInvalid,
42 BackingValid,
43 ApprovalValid,
44 BackingAndApprovalValid,
45}
46
47impl FakeCandidateValidation {
48 fn misbehaves_valid(&self) -> bool {
49 use FakeCandidateValidation::*;
50
51 match *self {
52 BackingValid | ApprovalValid | BackingAndApprovalValid => true,
53 _ => false,
54 }
55 }
56
57 fn misbehaves_invalid(&self) -> bool {
58 use FakeCandidateValidation::*;
59
60 match *self {
61 BackingInvalid | ApprovalInvalid | BackingAndApprovalInvalid => true,
62 _ => false,
63 }
64 }
65
66 fn includes_backing(&self) -> bool {
67 use FakeCandidateValidation::*;
68
69 match *self {
70 BackingInvalid | BackingAndApprovalInvalid | BackingValid | BackingAndApprovalValid =>
71 true,
72 _ => false,
73 }
74 }
75
76 fn includes_approval(&self) -> bool {
77 use FakeCandidateValidation::*;
78
79 match *self {
80 ApprovalInvalid |
81 BackingAndApprovalInvalid |
82 ApprovalValid |
83 BackingAndApprovalValid => true,
84 _ => false,
85 }
86 }
87
88 fn should_misbehave(&self, timeout: PvfExecKind) -> bool {
89 match timeout {
90 PvfExecKind::Backing => self.includes_backing(),
91 PvfExecKind::Approval => self.includes_approval(),
92 }
93 }
94}
95
96#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)]
98#[value(rename_all = "kebab-case")]
99pub enum FakeCandidateValidationError {
100 InvalidOutputs,
102 ExecutionError,
104 Timeout,
106 ParamsTooLarge,
108 CodeTooLarge,
110 POVDecompressionFailure,
112 BadReturn,
114 BadParent,
116 POVHashMismatch,
118 BadSignature,
120 ParaHeadHashMismatch,
122 CodeHashMismatch,
124}
125
126impl Into<InvalidCandidate> for FakeCandidateValidationError {
127 fn into(self) -> InvalidCandidate {
128 match self {
129 FakeCandidateValidationError::ExecutionError =>
130 InvalidCandidate::ExecutionError("Malus".into()),
131 FakeCandidateValidationError::InvalidOutputs => InvalidCandidate::InvalidOutputs,
132 FakeCandidateValidationError::Timeout => InvalidCandidate::Timeout,
133 FakeCandidateValidationError::ParamsTooLarge => InvalidCandidate::ParamsTooLarge(666),
134 FakeCandidateValidationError::CodeTooLarge => InvalidCandidate::CodeTooLarge(666),
135 FakeCandidateValidationError::POVDecompressionFailure =>
136 InvalidCandidate::PoVDecompressionFailure,
137 FakeCandidateValidationError::BadReturn => InvalidCandidate::BadReturn,
138 FakeCandidateValidationError::BadParent => InvalidCandidate::BadParent,
139 FakeCandidateValidationError::POVHashMismatch => InvalidCandidate::PoVHashMismatch,
140 FakeCandidateValidationError::BadSignature => InvalidCandidate::BadSignature,
141 FakeCandidateValidationError::ParaHeadHashMismatch =>
142 InvalidCandidate::ParaHeadHashMismatch,
143 FakeCandidateValidationError::CodeHashMismatch => InvalidCandidate::CodeHashMismatch,
144 }
145 }
146}
147
148#[derive(Clone, Debug)]
149pub struct ReplaceValidationResult {
152 fake_validation: FakeCandidateValidation,
153 fake_validation_error: FakeCandidateValidationError,
154 distribution: Bernoulli,
155}
156
157impl ReplaceValidationResult {
158 pub fn new(
159 fake_validation: FakeCandidateValidation,
160 fake_validation_error: FakeCandidateValidationError,
161 percentage: f64,
162 ) -> Self {
163 let distribution = Bernoulli::new(percentage / 100.0)
164 .expect("Invalid probability! Percentage must be in range [0..=100].");
165 Self { fake_validation, fake_validation_error, distribution }
166 }
167}
168
169pub fn create_fake_candidate_commitments(
170 persisted_validation_data: &PersistedValidationData,
171) -> CandidateCommitments {
172 let mut head_data = persisted_validation_data.parent_head.clone();
175 if head_data.0.is_empty() {
176 head_data.0.push(0);
177 } else {
178 head_data.0[0] = head_data.0[0].wrapping_add(1);
179 };
180
181 CandidateCommitments {
182 upward_messages: Default::default(),
183 horizontal_messages: Default::default(),
184 new_validation_code: None,
185 head_data,
186 processed_downward_messages: 0,
187 hrmp_watermark: persisted_validation_data.relay_parent_number,
188 }
189}
190
191fn create_validation_response(
193 persisted_validation_data: PersistedValidationData,
194 descriptor: CandidateDescriptor,
195 response_sender: oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
196) {
197 let commitments = create_fake_candidate_commitments(&persisted_validation_data);
198
199 let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() };
201
202 let result = Ok(ValidationResult::Valid(commitments, persisted_validation_data));
203
204 gum::debug!(
205 target: MALUS,
206 para_id = ?candidate_receipt.descriptor.para_id(),
207 candidate_hash = ?candidate_receipt.hash(),
208 "ValidationResult: {:?}",
209 &result
210 );
211
212 response_sender.send(result).unwrap();
213}
214
215impl<Sender> MessageInterceptor<Sender> for ReplaceValidationResult
216where
217 Sender: overseer::CandidateValidationSenderTrait + Clone + Send + 'static,
218{
219 type Message = CandidateValidationMessage;
220
221 fn intercept_incoming(
224 &self,
225 _subsystem_sender: &mut Sender,
226 msg: FromOrchestra<Self::Message>,
227 ) -> Option<FromOrchestra<Self::Message>> {
228 match msg {
229 FromOrchestra::Communication {
231 msg:
232 CandidateValidationMessage::ValidateFromExhaustive {
233 validation_data,
234 validation_code,
235 candidate_receipt,
236 pov,
237 executor_params,
238 exec_kind,
239 response_sender,
240 ..
241 },
242 } => {
243 match self.fake_validation {
244 x if x.misbehaves_valid() && x.should_misbehave(exec_kind.into()) => {
245 if pov.block_data.0.as_slice() != MALICIOUS_POV {
247 return Some(FromOrchestra::Communication {
248 msg: CandidateValidationMessage::ValidateFromExhaustive {
249 validation_data,
250 validation_code,
251 candidate_receipt,
252 pov,
253 executor_params,
254 exec_kind,
255 response_sender,
256 },
257 })
258 }
259 let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
262 match behave_maliciously {
263 true => {
264 gum::info!(
265 target: MALUS,
266 ?behave_maliciously,
267 "๐ Creating malicious ValidationResult::Valid message with fake candidate commitments.",
268 );
269
270 create_validation_response(
271 validation_data,
272 candidate_receipt.descriptor,
273 response_sender,
274 );
275 None
276 },
277 false => {
278 gum::info!(
280 target: MALUS,
281 ?behave_maliciously,
282 "๐ Passing CandidateValidationMessage::ValidateFromExhaustive to the candidate validation subsystem.",
283 );
284
285 Some(FromOrchestra::Communication {
286 msg: CandidateValidationMessage::ValidateFromExhaustive {
287 validation_data,
288 validation_code,
289 candidate_receipt,
290 pov,
291 executor_params,
292 exec_kind,
293 response_sender,
294 },
295 })
296 },
297 }
298 },
299 x if x.misbehaves_invalid() && x.should_misbehave(exec_kind.into()) => {
300 let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
303 match behave_maliciously {
304 true => {
305 let validation_result =
306 ValidationResult::Invalid(self.fake_validation_error.into());
307
308 gum::info!(
309 target: MALUS,
310 ?behave_maliciously,
311 para_id = ?candidate_receipt.descriptor.para_id(),
312 "๐ Maliciously sending invalid validation result: {:?}.",
313 &validation_result,
314 );
315
316 response_sender.send(Ok(validation_result)).unwrap();
319 None
320 },
321 false => {
322 gum::info!(target: MALUS, "๐ 'Decided' to not act maliciously.",);
324
325 Some(FromOrchestra::Communication {
326 msg: CandidateValidationMessage::ValidateFromExhaustive {
327 validation_data,
328 validation_code,
329 candidate_receipt,
330 pov,
331 executor_params,
332 exec_kind,
333 response_sender,
334 },
335 })
336 },
337 }
338 },
339 _ => Some(FromOrchestra::Communication {
341 msg: CandidateValidationMessage::ValidateFromExhaustive {
342 validation_data,
343 validation_code,
344 candidate_receipt,
345 pov,
346 executor_params,
347 exec_kind,
348 response_sender,
349 },
350 }),
351 }
352 },
353 msg => Some(msg),
354 }
355 }
356}