use crate::{
interceptor::*,
shared::{MALICIOUS_POV, MALUS},
};
use polkadot_node_primitives::{InvalidCandidate, ValidationResult};
use polkadot_primitives::{
vstaging::{
CandidateDescriptorV2 as CandidateDescriptor, CandidateReceiptV2 as CandidateReceipt,
},
CandidateCommitments, PersistedValidationData, PvfExecKind,
};
use futures::channel::oneshot;
use rand::distributions::{Bernoulli, Distribution};
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)]
#[value(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum FakeCandidateValidation {
Disabled,
BackingInvalid,
ApprovalInvalid,
BackingAndApprovalInvalid,
BackingValid,
ApprovalValid,
BackingAndApprovalValid,
}
impl FakeCandidateValidation {
fn misbehaves_valid(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
BackingValid | ApprovalValid | BackingAndApprovalValid => true,
_ => false,
}
}
fn misbehaves_invalid(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
BackingInvalid | ApprovalInvalid | BackingAndApprovalInvalid => true,
_ => false,
}
}
fn includes_backing(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
BackingInvalid | BackingAndApprovalInvalid | BackingValid | BackingAndApprovalValid =>
true,
_ => false,
}
}
fn includes_approval(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
ApprovalInvalid |
BackingAndApprovalInvalid |
ApprovalValid |
BackingAndApprovalValid => true,
_ => false,
}
}
fn should_misbehave(&self, timeout: PvfExecKind) -> bool {
match timeout {
PvfExecKind::Backing => self.includes_backing(),
PvfExecKind::Approval => self.includes_approval(),
}
}
}
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)]
#[value(rename_all = "kebab-case")]
pub enum FakeCandidateValidationError {
InvalidOutputs,
ExecutionError,
Timeout,
ParamsTooLarge,
CodeTooLarge,
POVDecompressionFailure,
BadReturn,
BadParent,
POVHashMismatch,
BadSignature,
ParaHeadHashMismatch,
CodeHashMismatch,
}
impl Into<InvalidCandidate> for FakeCandidateValidationError {
fn into(self) -> InvalidCandidate {
match self {
FakeCandidateValidationError::ExecutionError =>
InvalidCandidate::ExecutionError("Malus".into()),
FakeCandidateValidationError::InvalidOutputs => InvalidCandidate::InvalidOutputs,
FakeCandidateValidationError::Timeout => InvalidCandidate::Timeout,
FakeCandidateValidationError::ParamsTooLarge => InvalidCandidate::ParamsTooLarge(666),
FakeCandidateValidationError::CodeTooLarge => InvalidCandidate::CodeTooLarge(666),
FakeCandidateValidationError::POVDecompressionFailure =>
InvalidCandidate::PoVDecompressionFailure,
FakeCandidateValidationError::BadReturn => InvalidCandidate::BadReturn,
FakeCandidateValidationError::BadParent => InvalidCandidate::BadParent,
FakeCandidateValidationError::POVHashMismatch => InvalidCandidate::PoVHashMismatch,
FakeCandidateValidationError::BadSignature => InvalidCandidate::BadSignature,
FakeCandidateValidationError::ParaHeadHashMismatch =>
InvalidCandidate::ParaHeadHashMismatch,
FakeCandidateValidationError::CodeHashMismatch => InvalidCandidate::CodeHashMismatch,
}
}
}
#[derive(Clone, Debug)]
pub struct ReplaceValidationResult {
fake_validation: FakeCandidateValidation,
fake_validation_error: FakeCandidateValidationError,
distribution: Bernoulli,
}
impl ReplaceValidationResult {
pub fn new(
fake_validation: FakeCandidateValidation,
fake_validation_error: FakeCandidateValidationError,
percentage: f64,
) -> Self {
let distribution = Bernoulli::new(percentage / 100.0)
.expect("Invalid probability! Percentage must be in range [0..=100].");
Self { fake_validation, fake_validation_error, distribution }
}
}
pub fn create_fake_candidate_commitments(
persisted_validation_data: &PersistedValidationData,
) -> CandidateCommitments {
let mut head_data = persisted_validation_data.parent_head.clone();
if head_data.0.is_empty() {
head_data.0.push(0);
} else {
head_data.0[0] = head_data.0[0].wrapping_add(1);
};
CandidateCommitments {
upward_messages: Default::default(),
horizontal_messages: Default::default(),
new_validation_code: None,
head_data,
processed_downward_messages: 0,
hrmp_watermark: persisted_validation_data.relay_parent_number,
}
}
fn create_validation_response(
persisted_validation_data: PersistedValidationData,
descriptor: CandidateDescriptor,
response_sender: oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
) {
let commitments = create_fake_candidate_commitments(&persisted_validation_data);
let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() };
let result = Ok(ValidationResult::Valid(commitments, persisted_validation_data));
gum::debug!(
target: MALUS,
para_id = ?candidate_receipt.descriptor.para_id(),
candidate_hash = ?candidate_receipt.hash(),
"ValidationResult: {:?}",
&result
);
response_sender.send(result).unwrap();
}
impl<Sender> MessageInterceptor<Sender> for ReplaceValidationResult
where
Sender: overseer::CandidateValidationSenderTrait + Clone + Send + 'static,
{
type Message = CandidateValidationMessage;
fn intercept_incoming(
&self,
_subsystem_sender: &mut Sender,
msg: FromOrchestra<Self::Message>,
) -> Option<FromOrchestra<Self::Message>> {
match msg {
FromOrchestra::Communication {
msg:
CandidateValidationMessage::ValidateFromExhaustive {
validation_data,
validation_code,
candidate_receipt,
pov,
executor_params,
exec_kind,
response_sender,
..
},
} => {
match self.fake_validation {
x if x.misbehaves_valid() && x.should_misbehave(exec_kind.into()) => {
if pov.block_data.0.as_slice() != MALICIOUS_POV {
return Some(FromOrchestra::Communication {
msg: CandidateValidationMessage::ValidateFromExhaustive {
validation_data,
validation_code,
candidate_receipt,
pov,
executor_params,
exec_kind,
response_sender,
},
})
}
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
match behave_maliciously {
true => {
gum::info!(
target: MALUS,
?behave_maliciously,
"๐ Creating malicious ValidationResult::Valid message with fake candidate commitments.",
);
create_validation_response(
validation_data,
candidate_receipt.descriptor,
response_sender,
);
None
},
false => {
gum::info!(
target: MALUS,
?behave_maliciously,
"๐ Passing CandidateValidationMessage::ValidateFromExhaustive to the candidate validation subsystem.",
);
Some(FromOrchestra::Communication {
msg: CandidateValidationMessage::ValidateFromExhaustive {
validation_data,
validation_code,
candidate_receipt,
pov,
executor_params,
exec_kind,
response_sender,
},
})
},
}
},
x if x.misbehaves_invalid() && x.should_misbehave(exec_kind.into()) => {
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
match behave_maliciously {
true => {
let validation_result =
ValidationResult::Invalid(self.fake_validation_error.into());
gum::info!(
target: MALUS,
?behave_maliciously,
para_id = ?candidate_receipt.descriptor.para_id(),
"๐ Maliciously sending invalid validation result: {:?}.",
&validation_result,
);
response_sender.send(Ok(validation_result)).unwrap();
None
},
false => {
gum::info!(target: MALUS, "๐ 'Decided' to not act maliciously.",);
Some(FromOrchestra::Communication {
msg: CandidateValidationMessage::ValidateFromExhaustive {
validation_data,
validation_code,
candidate_receipt,
pov,
executor_params,
exec_kind,
response_sender,
},
})
},
}
},
_ => Some(FromOrchestra::Communication {
msg: CandidateValidationMessage::ValidateFromExhaustive {
validation_data,
validation_code,
candidate_receipt,
pov,
executor_params,
exec_kind,
response_sender,
},
}),
}
},
msg => Some(msg),
}
}
}