Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Malus: improvements in dispute ancestor and suggest garbage candidate implementation #5011

Merged
merged 46 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
93656e2
Implement fake validation results
sandreim Feb 14, 2022
f216579
Merge branch 'master' of github.com:paritytech/polkadot into sandreim…
sandreim Feb 28, 2022
2744e75
refactor
sandreim Mar 1, 2022
9941f03
Merge branch 'master' of github.com:paritytech/polkadot into sandreim…
sandreim Mar 1, 2022
a3172b0
cargo lock
sandreim Mar 1, 2022
9100866
spell check
sandreim Mar 1, 2022
efd37ad
spellcheck
sandreim Mar 2, 2022
d4da58d
typos
sandreim Mar 2, 2022
b4719b4
Merge branch 'master' of github.com:paritytech/polkadot into sandreim…
sandreim Mar 4, 2022
b70512b
Review feedback
sandreim Mar 10, 2022
050bab4
Merge branch 'master' of github.com:paritytech/polkadot into sandreim…
sandreim Mar 10, 2022
f786866
move stuff around
sandreim Mar 10, 2022
9e72a42
Merge branch 'master' of github.com:paritytech/polkadot into sandreim…
sandreim Mar 23, 2022
c4a4560
chores
sandreim Mar 23, 2022
e90ab09
Impl valid - still wip
sandreim Mar 24, 2022
8226f4e
fixes
sandreim Mar 26, 2022
35c8c8e
fmt
sandreim Mar 26, 2022
1c279dc
Pull Ladi's implementation:
sandreim Mar 28, 2022
8932494
Fix build
sandreim Mar 28, 2022
fb75de4
Logs and comments
sandreim Mar 28, 2022
9408f45
WIP: suggest garbage candidate + implement validation result caching
sandreim Mar 28, 2022
78ed119
fix
sandreim Mar 28, 2022
efe4e50
Do commitment hash checks in candidate validation
sandreim Mar 30, 2022
1d02b2a
Minor refactor in approval, backing, dispute-coord
sandreim Mar 30, 2022
4719ad3
Working version of suggest garbage candidate
sandreim Mar 30, 2022
519d693
Dedup
sandreim Mar 30, 2022
6724b4a
cleanup #1
sandreim Mar 31, 2022
b6e1bbd
Fix tests
sandreim Apr 1, 2022
1980661
Merge branch 'master' of github.com:paritytech/polkadot into sandreim…
sandreim Apr 1, 2022
5f29b6c
remove debug leftovers
sandreim Apr 1, 2022
ea2b1e5
fmt
sandreim Apr 1, 2022
fca095f
Accidentally commited some local test
sandreim Apr 1, 2022
7d6b472
spellcheck
sandreim Apr 1, 2022
3103ce7
some more fixes
sandreim Apr 4, 2022
bde2403
Refactor and fix it
sandreim Apr 8, 2022
3784972
review feedback
sandreim Apr 8, 2022
991d77f
typo
sandreim Apr 8, 2022
0fa5890
tests review feedback
sandreim Apr 8, 2022
71c35a9
refactor disputer
sandreim Apr 8, 2022
fee4722
fix tests
sandreim Apr 8, 2022
6fb63d2
Fix zombienet disputes test
sandreim Apr 11, 2022
e29433e
Merge branch 'master' of github.com:paritytech/polkadot into sandreim…
sandreim Apr 11, 2022
ab22c59
spellcheck
sandreim Apr 11, 2022
f67b522
fix
sandreim Apr 11, 2022
6181157
Fix ui tests
sandreim Apr 11, 2022
155524c
fix typo
sandreim Apr 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub struct ValidationWorkerCommand {

#[allow(missing_docs)]
#[derive(Debug, Parser)]
#[cfg_attr(feature = "malus", derive(Clone))]
pub struct RunCmd {
#[allow(missing_docs)]
#[clap(flatten)]
Expand Down
8 changes: 5 additions & 3 deletions node/malus/src/malus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ enum NemesisVariant {
/// Back a candidate with a specifically crafted proof of validity.
BackGarbageCandidate(RunCmd),
/// Delayed disputing of ancestors that are perfectly fine.
DisputeAncestor(RunCmd),
DisputeAncestor(DisputeAncestorOptions),

#[allow(missing_docs)]
#[clap(name = "prepare-worker", hide = true)]
Expand Down Expand Up @@ -67,8 +67,10 @@ impl MalusCli {
polkadot_cli::run_node(run_cmd(cmd), BackGarbageCandidate)?,
NemesisVariant::SuggestGarbageCandidate(cmd) =>
polkadot_cli::run_node(run_cmd(cmd), SuggestGarbageCandidate)?,
NemesisVariant::DisputeAncestor(cmd) =>
polkadot_cli::run_node(run_cmd(cmd), DisputeValidCandidates)?,
NemesisVariant::DisputeAncestor(opts) => polkadot_cli::run_node(
run_cmd(opts.clone().cmd),
DisputeValidCandidates::new(opts),
)?,
NemesisVariant::PvfPrepareWorker(cmd) => {
#[cfg(target_os = "android")]
{
Expand Down
138 changes: 138 additions & 0 deletions node/malus/src/variants/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// 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/>.

//! Implements common code for nemesis. Currently, only `FakeValidationResult`
//! interceptor is implemented.

#![allow(missing_docs)]

// Filter wrapping related types.
use super::dispute_valid_candidates::{FakeCandidateValidation, FakeCandidateValidationError};
use crate::{interceptor::*, shared::MALUS};
use polkadot_node_primitives::{InvalidCandidate, ValidationResult};
use polkadot_node_subsystem::messages::CandidateValidationMessage;

#[derive(Clone, Debug)]
/// An interceptor which fakes validation result with a preconfigured result.
/// Replaces `CandidateValidationSubsystem`.
pub struct ReplaceValidationResult {
fake_validation: FakeCandidateValidation,
fake_validation_error: FakeCandidateValidationError,
}

impl ReplaceValidationResult {
pub fn new(
fake_validation: FakeCandidateValidation,
fake_validation_error: FakeCandidateValidationError,
) -> Self {
Self { fake_validation, fake_validation_error }
}
}

impl<Sender> MessageInterceptor<Sender> for ReplaceValidationResult
where
Sender: overseer::SubsystemSender<CandidateValidationMessage> + Clone + Send + 'static,
{
type Message = CandidateValidationMessage;

// Capture all candidate validation requests and depending on configuration fail them.
// MaybeTODO: add option to configure the failure reason.
fn intercept_incoming(
&self,
_sender: &mut Sender,
msg: FromOverseer<Self::Message>,
) -> Option<FromOverseer<Self::Message>> {
if self.fake_validation == FakeCandidateValidation::Disabled {
return Some(msg)
}

match msg {
FromOverseer::Communication {
msg:
CandidateValidationMessage::ValidateFromExhaustive(
validation_data,
validation_code,
descriptor,
pov,
timeout,
sender,
),
} => {
match self.fake_validation {
FakeCandidateValidation::ApprovalInvalid |
FakeCandidateValidation::BackingAndApprovalInvalid => {
let validation_result =
ValidationResult::Invalid(InvalidCandidate::InvalidOutputs);

tracing::info!(
target = MALUS,
para_id = ?descriptor.para_id,
candidate_hash = ?descriptor.para_head,
"ValidateFromExhaustive result: {:?}",
&validation_result
);
// We're not even checking the candidate, this makes us appear faster than honest validators.
sender.send(Ok(validation_result)).unwrap();
None
},
_ => Some(FromOverseer::Communication {
msg: CandidateValidationMessage::ValidateFromExhaustive(
validation_data,
validation_code,
descriptor,
pov,
timeout,
sender,
),
}),
}
},
FromOverseer::Communication {
msg:
CandidateValidationMessage::ValidateFromChainState(descriptor, pov, timeout, sender),
} => {
match self.fake_validation {
FakeCandidateValidation::BackingInvalid |
FakeCandidateValidation::BackingAndApprovalInvalid => {
let validation_result =
ValidationResult::Invalid(self.fake_validation_error.clone().into());
tracing::info!(
target = MALUS,
para_id = ?descriptor.para_id,
candidate_hash = ?descriptor.para_head,
"ValidateFromChainState result: {:?}",
&validation_result
);

// We're not even checking the candidate, this makes us appear faster than honest validators.
sender.send(Ok(validation_result)).unwrap();
None
},
_ => Some(FromOverseer::Communication {
msg: CandidateValidationMessage::ValidateFromChainState(
descriptor, pov, timeout, sender,
),
}),
}
},
msg => Some(msg),
}
}

fn intercept_outgoing(&self, msg: AllMessages) -> Option<AllMessages> {
Some(msg)
}
}
132 changes: 125 additions & 7 deletions node/malus/src/variants/dispute_valid_candidates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,113 @@

#![allow(missing_docs)]

use clap::{ArgEnum, Parser};
sandreim marked this conversation as resolved.
Show resolved Hide resolved
use polkadot_cli::{
prepared_overseer_builder,
service::{
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
ProvideRuntimeApi, SpawnNamed,
},
RunCmd,
};

// Filter wrapping related types.
use crate::interceptor::*;
use crate::{interceptor::*, shared::MALUS, variants::ReplaceValidationResult};
use polkadot_node_primitives::InvalidCandidate;

// Import extra types relevant to the particular
// subsystem.
use polkadot_node_core_backing::CandidateBackingSubsystem;
use polkadot_node_core_candidate_validation::CandidateValidationSubsystem;

use polkadot_node_subsystem::messages::{
ApprovalDistributionMessage, CandidateBackingMessage, DisputeCoordinatorMessage,
};
use sp_keystore::SyncCryptoStorePtr;

use std::sync::Arc;

#[derive(ArgEnum, Clone, Debug, PartialEq)]
#[clap(rename_all = "kebab-case")]
pub enum FakeCandidateValidation {
sandreim marked this conversation as resolved.
Show resolved Hide resolved
Disabled,
BackingInvalid,
ApprovalInvalid,
BackingAndApprovalInvalid,
// TODO: impl Valid.
sandreim marked this conversation as resolved.
Show resolved Hide resolved
}

/// Candidate invalidity details
#[derive(ArgEnum, Clone, Debug, PartialEq)]
#[clap(rename_all = "kebab-case")]
pub enum FakeCandidateValidationError {
/// Validation outputs check doesn't pass.
InvalidOutputs,
/// Failed to execute.`validate_block`. This includes function panicking.
ExecutionError,
/// Execution timeout.
Timeout,
/// Validation input is over the limit.
ParamsTooLarge,
/// Code size is over the limit.
CodeTooLarge,
/// Code does not decompress correctly.
CodeDecompressionFailure,
/// PoV does not decompress correctly.
POVDecompressionFailure,
/// Validation function returned invalid data.
BadReturn,
/// Invalid relay chain parent.
BadParent,
/// POV hash does not match.
POVHashMismatch,
/// Bad collator signature.
BadSignature,
/// Para head hash does not match.
ParaHeadHashMismatch,
/// Validation code hash does not match.
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::CodeDecompressionFailure =>
InvalidCandidate::CodeDecompressionFailure,
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, Parser)]
#[clap(rename_all = "kebab-case")]
#[allow(missing_docs)]
pub struct DisputeAncestorOptions {
#[clap(long, arg_enum, ignore_case = true, default_value_t = FakeCandidateValidation::Disabled)]
pub fake_validation: FakeCandidateValidation,

#[clap(long, arg_enum, ignore_case = true, default_value_t = FakeCandidateValidationError::InvalidOutputs)]
pub fake_validation_error: FakeCandidateValidationError,

#[clap(flatten)]
pub cmd: RunCmd,
sandreim marked this conversation as resolved.
Show resolved Hide resolved
}

/// Replace outgoing approval messages with disputes.
#[derive(Clone, Debug)]
struct ReplaceApprovalsWithDisputes;
Expand Down Expand Up @@ -75,6 +160,12 @@ where
session,
..
}) => {
tracing::info!(
target = MALUS,
para_id = ?candidate_receipt.descriptor.para_id,
?candidate_hash,
"Disputing candidate",
);
// this would also dispute candidates we were not assigned to approve
Some(AllMessages::DisputeCoordinator(
DisputeCoordinatorMessage::IssueLocalStatement(
Expand All @@ -90,8 +181,16 @@ where
}
}

/// Generates an overseer that disputes instead of approving valid candidates.
pub(crate) struct DisputeValidCandidates;
pub(crate) struct DisputeValidCandidates {
/// Fake validation config (applies to disputes as well).
opts: DisputeAncestorOptions,
}

impl DisputeValidCandidates {
pub fn new(opts: DisputeAncestorOptions) -> Self {
Self { opts }
}
}

impl OverseerGen for DisputeValidCandidates {
fn generate<'a, Spawner, RuntimeClient>(
Expand All @@ -106,13 +205,32 @@ impl OverseerGen for DisputeValidCandidates {
{
let spawner = args.spawner.clone();
let crypto_store_ptr = args.keystore.clone() as SyncCryptoStorePtr;
let filter = ReplaceApprovalsWithDisputes;
let backing_filter = ReplaceApprovalsWithDisputes;
let validation_filter = ReplaceValidationResult::new(
self.opts.fake_validation.clone(),
self.opts.fake_validation_error.clone(),
sandreim marked this conversation as resolved.
Show resolved Hide resolved
);
let candidate_validation_config = args.candidate_validation_config.clone();

prepared_overseer_builder(args)?
.replace_candidate_backing(move |cb| {
.replace_candidate_backing(move |cb_subsystem| {
InterceptedSubsystem::new(
CandidateBackingSubsystem::new(spawner, crypto_store_ptr, cb.params.metrics),
filter,
CandidateBackingSubsystem::new(
spawner,
crypto_store_ptr,
cb_subsystem.params.metrics,
),
backing_filter,
)
})
.replace_candidate_validation(move |cv_subsystem| {
InterceptedSubsystem::new(
CandidateValidationSubsystem::with_config(
candidate_validation_config,
cv_subsystem.metrics,
cv_subsystem.pvf_metrics,
),
validation_filter,
)
})
.build_with_connector(connector)
Expand Down
5 changes: 4 additions & 1 deletion node/malus/src/variants/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
//! Collection of behavior variants.

mod back_garbage_candidate;
mod common;
mod dispute_valid_candidates;
mod suggest_garbage_candidate;

pub(crate) use self::{
back_garbage_candidate::BackGarbageCandidate, dispute_valid_candidates::DisputeValidCandidates,
back_garbage_candidate::BackGarbageCandidate,
dispute_valid_candidates::{DisputeAncestorOptions, DisputeValidCandidates},
suggest_garbage_candidate::SuggestGarbageCandidate,
};
pub(crate) use common::ReplaceValidationResult;