introduce malus + zombienet based integration tests (#4131)

* test/malus: craft the first maliciously disputing actor

* initial draft

* Add Dockerfile and instructions how to use it to build malus image locally

* Forgot one flag for the build cmd

* we are not docker specific, we are happy to use any containerruntime

* shuffle things around

* add initial tera based integration test

* chores

* fixins

* simple setup to start

* other samples (WIP)

* add Docker version with cargo-chef

* update substarte and small change of orders in commands in the container file

* metrics one

* fmt

* minor

* fixin

* fix metric names

* -d

* add open gauge

* fmt

* spellcheck

* fix test

* adjust to changed error messages

* refactor, more malus impls

* more malus changes

* foo

* minor cleanup

* suggest garbage candidate

* chore

* fix suggest garabge malus

* malus: back garbage candidate

* cargo lock

* re-introduce metrics

* chore: cargo fmt

* undoe 1.54.0 output, CI uses 1.53.0 rustc

* update location of js types

* Fix trybuild

* add tag to image name also; this will be replaced in the prod version

* Tests fixed

* add some fix me

* add dockerfile for ci

* Add docker file for malus for ci

* use variables in .toml file

* add chnages for malus test

* some fixes

* some more fixes

* Update .gitlab-ci.yml

* add local build for polkadot and malus

* some fixes

* enable disputes feature in CI

* ok, ok

* rename: MsgFilter -> MessageInterceptor

* remove TODO that would not have worked

* intercept

* refactor

* fix README and containers

* fix

* chore: cargo fmt

* avoid some more malus-$VARIANT references

* fix argument order

* chore: add about

* Update sanity check in relay chain selection

* fix order, add dispute-unavailable-block malus

* fixup: avoid underflow issue

* it's all u32

* fix conditional use

* Revert "it's all u32"

This reverts commit 6b3ae25bfd0bbf0b51d90d743642a75a4a815d6e.

* Revert "fixup: avoid underflow issue"

This reverts commit 336cbe2938e9720f870d37d8feeab7ca69200f47.

* Revert "Update sanity check in relay chain selection"

This reverts commit 970647f35e1116136e12fd91cd9f2fb7e18ad28d.

* update the malus bin

* Update node/malus/integrationtests/0003-dispute-unavailable-block.feature

Co-authored-by: Andronik Ordian <write@reusable.software>

* add some FIXME reminders

* update path to index.js

* Update .gitlab-ci.yml

* Update node/malus/integrationtests/0001-dispute-valid-block.toml

* try 1: make malus test run

* chore: cargo fmt

* temporary fix

* use subcommand syntax from latest gurke

* cargo +nightly fmt

* add collator to a a test

* docs: add env vars to README

* update ci to run dispute-valid-block test

* needs the polkadot image

* Fix path for nodejs container

* post merge fix

* download proper dir  containg configs for malus test

* update the malus ci job

* rm a whitespace

* temp build for malus

* use correct build command for temp malus

* remove subcommands for now

* set max validators per core in the default HostConfig

* tabs

* update beefy

* fixup

* fixup II

* make one variant compile

* make other variants compile

* revert changes to chain_spec

* fmt

* build malus image from polkadot-test-malus again

* revert unrelated changes

* try fixing build-malus job

* Revert "remove subcommands for now"

This reverts commit 5d8292bc49252124937affec4b7c28181a5deb7e.

* try fixing build-malus job II

* MVP working dispute-ancestor

* renames

* fix PVF execution on malus

* fix test

* fix typo

* fmt

* checkmate

* try something

* make it actually work

* some tweaks to 01 feature test

* fmt

* sleep a bit more

* complete wococoization

* some tweaks to 01 feature test

* typo fix

* use correct metric names

* fix

* ffs

* .

* try some rearrangement

* Attempt to wait till initial node bootstrap in test

* Fix test syntax

* Run malus tests with v2 script

* Proper symlink created

* simnet v14

* add zombienet tests

* add zombie net test - draft

* add more tests to dispute suite

* add within to fix tests

* replace test directory and start test migration

* migrate all the tests

* add timeout to tests

* reduce debug

* make easy to test in dev

* set appropriated debug

* use image from ci

* fix config for test

* set images from ci

* fix config

* add COLIMAGE env

* tweek tests

* reduce debug

* typo

* wip, migrate old test to zombie-net

* adjunt test config for zombie-net

* run mauls 0001 test only

* clean and setup smoke-test in zombie-net

* add extra time to assertinons

* clean code to merge and improve README

* add info to access logs

* improved readme

* merge master and resolve conflicts

* Update zombienet_tests/README.md

Co-authored-by: Bernhard Schuster <bernhard@ahoi.io>

* clean and consolidate zombienet name

* change runner in gitlab

* add comment explain why we use wococo

* change tag for runner

* remove unused tests

* remove dup Dockerfile and update description

* fmt

* fix compilation post-merge

* fmt

* cut me Some slack

Co-authored-by: Bernhard Schuster <bernhard@ahoi.io>
Co-authored-by: radupopa2010 <radupopa2010@yahoo.com>
Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: Anton Gavrilov <AntonE.Gavrilov@gmail.com>
Co-authored-by: Andronik Ordian <write@reusable.software>
Co-authored-by: Lldenaurois <Ljdenaurois@gmail.com>
This commit is contained in:
Javier Viola
2021-11-20 15:03:28 +01:00
committed by GitHub
parent e35ebee0f1
commit ea5dbd0475
32 changed files with 1401 additions and 331 deletions
@@ -24,9 +24,6 @@ use polkadot_node_subsystem::*;
pub use polkadot_node_subsystem::{messages::AllMessages, overseer, FromOverseer};
use std::{future::Future, pin::Pin};
#[cfg(test)]
mod tests;
/// Filter incoming and outgoing messages.
pub trait MessageInterceptor<Sender>: Send + Sync + Clone + 'static
where
+126
View File
@@ -0,0 +1,126 @@
// Copyright 2021 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/>.
//! A malus or nemesis node launch code.
use color_eyre::eyre;
use polkadot_cli::{Cli, RunCmd};
use structopt::StructOpt;
pub(crate) mod interceptor;
pub(crate) mod shared;
mod variants;
use variants::*;
/// Define the different variants of behavior.
#[derive(Debug, StructOpt)]
#[structopt(about = "Malus - the nemesis of polkadot.")]
#[structopt(rename_all = "kebab-case")]
enum NemesisVariant {
/// Suggest a candidate with an invalid proof of validity.
SuggestGarbageCandidate(RunCmd),
/// Back a candidate with a specifically crafted proof of validity.
BackGarbageCandidate(RunCmd),
/// Delayed disputing of ancestors that are perfectly fine.
DisputeAncestor(RunCmd),
#[allow(missing_docs)]
#[structopt(name = "prepare-worker", setting = structopt::clap::AppSettings::Hidden)]
PvfPrepareWorker(polkadot_cli::ValidationWorkerCommand),
#[allow(missing_docs)]
#[structopt(name = "execute-worker", setting = structopt::clap::AppSettings::Hidden)]
PvfExecuteWorker(polkadot_cli::ValidationWorkerCommand),
}
#[derive(Debug, StructOpt)]
#[allow(missing_docs)]
struct MalusCli {
#[structopt(subcommand)]
pub variant: NemesisVariant,
}
fn run_cmd(run: RunCmd) -> Cli {
Cli { subcommand: None, run }
}
impl MalusCli {
/// Launch a malus node.
fn launch(self) -> eyre::Result<()> {
match self.variant {
NemesisVariant::BackGarbageCandidate(cmd) =>
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::PvfPrepareWorker(cmd) => {
#[cfg(target_os = "android")]
{
return Err("PVF preparation workers are not supported under this platform")
.into()
}
#[cfg(not(target_os = "android"))]
{
polkadot_node_core_pvf::prepare_worker_entrypoint(&cmd.socket_path);
}
},
NemesisVariant::PvfExecuteWorker(cmd) => {
#[cfg(target_os = "android")]
{
return Err("PVF execution workers are not supported under this platform").into()
}
#[cfg(not(target_os = "android"))]
{
polkadot_node_core_pvf::execute_worker_entrypoint(&cmd.socket_path);
}
},
}
Ok(())
}
}
fn main() -> eyre::Result<()> {
color_eyre::install()?;
let cli = MalusCli::from_args();
cli.launch()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subcommand_works() {
let cli = MalusCli::from_iter_safe(IntoIterator::into_iter([
"malus",
"dispute-ancestor",
"--bob",
]))
.unwrap();
assert_matches::assert_matches!(cli, MalusCli {
variant: NemesisVariant::DisputeAncestor(run),
..
} => {
assert!(run.base.bob);
});
}
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2021 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/>.
use futures::prelude::*;
use polkadot_node_primitives::SpawnNamed;
pub const MALUS: &str = "MALUS😈😈😈";
#[allow(unused)]
pub(crate) const MALICIOUS_POV: &[u8] = "😈😈pov_looks_valid_to_me😈😈".as_bytes();
/// Launch a service task for each item in the provided queue.
#[allow(unused)]
pub(crate) fn launch_processing_task<X, F, U, Q, S>(spawner: &S, queue: Q, action: F)
where
F: Fn(X) -> U + Send + 'static,
U: Future<Output = ()> + Send + 'static,
Q: Stream<Item = X> + Send + 'static,
X: Send,
S: 'static + SpawnNamed + Clone + Unpin,
{
let spawner2: S = spawner.clone();
spawner.spawn(
"nemesis-queue-processor",
Some("malus"),
Box::pin(async move {
let spawner3 = spawner2.clone();
queue
.for_each(move |input| {
spawner3.spawn("nemesis-task", Some("malus"), Box::pin(action(input)));
async move { () }
})
.await;
}),
);
}
-121
View File
@@ -1,121 +0,0 @@
// Copyright 2017-2020 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/>.
//! A malicious overseer.
//!
//! An example on how to use the `OverseerGen` pattern to
//! instantiate a modified subsystem implementation
//! for usage with `simnet`/Gurke.
#![allow(missing_docs)]
use color_eyre::eyre;
use polkadot_cli::{
prepared_overseer_builder,
service::{
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, OverseerGen,
OverseerGenArgs, ParachainHost, ProvideRuntimeApi, SpawnNamed,
},
Cli,
};
// Import extra types relevant to the particular
// subsystem.
use polkadot_node_core_candidate_validation::CandidateValidationSubsystem;
use polkadot_node_subsystem::{
messages::{AllMessages, CandidateValidationMessage},
overseer::{self, Overseer, OverseerConnector, OverseerHandle},
FromOverseer,
};
use malus::*;
// Filter wrapping related types.
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use structopt::StructOpt;
/// Silly example, just drop every second outgoing message.
#[derive(Clone, Default, Debug)]
struct Skippy(Arc<AtomicUsize>);
impl<Sender> MessageInterceptor<Sender> for Skippy
where
Sender: overseer::SubsystemSender<AllMessages>
+ overseer::SubsystemSender<CandidateValidationMessage>
+ Clone
+ 'static,
{
type Message = CandidateValidationMessage;
fn intercept_incoming(
&self,
_sender: &mut Sender,
msg: FromOverseer<Self::Message>,
) -> Option<FromOverseer<Self::Message>> {
if self.0.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
Some(msg)
} else {
None
}
}
fn intercept_outgoing(&self, msg: AllMessages) -> Option<AllMessages> {
Some(msg)
}
}
/// Generates an overseer that exposes bad behavior.
struct BehaveMaleficient;
impl OverseerGen for BehaveMaleficient {
fn generate<'a, Spawner, RuntimeClient>(
&self,
connector: OverseerConnector,
args: OverseerGenArgs<'a, Spawner, RuntimeClient>,
) -> Result<(Overseer<Spawner, Arc<RuntimeClient>>, OverseerHandle), Error>
where
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
let candidate_validation_config = args.candidate_validation_config.clone();
prepared_overseer_builder(args)?
.replace_candidate_validation(|orig: CandidateValidationSubsystem| {
InterceptedSubsystem::new(
CandidateValidationSubsystem::with_config(
candidate_validation_config,
orig.metrics,
orig.pvf_metrics,
),
Skippy::default(),
)
})
.build_with_connector(connector)
.map_err(|e| e.into())
}
}
fn main() -> eyre::Result<()> {
color_eyre::install()?;
let cli = Cli::from_args();
assert_matches::assert_matches!(cli.subcommand, None);
polkadot_cli::run_node(cli, BehaveMaleficient)?;
Ok(())
}
@@ -0,0 +1,230 @@
// Copyright 2021 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/>.
//! A malicious overseer backing a particular candidate with a
//! malicious proof of validity that is received.
#![allow(missing_docs)]
use polkadot_cli::{
prepared_overseer_builder,
service::{
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
ProvideRuntimeApi, SpawnNamed,
},
};
// Import extra types relevant to the particular
// subsystem.
use polkadot_node_core_candidate_validation::CandidateValidationSubsystem;
use polkadot_node_subsystem::messages::{
AvailabilityRecoveryMessage, CandidateValidationMessage, ValidationFailed,
};
use polkadot_node_subsystem_util as util;
// Filter wrapping related types.
use crate::{interceptor::*, shared::*};
use polkadot_node_primitives::{PoV, ValidationResult};
use polkadot_primitives::v1::{
CandidateCommitments, CandidateDescriptor, CandidateReceipt, PersistedValidationData,
ValidationCode,
};
use futures::channel::oneshot;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
#[derive(Clone, Debug)]
struct BribedPassageInner<Spawner> {
spawner: Spawner,
cache: HashMap<CandidateDescriptor, CandidateReceipt>,
}
#[derive(Clone, Debug)]
struct BribedPassage<Spawner> {
inner: Arc<Mutex<BribedPassageInner<Spawner>>>,
}
impl<Spawner> BribedPassage<Spawner>
where
Spawner: SpawnNamed,
{
fn let_pass(
persisted_validation_data: PersistedValidationData,
validation_code: Option<ValidationCode>,
_candidate_descriptor: CandidateDescriptor,
_pov: Arc<PoV>,
response_sender: oneshot::Sender<Result<ValidationResult, ValidationFailed>>,
) {
let candidate_commitmentments = CandidateCommitments {
head_data: persisted_validation_data.parent_head.clone(),
new_validation_code: validation_code,
..Default::default()
};
response_sender
.send(Ok(ValidationResult::Valid(candidate_commitmentments, persisted_validation_data)))
.unwrap();
}
}
impl<Sender, Spawner> MessageInterceptor<Sender> for BribedPassage<Spawner>
where
Sender: overseer::SubsystemSender<CandidateValidationMessage>
+ overseer::SubsystemSender<AllMessages>
+ Clone
+ Send
+ 'static,
Spawner: SpawnNamed + Send + Clone + 'static,
{
type Message = CandidateValidationMessage;
fn intercept_incoming(
&self,
sender: &mut Sender,
msg: FromOverseer<Self::Message>,
) -> Option<FromOverseer<Self::Message>> {
match msg {
FromOverseer::Communication {
msg:
CandidateValidationMessage::ValidateFromExhaustive(
persisted_validation_data,
validation_code,
candidate_descriptor,
pov,
_duration,
response_sender,
),
} if pov.block_data.0.as_slice() == MALICIOUS_POV => {
Self::let_pass(
persisted_validation_data,
Some(validation_code),
candidate_descriptor,
pov,
response_sender,
);
None
},
FromOverseer::Communication {
msg:
CandidateValidationMessage::ValidateFromChainState(
candidate_descriptor,
pov,
_duration,
response_sender,
),
} if pov.block_data.0.as_slice() == MALICIOUS_POV => {
if let Some(candidate_receipt) =
self.inner.lock().unwrap().cache.get(&candidate_descriptor).cloned()
{
let mut subsystem_sender = sender.clone();
let spawner = self.inner.lock().unwrap().spawner.clone();
spawner.spawn(
"malus-back-garbage-adhoc",
Some("malus"),
Box::pin(async move {
let relay_parent = candidate_descriptor.relay_parent;
let session_index = util::request_session_index_for_child(
relay_parent,
&mut subsystem_sender,
)
.await;
let session_index = session_index.await.unwrap().unwrap();
let (a_tx, a_rx) = oneshot::channel();
subsystem_sender
.send_message(AllMessages::from(
AvailabilityRecoveryMessage::RecoverAvailableData(
candidate_receipt,
session_index,
None,
a_tx,
),
))
.await;
if let Ok(Ok(availability_data)) = a_rx.await {
Self::let_pass(
availability_data.validation_data,
None,
candidate_descriptor,
pov,
response_sender,
);
} else {
tracing::info!(
target = MALUS,
"Could not get availability data, can't back"
);
}
}),
);
} else {
tracing::info!(target = MALUS, "No CandidateReceipt available to work with");
}
None
},
msg => Some(msg),
}
}
fn intercept_outgoing(&self, msg: AllMessages) -> Option<AllMessages> {
Some(msg)
}
}
/// Generates an overseer that exposes bad behavior.
pub(crate) struct BackGarbageCandidate;
impl OverseerGen for BackGarbageCandidate {
fn generate<'a, Spawner, RuntimeClient>(
&self,
connector: OverseerConnector,
args: OverseerGenArgs<'a, Spawner, RuntimeClient>,
) -> Result<(Overseer<Spawner, Arc<RuntimeClient>>, OverseerHandle), Error>
where
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
let candidate_validation_config = args.candidate_validation_config.clone();
let spawner = args.spawner.clone();
prepared_overseer_builder(args)?
.replace_candidate_validation(|cv| {
InterceptedSubsystem::new(
CandidateValidationSubsystem::with_config(
candidate_validation_config,
cv.metrics,
cv.pvf_metrics,
),
BribedPassage::<Spawner> {
inner: Arc::new(Mutex::new(BribedPassageInner {
spawner,
cache: Default::default(),
})),
},
)
})
.build_with_connector(connector)
.map_err(|e| e.into())
}
}
@@ -0,0 +1,121 @@
// Copyright 2021 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/>.
//! A malicious node that replaces approvals with invalid disputes
//! against valid candidates.
//!
//! Attention: For usage with `zombienet` only!
#![allow(missing_docs)]
use polkadot_cli::{
prepared_overseer_builder,
service::{
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
ProvideRuntimeApi, SpawnNamed,
},
};
// Filter wrapping related types.
use crate::interceptor::*;
// Import extra types relevant to the particular
// subsystem.
use polkadot_node_core_backing::CandidateBackingSubsystem;
use polkadot_node_subsystem::messages::{
ApprovalDistributionMessage, CandidateBackingMessage, DisputeCoordinatorMessage,
};
use sp_keystore::SyncCryptoStorePtr;
use std::sync::Arc;
/// Replace outgoing approval messages with disputes.
#[derive(Clone, Debug)]
struct ReplaceApprovalsWithDisputes;
impl<Sender> MessageInterceptor<Sender> for ReplaceApprovalsWithDisputes
where
Sender: overseer::SubsystemSender<CandidateBackingMessage> + Clone + Send + 'static,
{
type Message = CandidateBackingMessage;
fn intercept_incoming(
&self,
_sender: &mut Sender,
msg: FromOverseer<Self::Message>,
) -> Option<FromOverseer<Self::Message>> {
Some(msg)
}
fn intercept_outgoing(&self, msg: AllMessages) -> Option<AllMessages> {
match msg {
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(
_,
)) => {
// drop the message on the floor
None
},
AllMessages::DisputeCoordinator(DisputeCoordinatorMessage::ImportStatements {
candidate_hash,
candidate_receipt,
session,
..
}) => {
// this would also dispute candidates we were not assigned to approve
Some(AllMessages::DisputeCoordinator(
DisputeCoordinatorMessage::IssueLocalStatement(
session,
candidate_hash,
candidate_receipt,
false,
),
))
},
msg => Some(msg),
}
}
}
/// Generates an overseer that disputes instead of approving valid candidates.
pub(crate) struct DisputeValidCandidates;
impl OverseerGen for DisputeValidCandidates {
fn generate<'a, Spawner, RuntimeClient>(
&self,
connector: OverseerConnector,
args: OverseerGenArgs<'a, Spawner, RuntimeClient>,
) -> Result<(Overseer<Spawner, Arc<RuntimeClient>>, OverseerHandle), Error>
where
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
let spawner = args.spawner.clone();
let crypto_store_ptr = args.keystore.clone() as SyncCryptoStorePtr;
let filter = ReplaceApprovalsWithDisputes;
prepared_overseer_builder(args)?
.replace_candidate_backing(move |cb| {
InterceptedSubsystem::new(
CandidateBackingSubsystem::new(spawner, crypto_store_ptr, cb.params.metrics),
filter,
)
})
.build_with_connector(connector)
.map_err(|e| e.into())
}
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2021 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/>.
//! Collection of behavior variants.
mod back_garbage_candidate;
mod dispute_valid_candidates;
mod suggest_garbage_candidate;
pub(crate) use self::{
back_garbage_candidate::BackGarbageCandidate, dispute_valid_candidates::DisputeValidCandidates,
suggest_garbage_candidate::SuggestGarbageCandidate,
};
@@ -0,0 +1,172 @@
// Copyright 2021 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/>.
//! A malicious overseer proposing a garbage block.
//!
//! Supposed to be used with regular nodes or in conjunction
//! with [`malus-back-garbage-candidate.rs`](./malus-back-garbage-candidate.rs)
//! to simulate a coordinated attack.
#![allow(missing_docs)]
use polkadot_cli::{
prepared_overseer_builder,
service::{
AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
ProvideRuntimeApi, SpawnNamed,
},
};
// Import extra types relevant to the particular
// subsystem.
use polkadot_node_core_backing::CandidateBackingSubsystem;
use polkadot_node_primitives::Statement;
use polkadot_node_subsystem::{
messages::{CandidateBackingMessage, StatementDistributionMessage},
overseer::{self, SubsystemSender},
};
use polkadot_node_subsystem_util as util;
// Filter wrapping related types.
use crate::interceptor::*;
use polkadot_primitives::v1::{
CandidateCommitments, CandidateReceipt, CommittedCandidateReceipt, CompactStatement, Hash,
Signed,
};
use sp_keystore::SyncCryptoStorePtr;
use util::metered;
use std::sync::Arc;
use crate::shared::*;
/// Replaces the seconded PoV data
/// of outgoing messages by some garbage data.
#[derive(Clone)]
struct ReplacePoVBytes<Sender>
where
Sender: Send,
{
keystore: SyncCryptoStorePtr,
queue: metered::UnboundedMeteredSender<(Sender, Hash, CandidateReceipt)>,
}
impl<Sender> MessageInterceptor<Sender> for ReplacePoVBytes<Sender>
where
Sender: overseer::SubsystemSender<CandidateBackingMessage> + Clone + Send + 'static,
{
type Message = CandidateBackingMessage;
fn intercept_incoming(
&self,
sender: &mut Sender,
msg: FromOverseer<Self::Message>,
) -> Option<FromOverseer<Self::Message>> {
match msg {
FromOverseer::Communication {
msg: CandidateBackingMessage::Second(hash, candidate_receipt, _pov),
} => {
self.queue
.unbounded_send((sender.clone(), hash, candidate_receipt.clone()))
.unwrap();
None
},
other => Some(other),
}
}
fn intercept_outgoing(&self, msg: AllMessages) -> Option<AllMessages> {
Some(msg)
}
}
/// Generates an overseer that exposes bad behavior.
pub(crate) struct SuggestGarbageCandidate;
impl OverseerGen for SuggestGarbageCandidate {
fn generate<'a, Spawner, RuntimeClient>(
&self,
connector: OverseerConnector,
args: OverseerGenArgs<'a, Spawner, RuntimeClient>,
) -> Result<(Overseer<Spawner, Arc<RuntimeClient>>, OverseerHandle), Error>
where
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
let spawner = args.spawner.clone();
let (sink, source) = metered::unbounded();
let keystore = args.keystore.clone() as SyncCryptoStorePtr;
let filter = ReplacePoVBytes { keystore: keystore.clone(), queue: sink };
let keystore2 = keystore.clone();
let spawner2 = spawner.clone();
let result = prepared_overseer_builder(args)?
.replace_candidate_backing(move |cb| {
InterceptedSubsystem::new(
CandidateBackingSubsystem::new(spawner2, keystore2, cb.params.metrics),
filter,
)
})
.build_with_connector(connector)
.map_err(|e| e.into());
launch_processing_task(
&spawner,
source,
move |(mut subsystem_sender, hash, candidate_receipt): (_, Hash, CandidateReceipt)| {
let keystore = keystore.clone();
async move {
tracing::info!(
target = MALUS,
"Replacing seconded candidate pov with something else"
);
let committed_candidate_receipt = CommittedCandidateReceipt {
descriptor: candidate_receipt.descriptor.clone(),
commitments: CandidateCommitments::default(),
};
let statement = Statement::Seconded(committed_candidate_receipt);
if let Ok(validator) =
util::Validator::new(hash, keystore.clone(), &mut subsystem_sender).await
{
let signed_statement: Signed<Statement, CompactStatement> = validator
.sign(keystore, statement)
.await
.expect("Signing works. qed")
.expect("Something must come out of this. qed");
subsystem_sender
.send_message(StatementDistributionMessage::Share(
hash,
signed_statement,
))
.await;
} else {
tracing::info!("We are not a validator. Not siging anything.");
}
}
},
);
result
}
}