From 139ecf9028acffeca8e060eb63dc8afc276d638f Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 31 Aug 2021 13:49:30 +0200 Subject: [PATCH] fix order, add dispute-unavailable-block malus --- .../0001-dispute-valid-block.toml | 6 +- .../0002-dispute-invalid-block.toml | 10 +- .../0003-dispute-unavailable-block.feature | 24 +++ .../0003-dispute-unavailable-block.toml | 35 ++++ node/malus/src/malus.rs | 3 + .../src/variants/dispute_unavailable_block.rs | 164 ++++++++++++++++++ node/malus/src/variants/mod.rs | 2 + 7 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 node/malus/integrationtests/0003-dispute-unavailable-block.feature create mode 100644 node/malus/integrationtests/0003-dispute-unavailable-block.toml create mode 100644 node/malus/src/variants/dispute_unavailable_block.rs 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, };