diff --git a/node/malus/integrationtests/0001-dispute-valid-block.toml b/node/malus/integrationtests/0001-dispute-valid-block.toml
index 009d3c7fe889..d0a66ef3af31 100644
--- a/node/malus/integrationtests/0001-dispute-valid-block.toml
+++ b/node/malus/integrationtests/0001-dispute-valid-block.toml
@@ -6,21 +6,21 @@ timeout = 1000
[nodes.alice]
validator = true
-extra-args = ["--alice"]
image = "{{get_env(name="SYNTHIMAGE") | safe }}"
command = "polkadot"
+extra-args = ["--alice"]
[nodes.bob]
validator = true
-extra-args = ["--bob"]
image = "{{get_env(name="SYNTHIMAGE") | safe }}"
command = "polkadot"
+extra-args = ["--bob"]
[nodes.charlie]
validator = true
-extra-args = ["--charlie"]
image = "{{get_env(name="SYNTHIMAGE") | safe }}"
command = "polkadot"
+extra-args = ["--charlie"]
[nodes.david]
validator = true
diff --git a/node/malus/integrationtests/0002-dispute-invalid-block.toml b/node/malus/integrationtests/0002-dispute-invalid-block.toml
index ecc336faad9d..380efd6c8ff2 100644
--- a/node/malus/integrationtests/0002-dispute-invalid-block.toml
+++ b/node/malus/integrationtests/0002-dispute-invalid-block.toml
@@ -6,30 +6,30 @@ timeout = 1000
[nodes.alice]
validator = true
-extra-args = ["--alice"]
image = "{{get_env(name="SYNTHIMAGE") | safe }}"
command = "polkadot"
+extra-args = ["--alice"]
[nodes.bob]
validator = true
-extra-args = ["back-garbage-candidate", "--bob"]
image = "{{get_env(name="MALUSIMAGE") | safe }}"
command = "/usr/local/bin/malus"
+extra-args = ["back-garbage-candidate", "--bob"]
[nodes.charlie]
validator = true
-extra-args = ["back-garbage-candidate", "--charlie"]
image = "{{get_env(name="MALUSIMAGE") | safe }}"
command = "/usr/local/bin/malus"
+extra-args = ["back-garbage-candidate", "--charlie"]
[nodes.david]
validator = true
-extra-args = ["back-garbage-candidate", "--dave"]
image = "{{get_env(name="MALUSIMAGE") | safe }}"
command = "/usr/local/bin/malus"
+extra-args = ["back-garbage-candidate", "--dave"]
[nodes.eve]
validator = true
-extra-args = ["suggest-garbage-candidate","--eve"]
image = "{{get_env(name="MALUSIMAGE") | safe }}"
command = "/usr/local/bin/malus"
+extra-args = ["suggest-garbage-candidate","--eve"]
diff --git a/node/malus/integrationtests/0003-dispute-unavailable-block.feature b/node/malus/integrationtests/0003-dispute-unavailable-block.feature
new file mode 100644
index 000000000000..8d3bba0ab59e
--- /dev/null
+++ b/node/malus/integrationtests/0003-dispute-unavailable-block.feature
@@ -0,0 +1,24 @@
+Feature: Disputes
+
+ Scenario: Dispute Invalid Block
+ Given a test network
+ Then alice is up
+ And alice reports node_roles is 4
+ And alice reports sub_libp2p_is_major_syncing is 0
+ Then sleep 15 seconds
+ Then alice reports block height is greater than 2
+ And alice reports peers count is at least 2
+ Then bob is up
+ And bob reports block height is greater than 2
+ And bob reports peers count is at least 2
+ Then charlie is up
+ And charlie reports block height is greater than 2
+ And charlie reports peers count is at least 2
+ Then david is up
+ Then eve is up
+ And alice reports polkadot_parachain_candidate_open_disputes is 1
+ Then alice polkadot_parachain_candidate_dispute_votes is at least 1
+ And bob polkadot_parachain_candidate_dispute_votes is is at least 2
+ And charlie polkadot_parachain_candidate_dispute_votes is at least 3
+ And david polkadot_parachain_candidate_dispute_votes is at least 4
+ Then alice polkadot_parachain_candidate_dispute_concluded is "invalid"
diff --git a/node/malus/integrationtests/0003-dispute-unavailable-block.toml b/node/malus/integrationtests/0003-dispute-unavailable-block.toml
new file mode 100644
index 000000000000..380efd6c8ff2
--- /dev/null
+++ b/node/malus/integrationtests/0003-dispute-unavailable-block.toml
@@ -0,0 +1,35 @@
+[settings.defaults]
+image = "{{get_env(name="SYNTHIMAGE") | safe }}"
+command = "polkadot"
+chain-name = "polkadot-local"
+timeout = 1000
+
+[nodes.alice]
+validator = true
+image = "{{get_env(name="SYNTHIMAGE") | safe }}"
+command = "polkadot"
+extra-args = ["--alice"]
+
+[nodes.bob]
+validator = true
+image = "{{get_env(name="MALUSIMAGE") | safe }}"
+command = "/usr/local/bin/malus"
+extra-args = ["back-garbage-candidate", "--bob"]
+
+[nodes.charlie]
+validator = true
+image = "{{get_env(name="MALUSIMAGE") | safe }}"
+command = "/usr/local/bin/malus"
+extra-args = ["back-garbage-candidate", "--charlie"]
+
+[nodes.david]
+validator = true
+image = "{{get_env(name="MALUSIMAGE") | safe }}"
+command = "/usr/local/bin/malus"
+extra-args = ["back-garbage-candidate", "--dave"]
+
+[nodes.eve]
+validator = true
+image = "{{get_env(name="MALUSIMAGE") | safe }}"
+command = "/usr/local/bin/malus"
+extra-args = ["suggest-garbage-candidate","--eve"]
diff --git a/node/malus/src/malus.rs b/node/malus/src/malus.rs
index 4961bd3e1e65..9809855dc9d2 100644
--- a/node/malus/src/malus.rs
+++ b/node/malus/src/malus.rs
@@ -38,6 +38,8 @@ enum NemesisVariant {
BackGarbageCandidate(RunCmd),
/// Delayed disputing of ancestors that are perfectly fine.
DisputeAncestor(RunCmd),
+ /// Instant disputing of a block that was never made available.
+ DisputeUnavailable(RunCmd),
}
#[derive(Debug, StructOpt)]
@@ -56,6 +58,7 @@ impl MalusCli {
NemesisVariant::SuggestGarabageCandidate(run) =>
polkadot_cli::run_node(run, SuggestGarbageCandidate)?,
NemesisVariant::DisputeAncestor(run) => polkadot_cli::run_node(run, DisputeAncestor)?,
+ NemesisVariant::DisputeUnavailable(run) => polkadot_cli::run_node(run, DisputeUnavailable)?,
}
Ok(())
}
diff --git a/node/malus/src/variants/dispute_unavailable_block.rs b/node/malus/src/variants/dispute_unavailable_block.rs
new file mode 100644
index 000000000000..7fcf410cf0b6
--- /dev/null
+++ b/node/malus/src/variants/dispute_unavailable_block.rs
@@ -0,0 +1,164 @@
+// 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 .
+
+//! A malicious overseer that always disputes a block as
+//! as it is observed.
+//!
+//! Attention: For usage with `simnet`/`gurke` only!
+
+#![allow(missing_docs)]
+
+use polkadot_cli::{
+ create_default_subsystems,
+ service::{
+ AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer,
+ OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost, ProvideRuntimeApi, SpawnNamed,
+ },
+};
+
+// Filter wrapping related types.
+use crate::{interceptor::*, shared::*};
+use polkadot_node_subsystem::overseer::SubsystemSender;
+
+// Import extra types relevant to the particular
+// subsystem.
+use polkadot_node_core_backing::{CandidateBackingSubsystem, Metrics as CandidateBackingMetrics};
+use polkadot_node_subsystem::messages::{CandidateBackingMessage, DisputeCoordinatorMessage};
+use polkadot_node_subsystem_util as util;
+use polkadot_primitives::v1::CandidateReceipt;
+use sp_keystore::SyncCryptoStorePtr;
+use util::{metered, metrics::Metrics as _};
+
+use std::sync::Arc;
+
+/// Become Loki and throw in a dispute once in a while, for an unfinalized block.
+#[derive(Clone, Debug)]
+struct TrackCollations
+where
+ Sender: Send,
+{
+ sink: metered::UnboundedMeteredSender<(Sender, CandidateReceipt)>,
+}
+
+impl MessageInterceptor for TrackCollations
+where
+ Sender: overseer::SubsystemSender + Clone + Send + 'static,
+{
+ type Message = CandidateBackingMessage;
+
+ fn intercept_incoming(
+ &self,
+ sender: &mut Sender,
+ msg: FromOverseer,
+ ) -> Option> {
+ match msg {
+ FromOverseer::Communication {
+ // `DistributeCollation` is only received
+ // by a _collator_, but we are a validator.
+ // `CollatorProtocolMessage::DistributeCollation(ref ccr, ref pov, _)` hence
+ // cannot be used.
+
+ // Intercepting [`fn request_collation`](https://github.com/paritytech/polkadot/blob/117466aa8e471562f921a90b69a6c265cb6c656f/node/network/collator-protocol/src/validator_side/mod.rs#L736-L736)
+ // is bothersome, so we wait for the seconding and
+ // make that disappear, and instead craft our own message.
+ msg: CandidateBackingMessage::Second(_, ccr, _),
+ } => {
+ self.sink.unbounded_send((sender.clone(), ccr)).unwrap();
+ None
+ },
+ msg => Some(msg),
+ }
+ }
+
+ fn intercept_outgoing(&self, msg: AllMessages) -> Option {
+ Some(msg)
+ }
+}
+
+/// Generates an overseer that disputes every ancestor.
+pub(crate) struct DisputeUnavailable;
+
+impl OverseerGen for DisputeUnavailable {
+ fn generate<'a, Spawner, RuntimeClient>(
+ &self,
+ args: OverseerGenArgs<'a, Spawner, RuntimeClient>,
+ ) -> Result<(Overseer>, OverseerHandle), Error>
+ where
+ RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore,
+ RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi,
+ Spawner: 'static + SpawnNamed + Clone + Unpin,
+ {
+ let spawner = args.spawner.clone();
+ let leaves = args.leaves.clone();
+ let runtime_client = args.runtime_client.clone();
+ let registry = args.registry.clone();
+
+ let (sink, source) = metered::unbounded();
+
+ let coll = TrackCollations { sink };
+
+ let metrics = CandidateBackingMetrics::register(registry).unwrap();
+
+ let crypto_store_ptr = args.keystore.clone() as SyncCryptoStorePtr;
+
+ // modify the subsystem(s) as needed:
+ let all_subsystems = create_default_subsystems(args)?.replace_candidate_backing(
+ // create the filtered subsystem
+ FilteredSubsystem::new(
+ CandidateBackingSubsystem::new(spawner.clone(), crypto_store_ptr, metrics),
+ coll,
+ ),
+ );
+
+ let (overseer, handle) =
+ Overseer::new(leaves, all_subsystems, registry, runtime_client, spawner.clone())?;
+
+ launch_processing_task(
+ &spawner,
+ source,
+ |(mut subsystem_sender, candidate_receipt): (_, CandidateReceipt)| async move {
+ let relay_parent = candidate_receipt.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 candidate_hash = candidate_receipt.hash();
+
+ tracing::warn!(
+ target = MALUS,
+ "Disputing unvailable block with candidate /w hash {} in session {:?} on relay_parent {}",
+ candidate_hash,
+ session_index,
+ relay_parent,
+ );
+
+ // no delay, dispute right away, before it becomes available
+
+ // 😈
+ let msg = DisputeCoordinatorMessage::IssueLocalStatement(
+ session_index,
+ candidate_hash,
+ candidate_receipt,
+ false,
+ );
+
+ subsystem_sender.send_message(msg).await;
+ },
+ );
+
+ Ok((overseer, handle))
+ }
+}
diff --git a/node/malus/src/variants/mod.rs b/node/malus/src/variants/mod.rs
index 27262b68815a..d2ccfc5004e9 100644
--- a/node/malus/src/variants/mod.rs
+++ b/node/malus/src/variants/mod.rs
@@ -19,8 +19,10 @@
mod back_garbage_candidate;
mod dispute_ancestor;
mod suggest_garbage_candidate;
+mod dispute_unavailable_block;
pub(crate) use self::{
back_garbage_candidate::BackGarbageCandidate, dispute_ancestor::DisputeAncestor,
suggest_garbage_candidate::SuggestGarbageCandidate,
+ dispute_unavailable_block::DisputeUnavailable,
};