From b3532393b8f6ffda28e0579c2ab07eea84e2d572 Mon Sep 17 00:00:00 2001
From: "Mattia L.V. Bradascio" <28816406+bredamatt@users.noreply.github.com>
Date: Thu, 13 Oct 2022 10:48:48 +0100
Subject: [PATCH] Malus: add disputed block percentage (#6100)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Malus: add disputed block percentage

* Bump clap to support value_parser with range

* Add rand crate and use Bernoulli and Distribution

* Add conditional logic based on sampled value from Bernoulli distribution

* Add SuggestGarbageCandidateOptions struct

* Cleanup tests

* * Replace unwrap with expect and meaningful error message

* * Remove Inner
* Remove intercept_outgoing

* * Rename sampled variable
* Move info! logs to include candidate hash of malicious candidate

* * Add percentage option to dispute_ancestor

* * Support static probability for `ReplaceValidationResult` proxy
* Update some comments and docs

* * Add `--percentage` to `back-garbage-candidate` variant
* Rename structs for consistency

* * Add probabilistic behavior to `dispute-ancestor` variant
* Add probabilistic behavior to `back-garbage-candidate` variant
* Rename structs in dispute variant

* * More descriptive comments

* * cargo +nightly fmt --all

* * Move Bernoulli distributrion to ReplaceValidationResult constructor

* Rename random_bool to behave_maliciously

* * Remove dangling comment

* * Consistent log

* * Add logs based on sampled value

* * Cargo +nightly fmt --all

* * Remove unused percentage attributed after moving Bernoulli to constructor

* Squashed commit of the following:

commit e4361b6d80d64e65d74a8e31a3d1cdc1948ee8a4
Author: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Date:   Mon Oct 10 10:06:44 2022 +0400

    Fix flaky test (#6131)

    * Split test + decrease test timeout

    * fmt

    * spellcheck

commit f614752c22270c3d948aec08a234637e1834269c
Author: girazoki <gorka.irazoki@gmail.com>
Date:   Mon Oct 10 06:39:30 2022 +0200

    Add event to asset claim (#6029)

commit 71197818a4c40c7fdd79e233f8608e8f513e3bfc
Author: Leszek Wiesner <leszek@jsgenesis.com>
Date:   Mon Oct 10 00:23:54 2022 +0200

    Companion for 12109 (#5929)

    * Update following `pallet-vesting` configurable `WithdrawReasons`

    * Update lib.rs

    * Update lib.rs

    * Update lib.rs

    * update lockfile for {"substrate"}

    * fix warning

    Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
    Co-authored-by: parity-processbot <>

commit 607350449c553cd6e17c4e0bca2b553cd8648ca6
Author: Bastian Köcher <info@kchr.de>
Date:   Fri Oct 7 13:40:40 2022 +0200

    Companion for upgrading pin-project (#6118)

    * Companion for upgrading pin-project

    This will remove some warnings with the latest rustc nightly/stable.

    https://github.com/paritytech/substrate/pull/12426

    * update lockfile for {"substrate"}

    Co-authored-by: parity-processbot <>

commit c8151aed3c72293a7d93bec140b376a36658cfcb
Author: Sergej Sakac <73715684+Szegoo@users.noreply.github.com>
Date:   Thu Oct 6 19:20:58 2022 +0200

    Maximum value for `MultiplierUpdate` (#6021)

    * update multiplier

    * fix

    * update lockfile for {"substrate"}

    * fmt

    * fix typo

    Co-authored-by: parity-processbot <>

commit 8d1c16dc0d899e4e47bdcc4d4d9658c0628f37d7
Author: Adrian Catangiu <adrian@parity.io>
Date:   Thu Oct 6 12:58:39 2022 +0300

    service: use MmrRootProvider as custom BEEFY payload provider (companion for 12428) (#6112)

    * service: use MmrRootProvider as custom BEEFY payload provider

    * update lockfile for {"substrate"}

    Co-authored-by: parity-processbot <>

commit 910e21847f4d75efbd978c860f3f1b8c946ea5fa
Author: Branislav Kontur <bkontur@gmail.com>
Date:   Thu Oct 6 10:03:34 2022 +0200

    Skip `unexpected metric type`

    * Dump more info for `unexpected metric type`

    * Skip `unexpected metric type`

commit af6a5cd96ada80c574346f69f2720bf9a6a556a4
Author: Andronik <write@reusable.software>
Date:   Thu Oct 6 00:36:51 2022 +0200

    update kvdb & co (#6111)

    * toml changes

    * REVERTME: patch

    * adapt parachains db interface

    * fix Cargo.toml patch after master rebase

    * fix av-store

    * fix chain-selection

    * fix parachains-db?

    * Revert "fix Cargo.toml patch after master rebase"

    This reverts commit 3afcbf033c86027b3f2b909d83ec703591bdd287.

    * Revert "REVERTME: patch"

    This reverts commit 464b717cf4142d3d09c3d77b83700b632d8c5f54.

    * Use `Ok` imported from prelude

    Co-authored-by: Bastian Köcher <info@kchr.de>

    * update lockfile for {"substrate"}

    * Revert "update lockfile for {"substrate"}"

    This reverts commit fdc623de226f7645741b86c4b1a7d030fed2172d.

    * cargo update -p sp-io

    Co-authored-by: Bastian Köcher <info@kchr.de>
    Co-authored-by: parity-processbot <>

commit 9a3cf4cd1f3c68b620b8a2ad4c3c22418b9b83a7
Author: Gavin Wood <gavin@parity.io>
Date:   Wed Oct 5 22:17:59 2022 +0100

    Companion for #11649: Bound uses of `Call` (#5729)

    * Fixes

    * Clear out old weights

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Resolve merges

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Fix weight traits

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * polkadot runtime: Clippy

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * rococo runtime: update pallet configs

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Add preimage migration

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Add all migrations

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Democracy is not on Westend

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * [Migration] Refund stored multisig calls (#6075)

    * Add Preimages to referenda config

    Needed since Gov V2 just merged.

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Update weights

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Add multisig migration to Westend+Rococo

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Fix Executive syntax

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Bump Substrate

    Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
    Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
    Co-authored-by: parity-processbot <>
    Co-authored-by: Roman Useinov <roman.useinov@gmail.com>

commit 4aea71a95f6b67a640057a6f44e85415a605582f
Author: Alexander Theißen <alex.theissen@me.com>
Date:   Wed Oct 5 15:15:07 2022 +0200

    Pass through `runtime-benchmark` feature (#6110)

commit 42c043d7f4e2ae5dbeb162c4eb7d4df16dde6910
Author: Keith Yeung <kungfukeith11@gmail.com>
Date:   Wed Oct 5 17:47:15 2022 +0800

    Properly migrate weights to v2 (#6091)

    * Create migration for config pallet

    * Use XcmWeight in XCM pallet extrinsics

    * Link to PR in doc comment

    * cargo fmt

    * Fix tests

    * Fix tests

    * Remove unused import

    * Update runtime/parachains/src/configuration/migration.rs

    Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

    * Add missing on_runtime_upgrade implementation

    * Use new migration API

    * cargo fmt

    * Fix log message

    Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

commit b13e07bc47073c2972b1d1d82321cb359803874c
Author: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Date:   Wed Oct 5 11:48:50 2022 +0400

    Buffered connection management for collator-protocol (#6022)

    * Extract metrics into a separate module

    * Introduce validators buffer

    * Integrate buffer into the subsystem

    * Only reconnect on new advertisements

    * Test

    * comma

    * doc comment

    * Make capacity buffer compile time non-zero

    * Add doc comments

    * nits

    * remove derives

    * review

    * better naming

    * check timeout

    * Extract interval stream into lib

    * Ensure collator disconnects after timeout

    * spellcheck

    * rename buf

    * Remove double interval

    * Add a log on timeout

    * Cleanup buffer on timeout

commit e0e836671f068bde4c3f356ab1dede8ca51e7b50
Author: Robert Klotzner <eskimor@users.noreply.github.com>
Date:   Tue Oct 4 18:47:52 2022 +0200

    Add unknown words (#6105)

commit 938bc96a2cfd881ad309f51e16294ed87813b998
Author: Robert Klotzner <eskimor@users.noreply.github.com>
Date:   Tue Oct 4 18:02:05 2022 +0200

    Batch vote import in dispute-distribution (#5894)

    * Start work on batching in dispute-distribution.

    * Guide work.

    * More guide changes. Still very much WIP.

    * Finish guide changes.

    * Clarification

    * Adjust argument about slashing.

    * WIP: Add constants to receiver.

    * Maintain order of disputes.

    * dispute-distribuion sender Rate limit.

    * Cleanup

    * WIP: dispute-distribution receiver.

    - [ ] Rate limiting
    - [ ] Batching

    * WIP: Batching.

    * fmt

    * Update `PeerQueues` to maintain more invariants.

    * WIP: Batching.

    * Small cleanup

    * Batching logic.

    * Some integration work.

    * Finish.

    Missing: Tests

    * Typo.

    * Docs.

    * Report missing metric.

    * Doc pass.

    * Tests for waiting_queue.

    * Speed up some crypto by 10x.

    * Fix redundant import.

    * Add some tracing.

    * Better sender rate limit

    * Some tests.

    * Tests

    * Add logging to rate limiter

    * Update roadmap/implementers-guide/src/node/disputes/dispute-distribution.md

    Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

    * Update roadmap/implementers-guide/src/node/disputes/dispute-distribution.md

    Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

    * Update node/network/dispute-distribution/src/receiver/mod.rs

    Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

    * Review feedback.

    * Also log peer in log messages.

    * Fix indentation.

    * waker -> timer

    * Guide improvement.

    * Remove obsolete comment.

    * waker -> timer

    * Fix spell complaints.

    * Fix Cargo.lock

    Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

commit a64cc4a8600dd7cf7839c170b5f795dc8a21a8ec
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Tue Oct 4 11:28:21 2022 +0000

    Bump lru from 0.7.8 to 0.8.0 (#6060)

    * Bump lru from 0.7.8 to 0.8.0

    Bumps [lru](https://github.com/jeromefroe/lru-rs) from 0.7.8 to 0.8.0.
    - [Release notes](https://github.com/jeromefroe/lru-rs/releases)
    - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.7.8...0.8.0)

    ---
    updated-dependencies:
    - dependency-name: lru
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...

    Signed-off-by: dependabot[bot] <support@github.com>

    * Change `LruCache` paramerter to `NonZeroUsize`

    * Change type of `session_cache_lru_size` to `NonZeroUsize`

    * Add expects instead of unwrap

    Co-authored-by: Bastian Köcher <info@kchr.de>

    * Use match to get rid of expects

    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
    Co-authored-by: Bastian Köcher <info@kchr.de>

commit 7114a8cfcac07025db9815ef6819d4e2ddd5e9da
Author: Andrei Sandu <54316454+sandreim@users.noreply.github.com>
Date:   Tue Oct 4 13:36:42 2022 +0300

    Keep sessions in window for the full unfinalized chain (#6054)

    * Impl dynamic window size. Keep sessions for unfinalized chain

    Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

    * feedback

    Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

    * Stretch also in contructor plus  tests

    Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

    * review feedback

    Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

    * fix approval-voting tests

    Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

    * grunting: dispute coordinator tests

    Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

    Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

commit ab8f04f827b56ef1da8e0766b242c766cb0725b6
Author: Serban Iorga <serban@parity.io>
Date:   Tue Oct 4 12:25:48 2022 +0300

    Companion for BEEFY: Simplify hashing for pallet-beefy-mmr (#6098)

    * beefy-mmr: Simplify hashing

    * update lockfile for {"substrate"}

    Co-authored-by: parity-processbot <>

* Revert "Squashed commit of the following:"

This reverts commit 5001fa5d1dcd366029d156f81c40b99ca29d8f77.

* Companion for BEEFY: Simplify hashing for pallet-beefy-mmr (#6098)

* beefy-mmr: Simplify hashing

* update lockfile for {"substrate"}

Co-authored-by: parity-processbot <>

* Keep sessions in window for the full unfinalized chain (#6054)

* Impl dynamic window size. Keep sessions for unfinalized chain

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

* feedback

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

* Stretch also in contructor plus  tests

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

* review feedback

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

* fix approval-voting tests

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

* grunting: dispute coordinator tests

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

* Bump lru from 0.7.8 to 0.8.0 (#6060)

* Bump lru from 0.7.8 to 0.8.0

Bumps [lru](https://github.com/jeromefroe/lru-rs) from 0.7.8 to 0.8.0.
- [Release notes](https://github.com/jeromefroe/lru-rs/releases)
- [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jeromefroe/lru-rs/compare/0.7.8...0.8.0)

---
updated-dependencies:
- dependency-name: lru
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Change `LruCache` paramerter to `NonZeroUsize`

* Change type of `session_cache_lru_size` to `NonZeroUsize`

* Add expects instead of unwrap

Co-authored-by: Bastian Köcher <info@kchr.de>

* Use match to get rid of expects

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
Co-authored-by: Bastian Köcher <info@kchr.de>

* Batch vote import in dispute-distribution (#5894)

* Start work on batching in dispute-distribution.

* Guide work.

* More guide changes. Still very much WIP.

* Finish guide changes.

* Clarification

* Adjust argument about slashing.

* WIP: Add constants to receiver.

* Maintain order of disputes.

* dispute-distribuion sender Rate limit.

* Cleanup

* WIP: dispute-distribution receiver.

- [ ] Rate limiting
- [ ] Batching

* WIP: Batching.

* fmt

* Update `PeerQueues` to maintain more invariants.

* WIP: Batching.

* Small cleanup

* Batching logic.

* Some integration work.

* Finish.

Missing: Tests

* Typo.

* Docs.

* Report missing metric.

* Doc pass.

* Tests for waiting_queue.

* Speed up some crypto by 10x.

* Fix redundant import.

* Add some tracing.

* Better sender rate limit

* Some tests.

* Tests

* Add logging to rate limiter

* Update roadmap/implementers-guide/src/node/disputes/dispute-distribution.md

Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

* Update roadmap/implementers-guide/src/node/disputes/dispute-distribution.md

Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

* Update node/network/dispute-distribution/src/receiver/mod.rs

Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

* Review feedback.

* Also log peer in log messages.

* Fix indentation.

* waker -> timer

* Guide improvement.

* Remove obsolete comment.

* waker -> timer

* Fix spell complaints.

* Fix Cargo.lock

Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>

* Add unknown words (#6105)

* Buffered connection management for collator-protocol (#6022)

* Extract metrics into a separate module

* Introduce validators buffer

* Integrate buffer into the subsystem

* Only reconnect on new advertisements

* Test

* comma

* doc comment

* Make capacity buffer compile time non-zero

* Add doc comments

* nits

* remove derives

* review

* better naming

* check timeout

* Extract interval stream into lib

* Ensure collator disconnects after timeout

* spellcheck

* rename buf

* Remove double interval

* Add a log on timeout

* Cleanup buffer on timeout

* Properly migrate weights to v2 (#6091)

* Create migration for config pallet

* Use XcmWeight in XCM pallet extrinsics

* Link to PR in doc comment

* cargo fmt

* Fix tests

* Fix tests

* Remove unused import

* Update runtime/parachains/src/configuration/migration.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add missing on_runtime_upgrade implementation

* Use new migration API

* cargo fmt

* Fix log message

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Pass through `runtime-benchmark` feature (#6110)

* Companion for #11649: Bound uses of `Call` (#5729)

* Fixes

* Clear out old weights

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Resolve merges

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix weight traits

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* polkadot runtime: Clippy

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* rococo runtime: update pallet configs

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add preimage migration

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add all migrations

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Democracy is not on Westend

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* [Migration] Refund stored multisig calls (#6075)

* Add Preimages to referenda config

Needed since Gov V2 just merged.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update weights

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add multisig migration to Westend+Rococo

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix Executive syntax

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Bump Substrate

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: parity-processbot <>
Co-authored-by: Roman Useinov <roman.useinov@gmail.com>

* update kvdb & co (#6111)

* toml changes

* REVERTME: patch

* adapt parachains db interface

* fix Cargo.toml patch after master rebase

* fix av-store

* fix chain-selection

* fix parachains-db?

* Revert "fix Cargo.toml patch after master rebase"

This reverts commit 3afcbf033c86027b3f2b909d83ec703591bdd287.

* Revert "REVERTME: patch"

This reverts commit 464b717cf4142d3d09c3d77b83700b632d8c5f54.

* Use `Ok` imported from prelude

Co-authored-by: Bastian Köcher <info@kchr.de>

* update lockfile for {"substrate"}

* Revert "update lockfile for {"substrate"}"

This reverts commit fdc623de226f7645741b86c4b1a7d030fed2172d.

* cargo update -p sp-io

Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: parity-processbot <>

* Skip `unexpected metric type`

* Dump more info for `unexpected metric type`

* Skip `unexpected metric type`

* service: use MmrRootProvider as custom BEEFY payload provider (companion for 12428) (#6112)

* service: use MmrRootProvider as custom BEEFY payload provider

* update lockfile for {"substrate"}

Co-authored-by: parity-processbot <>

* Maximum value for `MultiplierUpdate` (#6021)

* update multiplier

* fix

* update lockfile for {"substrate"}

* fmt

* fix typo

Co-authored-by: parity-processbot <>

* Companion for upgrading pin-project (#6118)

* Companion for upgrading pin-project

This will remove some warnings with the latest rustc nightly/stable.

https://github.com/paritytech/substrate/pull/12426

* update lockfile for {"substrate"}

Co-authored-by: parity-processbot <>

* Companion for 12109 (#5929)

* Update following `pallet-vesting` configurable `WithdrawReasons`

* Update lib.rs

* Update lib.rs

* Update lib.rs

* update lockfile for {"substrate"}

* fix warning

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: parity-processbot <>

* Add event to asset claim (#6029)

* Fix flaky test (#6131)

* Split test + decrease test timeout

* fmt

* spellcheck

* ci/guide: install mdbook-graphviz (#6119)

* ci/guide: install mdbook-graphviz

* install graphviz in build-implementers-guide

* Update scripts/ci/gitlab/pipeline/build.yml

Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com>

* Revert "Squashed commit of the following:"

This reverts commit 5001fa5d1dcd366029d156f81c40b99ca29d8f77.

* * Remove unused imports

* * cargo +nightly fmt --all

* Make tweaks based on PR comments

* unit test related to gum formatting

* cargo +nightly fmt --all

* Resolve merge conflicts

* cargo +nightly fmt --all

* Fix tests so they use cli rather than cmd

* CI unused import check fix

* Move info! log to startup

* make info log more comprehensible

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Serban Iorga <serban@parity.io>
Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: Robert Klotzner <eskimor@users.noreply.github.com>
Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Roman Useinov <roman.useinov@gmail.com>
Co-authored-by: Andronik <write@reusable.software>
Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: Adrian Catangiu <adrian@parity.io>
Co-authored-by: Sergej Sakac <73715684+Szegoo@users.noreply.github.com>
Co-authored-by: Leszek Wiesner <leszek@jsgenesis.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: girazoki <gorka.irazoki@gmail.com>
Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com>
---
 polkadot/Cargo.lock                           |  19 +-
 polkadot/node/malus/Cargo.toml                |   3 +-
 polkadot/node/malus/src/malus.rs              | 105 +++++-
 .../src/variants/back_garbage_candidate.rs    |  22 +-
 polkadot/node/malus/src/variants/common.rs    | 177 ++++++++--
 .../src/variants/dispute_valid_candidates.rs  |   8 +
 polkadot/node/malus/src/variants/mod.rs       |   4 +-
 .../src/variants/suggest_garbage_candidate.rs | 323 +++++++++---------
 8 files changed, 445 insertions(+), 216 deletions(-)

diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock
index 278310281f5..ad6176d1acc 100644
--- a/polkadot/Cargo.lock
+++ b/polkadot/Cargo.lock
@@ -922,16 +922,16 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "3.1.18"
+version = "3.2.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
+checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
 dependencies = [
  "atty",
  "bitflags",
  "clap_derive",
  "clap_lex",
  "indexmap",
- "lazy_static",
+ "once_cell",
  "strsim",
  "termcolor",
  "textwrap",
@@ -939,9 +939,9 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "3.1.18"
+version = "3.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
+checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
 dependencies = [
  "heck",
  "proc-macro-error",
@@ -952,9 +952,9 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.2.0"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
 dependencies = [
  "os_str_bytes",
 ]
@@ -7470,6 +7470,7 @@ dependencies = [
  "polkadot-node-subsystem-types",
  "polkadot-node-subsystem-util",
  "polkadot-primitives",
+ "rand 0.8.5",
  "sp-core",
  "sp-keystore",
  "tracing-gum",
@@ -11391,9 +11392,9 @@ dependencies = [
 
 [[package]]
 name = "textwrap"
-version = "0.15.0"
+version = "0.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
 
 [[package]]
 name = "thiserror"
diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml
index c32fce56e4c..9548857a03e 100644
--- a/polkadot/node/malus/Cargo.toml
+++ b/polkadot/node/malus/Cargo.toml
@@ -29,11 +29,12 @@ assert_matches = "1.5"
 async-trait = "0.1.57"
 sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
 sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
-clap = { version = "3.1", features = ["derive"] }
+clap = { version = "3.2.21", features = ["derive"] }
 futures = "0.3.21"
 futures-timer = "3.0.2"
 gum = { package = "tracing-gum", path = "../gum/" }
 erasure = { package = "polkadot-erasure-coding", path = "../../erasure-coding" }
+rand = "0.8.5"
 
 [features]
 default = []
diff --git a/polkadot/node/malus/src/malus.rs b/polkadot/node/malus/src/malus.rs
index aa14b8e3d38..13e232198ea 100644
--- a/polkadot/node/malus/src/malus.rs
+++ b/polkadot/node/malus/src/malus.rs
@@ -18,7 +18,6 @@
 
 use clap::Parser;
 use color_eyre::eyre;
-use polkadot_cli::Cli;
 
 pub(crate) mod interceptor;
 pub(crate) mod shared;
@@ -33,9 +32,9 @@ use variants::*;
 #[clap(rename_all = "kebab-case")]
 enum NemesisVariant {
 	/// Suggest a candidate with an invalid proof of validity.
-	SuggestGarbageCandidate(Cli),
+	SuggestGarbageCandidate(SuggestGarbageCandidateOptions),
 	/// Back a candidate with a specifically crafted proof of validity.
-	BackGarbageCandidate(Cli),
+	BackGarbageCandidate(BackGarbageCandidateOptions),
 	/// Delayed disputing of ancestors that are perfectly fine.
 	DisputeAncestor(DisputeAncestorOptions),
 
@@ -62,16 +61,31 @@ impl MalusCli {
 	fn launch(self) -> eyre::Result<()> {
 		let finality_delay = self.finality_delay;
 		match self.variant {
-			NemesisVariant::BackGarbageCandidate(cli) =>
-				polkadot_cli::run_node(cli, BackGarbageCandidate, finality_delay)?,
-			NemesisVariant::SuggestGarbageCandidate(cli) =>
-				polkadot_cli::run_node(cli, BackGarbageCandidateWrapper, finality_delay)?,
+			NemesisVariant::BackGarbageCandidate(opts) => {
+				let BackGarbageCandidateOptions { percentage, cli } = opts;
+
+				polkadot_cli::run_node(cli, BackGarbageCandidates { percentage }, finality_delay)?
+			},
+			NemesisVariant::SuggestGarbageCandidate(opts) => {
+				let SuggestGarbageCandidateOptions { percentage, cli } = opts;
+
+				polkadot_cli::run_node(
+					cli,
+					SuggestGarbageCandidates { percentage },
+					finality_delay,
+				)?
+			},
 			NemesisVariant::DisputeAncestor(opts) => {
-				let DisputeAncestorOptions { fake_validation, fake_validation_error, cli } = opts;
+				let DisputeAncestorOptions {
+					fake_validation,
+					fake_validation_error,
+					percentage,
+					cli,
+				} = opts;
 
 				polkadot_cli::run_node(
 					cli,
-					DisputeValidCandidates { fake_validation, fake_validation_error },
+					DisputeValidCandidates { fake_validation, fake_validation_error, percentage },
 					finality_delay,
 				)?
 			},
@@ -129,4 +143,77 @@ mod tests {
 			assert!(run.cli.run.base.bob);
 		});
 	}
+
+	#[test]
+	fn percentage_works_suggest_garbage() {
+		let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
+			"malus",
+			"suggest-garbage-candidate",
+			"--percentage",
+			"100",
+			"--bob",
+		]))
+		.unwrap();
+		assert_matches::assert_matches!(cli, MalusCli {
+			variant: NemesisVariant::SuggestGarbageCandidate(run),
+			..
+		} => {
+			assert!(run.cli.run.base.bob);
+		});
+	}
+
+	#[test]
+	fn percentage_works_dispute_ancestor() {
+		let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
+			"malus",
+			"dispute-ancestor",
+			"--percentage",
+			"100",
+			"--bob",
+		]))
+		.unwrap();
+		assert_matches::assert_matches!(cli, MalusCli {
+			variant: NemesisVariant::DisputeAncestor(run),
+			..
+		} => {
+			assert!(run.cli.run.base.bob);
+		});
+	}
+
+	#[test]
+	fn percentage_works_back_garbage() {
+		let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
+			"malus",
+			"back-garbage-candidate",
+			"--percentage",
+			"100",
+			"--bob",
+		]))
+		.unwrap();
+		assert_matches::assert_matches!(cli, MalusCli {
+			variant: NemesisVariant::BackGarbageCandidate(run),
+			..
+		} => {
+			assert!(run.cli.run.base.bob);
+		});
+	}
+
+	#[test]
+	#[should_panic]
+	fn validate_range_for_percentage() {
+		let cli = MalusCli::try_parse_from(IntoIterator::into_iter([
+			"malus",
+			"suggest-garbage-candidate",
+			"--percentage",
+			"101",
+			"--bob",
+		]))
+		.unwrap();
+		assert_matches::assert_matches!(cli, MalusCli {
+			variant: NemesisVariant::DisputeAncestor(run),
+			..
+		} => {
+			assert!(run.cli.run.base.bob);
+		});
+	}
 }
diff --git a/polkadot/node/malus/src/variants/back_garbage_candidate.rs b/polkadot/node/malus/src/variants/back_garbage_candidate.rs
index cf72776b5f2..b17b8bca588 100644
--- a/polkadot/node/malus/src/variants/back_garbage_candidate.rs
+++ b/polkadot/node/malus/src/variants/back_garbage_candidate.rs
@@ -25,6 +25,7 @@ use polkadot_cli::{
 		OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
 		ProvideRuntimeApi,
 	},
+	Cli,
 };
 use polkadot_node_subsystem::SpawnGlue;
 use sp_core::traits::SpawnNamed;
@@ -36,11 +37,27 @@ use crate::{
 
 use std::sync::Arc;
 
+#[derive(Debug, clap::Parser)]
+#[clap(rename_all = "kebab-case")]
+#[allow(missing_docs)]
+pub struct BackGarbageCandidateOptions {
+	/// Determines the percentage of garbage candidates that should be backed.
+	/// Defaults to 100% of garbage candidates being backed.
+	#[clap(short, long, ignore_case = true, default_value_t = 100, value_parser = clap::value_parser!(u8).range(0..=100))]
+	pub percentage: u8,
+
+	#[clap(flatten)]
+	pub cli: Cli,
+}
+
 /// Generates an overseer that replaces the candidate validation subsystem with our malicious
 /// variant.
-pub(crate) struct BackGarbageCandidate;
+pub(crate) struct BackGarbageCandidates {
+	/// The probability of behaving maliciously.
+	pub percentage: u8,
+}
 
-impl OverseerGen for BackGarbageCandidate {
+impl OverseerGen for BackGarbageCandidates {
 	fn generate<'a, Spawner, RuntimeClient>(
 		&self,
 		connector: OverseerConnector,
@@ -55,6 +72,7 @@ impl OverseerGen for BackGarbageCandidate {
 		let validation_filter = ReplaceValidationResult::new(
 			FakeCandidateValidation::BackingAndApprovalValid,
 			FakeCandidateValidationError::InvalidOutputs,
+			f64::from(self.percentage),
 			SpawnGlue(spawner),
 		);
 
diff --git a/polkadot/node/malus/src/variants/common.rs b/polkadot/node/malus/src/variants/common.rs
index e112aa49f83..845dac0b6fe 100644
--- a/polkadot/node/malus/src/variants/common.rs
+++ b/polkadot/node/malus/src/variants/common.rs
@@ -34,6 +34,8 @@ use polkadot_primitives::v2::{
 
 use futures::channel::oneshot;
 
+use rand::distributions::{Bernoulli, Distribution};
+
 #[derive(clap::ArgEnum, Clone, Copy, Debug, PartialEq)]
 #[clap(rename_all = "kebab-case")]
 #[non_exhaustive]
@@ -109,6 +111,7 @@ impl Into<InvalidCandidate> for FakeCandidateValidationError {
 pub struct ReplaceValidationResult<Spawner> {
 	fake_validation: FakeCandidateValidation,
 	fake_validation_error: FakeCandidateValidationError,
+	distribution: Bernoulli,
 	spawner: Spawner,
 }
 
@@ -119,9 +122,12 @@ where
 	pub fn new(
 		fake_validation: FakeCandidateValidation,
 		fake_validation_error: FakeCandidateValidationError,
+		percentage: f64,
 		spawner: Spawner,
 	) -> Self {
-		Self { fake_validation, fake_validation_error, spawner }
+		let distribution = Bernoulli::new(percentage / 100.0)
+			.expect("Invalid probability! Percentage must be in range [0..=100].");
+		Self { fake_validation, fake_validation_error, distribution, spawner }
 	}
 
 	/// Creates and sends the validation response for a given candidate. Queries the runtime to obtain the validation data for the
@@ -202,13 +208,14 @@ where
 {
 	type Message = CandidateValidationMessage;
 
-	// Capture all candidate validation requests and depending on configuration fail them.
+	// Capture all (approval and backing) candidate validation requests and depending on configuration fail them.
 	fn intercept_incoming(
 		&self,
 		subsystem_sender: &mut Sender,
 		msg: FromOrchestra<Self::Message>,
 	) -> Option<FromOrchestra<Self::Message>> {
 		match msg {
+			// Message sent by the approval voting subsystem
 			FromOrchestra::Communication {
 				msg:
 					CandidateValidationMessage::ValidateFromExhaustive(
@@ -236,28 +243,84 @@ where
 								),
 							})
 						}
-						create_validation_response(
-							validation_data,
-							candidate_receipt.descriptor,
-							sender,
-						);
-						None
+						// Create the fake response with probability `p` if the `PoV` is malicious,
+						// where 'p' defaults to 100% for suggest-garbage-candidate variant.
+						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,
+									sender,
+								);
+								None
+							},
+							false => {
+								// Behave normally with probability `(1-p)` for a malicious `PoV`.
+								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,
+										timeout,
+										sender,
+									),
+								})
+							},
+						}
 					},
 					FakeCandidateValidation::ApprovalInvalid |
 					FakeCandidateValidation::BackingAndApprovalInvalid => {
-						let validation_result =
-							ValidationResult::Invalid(InvalidCandidate::InvalidOutputs);
+						// Set the validation result to invalid with probability `p` and trigger a dispute
+						let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
+						match behave_maliciously {
+							true => {
+								let validation_result =
+									ValidationResult::Invalid(InvalidCandidate::InvalidOutputs);
+
+								gum::info!(
+									target: MALUS,
+									?behave_maliciously,
+									para_id = ?candidate_receipt.descriptor.para_id,
+									"😈 Maliciously sending invalid validation result: {:?}.",
+									&validation_result,
+								);
 
-						gum::debug!(
-							target: MALUS,
-							para_id = ?candidate_receipt.descriptor.para_id,
-							"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
+								// We're not even checking the candidate, this makes us appear faster than honest validators.
+								sender.send(Ok(validation_result)).unwrap();
+								None
+							},
+							false => {
+								// Behave normally with probability `(1-p)`
+								gum::info!(target: MALUS, "😈 'Decided' to not act maliciously.",);
+
+								Some(FromOrchestra::Communication {
+									msg: CandidateValidationMessage::ValidateFromExhaustive(
+										validation_data,
+										validation_code,
+										candidate_receipt,
+										pov,
+										timeout,
+										sender,
+									),
+								})
+							},
+						}
 					},
+					// Handle FakeCandidateValidation::Disabled
 					_ => Some(FromOrchestra::Communication {
 						msg: CandidateValidationMessage::ValidateFromExhaustive(
 							validation_data,
@@ -270,6 +333,7 @@ where
 					}),
 				}
 			},
+			// Behaviour related to the backing subsystem
 			FromOrchestra::Communication {
 				msg:
 					CandidateValidationMessage::ValidateFromChainState(
@@ -293,27 +357,68 @@ where
 								),
 							})
 						}
-						self.send_validation_response(
-							candidate_receipt.descriptor,
-							subsystem_sender.clone(),
-							response_sender,
-						);
-						None
+						// If the `PoV` is malicious, back the candidate with some probability `p`,
+						// where 'p' defaults to 100% for suggest-garbage-candidate variant.
+						let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
+						match behave_maliciously {
+							true => {
+								gum::info!(
+									target: MALUS,
+									?behave_maliciously,
+									"😈 Backing candidate with malicious PoV.",
+								);
+
+								self.send_validation_response(
+									candidate_receipt.descriptor,
+									subsystem_sender.clone(),
+									response_sender,
+								);
+								None
+							},
+							// If the `PoV` is malicious, we behave normally with some probability `(1-p)`
+							false => Some(FromOrchestra::Communication {
+								msg: CandidateValidationMessage::ValidateFromChainState(
+									candidate_receipt,
+									pov,
+									timeout,
+									response_sender,
+								),
+							}),
+						}
 					},
 					FakeCandidateValidation::BackingInvalid |
 					FakeCandidateValidation::BackingAndApprovalInvalid => {
-						let validation_result =
-							ValidationResult::Invalid(self.fake_validation_error.clone().into());
-						gum::debug!(
-							target: MALUS,
-							para_id = ?candidate_receipt.descriptor.para_id,
-							"ValidateFromChainState result: {:?}",
-							&validation_result
-						);
+						// Maliciously set the validation result to invalid for a valid candidate with probability `p`
+						let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
+						match behave_maliciously {
+							true => {
+								let validation_result = ValidationResult::Invalid(
+									self.fake_validation_error.clone().into(),
+								);
+								gum::info!(
+									target: MALUS,
+									para_id = ?candidate_receipt.descriptor.para_id,
+									"😈 Maliciously sending invalid validation result: {:?}.",
+									&validation_result,
+								);
+								// We're not even checking the candidate, this makes us appear faster than honest validators.
+								response_sender.send(Ok(validation_result)).unwrap();
+								None
+							},
+							// With some probability `(1-p)` we behave normally
+							false => {
+								gum::info!(target: MALUS, "😈 'Decided' to not act maliciously.",);
 
-						// We're not even checking the candidate, this makes us appear faster than honest validators.
-						response_sender.send(Ok(validation_result)).unwrap();
-						None
+								Some(FromOrchestra::Communication {
+									msg: CandidateValidationMessage::ValidateFromChainState(
+										candidate_receipt,
+										pov,
+										timeout,
+										response_sender,
+									),
+								})
+							},
+						}
 					},
 					_ => Some(FromOrchestra::Communication {
 						msg: CandidateValidationMessage::ValidateFromChainState(
diff --git a/polkadot/node/malus/src/variants/dispute_valid_candidates.rs b/polkadot/node/malus/src/variants/dispute_valid_candidates.rs
index 175cdecee91..c8e6afe643c 100644
--- a/polkadot/node/malus/src/variants/dispute_valid_candidates.rs
+++ b/polkadot/node/malus/src/variants/dispute_valid_candidates.rs
@@ -55,6 +55,11 @@ pub struct DisputeAncestorOptions {
 	#[clap(long, arg_enum, ignore_case = true, default_value_t = FakeCandidateValidationError::InvalidOutputs)]
 	pub fake_validation_error: FakeCandidateValidationError,
 
+	/// Determines the percentage of candidates that should be disputed. Allows for fine-tuning
+	/// the intensity of the behavior of the malicious node. Value must be in the range [0..=100].
+	#[clap(short, long, ignore_case = true, default_value_t = 100, value_parser = clap::value_parser!(u8).range(0..=100))]
+	pub percentage: u8,
+
 	#[clap(flatten)]
 	pub cli: Cli,
 }
@@ -64,6 +69,8 @@ pub(crate) struct DisputeValidCandidates {
 	pub fake_validation: FakeCandidateValidation,
 	/// Fake validation error config.
 	pub fake_validation_error: FakeCandidateValidationError,
+	/// The probability of behaving maliciously.
+	pub percentage: u8,
 }
 
 impl OverseerGen for DisputeValidCandidates {
@@ -81,6 +88,7 @@ impl OverseerGen for DisputeValidCandidates {
 		let validation_filter = ReplaceValidationResult::new(
 			self.fake_validation,
 			self.fake_validation_error,
+			f64::from(self.percentage),
 			SpawnGlue(spawner.clone()),
 		);
 
diff --git a/polkadot/node/malus/src/variants/mod.rs b/polkadot/node/malus/src/variants/mod.rs
index d57580fdf8d..6f9a9359e02 100644
--- a/polkadot/node/malus/src/variants/mod.rs
+++ b/polkadot/node/malus/src/variants/mod.rs
@@ -22,8 +22,8 @@ mod dispute_valid_candidates;
 mod suggest_garbage_candidate;
 
 pub(crate) use self::{
-	back_garbage_candidate::BackGarbageCandidate,
+	back_garbage_candidate::{BackGarbageCandidateOptions, BackGarbageCandidates},
 	dispute_valid_candidates::{DisputeAncestorOptions, DisputeValidCandidates},
-	suggest_garbage_candidate::BackGarbageCandidateWrapper,
+	suggest_garbage_candidate::{SuggestGarbageCandidateOptions, SuggestGarbageCandidates},
 };
 pub(crate) use common::*;
diff --git a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs
index b8aaaa18c10..86b0c49e712 100644
--- a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs
+++ b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs
@@ -29,14 +29,17 @@ use polkadot_cli::{
 		OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost,
 		ProvideRuntimeApi,
 	},
+	Cli,
 };
 use polkadot_node_core_candidate_validation::find_validation_data;
 use polkadot_node_primitives::{AvailableData, BlockData, PoV};
-use polkadot_primitives::v2::{CandidateDescriptor, CandidateHash};
+use polkadot_primitives::v2::CandidateDescriptor;
 
 use polkadot_node_subsystem_util::request_validators;
 use sp_core::traits::SpawnNamed;
 
+use rand::distributions::{Bernoulli, Distribution};
+
 // Filter wrapping related types.
 use crate::{
 	interceptor::*,
@@ -49,28 +52,16 @@ use crate::{
 
 // Import extra types relevant to the particular
 // subsystem.
-use polkadot_node_subsystem::{
-	messages::{CandidateBackingMessage, CollatorProtocolMessage},
-	SpawnGlue,
-};
+use polkadot_node_subsystem::{messages::CandidateBackingMessage, SpawnGlue};
 use polkadot_primitives::v2::CandidateReceipt;
 
-use std::{
-	collections::HashMap,
-	sync::{Arc, Mutex},
-};
-
-struct Inner {
-	/// Maps malicious candidate hash to original candidate hash.
-	/// It is used to replace outgoing collator protocol seconded messages.
-	map: HashMap<CandidateHash, CandidateHash>,
-}
+use std::sync::Arc;
 
 /// Replace outgoing approval messages with disputes.
 #[derive(Clone)]
 struct NoteCandidate<Spawner> {
-	inner: Arc<Mutex<Inner>>,
 	spawner: Spawner,
+	percentage: f64,
 }
 
 impl<Sender, Spawner> MessageInterceptor<Sender> for NoteCandidate<Spawner>
@@ -80,7 +71,7 @@ where
 {
 	type Message = CandidateBackingMessage;
 
-	/// Intercept incoming `Second` requests from the `collator-protocol` subsystem. We take
+	/// Intercept incoming `Second` requests from the `collator-protocol` subsystem.
 	fn intercept_incoming(
 		&self,
 		subsystem_sender: &mut Sender,
@@ -88,163 +79,174 @@ where
 	) -> Option<FromOrchestra<Self::Message>> {
 		match msg {
 			FromOrchestra::Communication {
-				msg: CandidateBackingMessage::Second(relay_parent, candidate, _pov),
+				msg: CandidateBackingMessage::Second(relay_parent, ref candidate, ref _pov),
 			} => {
 				gum::debug!(
 					target: MALUS,
 					candidate_hash = ?candidate.hash(),
 					?relay_parent,
-					"Received request to second candidate"
-				);
-
-				let pov = PoV { block_data: BlockData(MALICIOUS_POV.into()) };
-
-				let (sender, receiver) = std::sync::mpsc::channel();
-				let mut new_sender = subsystem_sender.clone();
-				let _candidate = candidate.clone();
-				self.spawner.spawn_blocking(
-					"malus-get-validation-data",
-					Some("malus"),
-					Box::pin(async move {
-						gum::trace!(target: MALUS, "Requesting validators");
-						let n_validators = request_validators(relay_parent, &mut new_sender)
-							.await
-							.await
-							.unwrap()
-							.unwrap()
-							.len();
-						gum::trace!(target: MALUS, "Validators {}", n_validators);
-						match find_validation_data(&mut new_sender, &_candidate.descriptor()).await
-						{
-							Ok(Some((validation_data, validation_code))) => {
-								sender
-									.send((validation_data, validation_code, n_validators))
-									.expect("channel is still open");
-							},
-							_ => {
-								panic!("Unable to fetch validation data");
-							},
-						}
-					}),
-				);
-
-				let (validation_data, validation_code, n_validators) = receiver.recv().unwrap();
-
-				let validation_data_hash = validation_data.hash();
-				let validation_code_hash = validation_code.hash();
-				let validation_data_relay_parent_number = validation_data.relay_parent_number;
-
-				gum::trace!(
-					target: MALUS,
-					candidate_hash = ?candidate.hash(),
-					?relay_parent,
-					?n_validators,
-					?validation_data_hash,
-					?validation_code_hash,
-					?validation_data_relay_parent_number,
-					"Fetched validation data."
+					"Received request to second candidate",
 				);
 
-				let malicious_available_data =
-					AvailableData { pov: Arc::new(pov.clone()), validation_data };
-
-				let pov_hash = pov.hash();
-				let erasure_root = {
-					let chunks =
-						erasure::obtain_chunks_v1(n_validators as usize, &malicious_available_data)
-							.unwrap();
-
-					let branches = erasure::branches(chunks.as_ref());
-					branches.root()
-				};
-
-				let (collator_id, collator_signature) = {
-					use polkadot_primitives::v2::CollatorPair;
-					use sp_core::crypto::Pair;
-
-					let collator_pair = CollatorPair::generate().0;
-					let signature_payload = polkadot_primitives::v2::collator_signature_payload(
-						&relay_parent,
-						&candidate.descriptor().para_id,
-						&validation_data_hash,
-						&pov_hash,
-						&validation_code_hash,
+				// Need to draw value from Bernoulli distribution with given probability of success defined by the clap parameter.
+				// Note that clap parameter must be f64 since this is expected by the Bernoulli::new() function.
+				// It must be converted from u8, due to the lack of support for the .range() call on u64 in the clap crate.
+				let distribution = Bernoulli::new(self.percentage / 100.0)
+					.expect("Invalid probability! Percentage must be in range [0..=100].");
+
+				// Draw a random boolean from the Bernoulli distribution with probability of true equal to `p`.
+				// We use `rand::thread_rng` as the source of randomness.
+				let generate_malicious_candidate = distribution.sample(&mut rand::thread_rng());
+
+				if generate_malicious_candidate == true {
+					gum::debug!(target: MALUS, "😈 Suggesting malicious candidate.",);
+
+					let pov = PoV { block_data: BlockData(MALICIOUS_POV.into()) };
+
+					let (sender, receiver) = std::sync::mpsc::channel();
+					let mut new_sender = subsystem_sender.clone();
+					let _candidate = candidate.clone();
+					self.spawner.spawn_blocking(
+						"malus-get-validation-data",
+						Some("malus"),
+						Box::pin(async move {
+							gum::trace!(target: MALUS, "Requesting validators");
+							let n_validators = request_validators(relay_parent, &mut new_sender)
+								.await
+								.await
+								.unwrap()
+								.unwrap()
+								.len();
+							gum::trace!(target: MALUS, "Validators {}", n_validators);
+							match find_validation_data(&mut new_sender, &_candidate.descriptor())
+								.await
+							{
+								Ok(Some((validation_data, validation_code))) => {
+									sender
+										.send((validation_data, validation_code, n_validators))
+										.expect("channel is still open");
+								},
+								_ => {
+									panic!("Unable to fetch validation data");
+								},
+							}
+						}),
 					);
 
-					(collator_pair.public(), collator_pair.sign(&signature_payload))
-				};
-
-				let malicious_commitments =
-					create_fake_candidate_commitments(&malicious_available_data.validation_data);
-
-				let malicious_candidate = CandidateReceipt {
-					descriptor: CandidateDescriptor {
-						para_id: candidate.descriptor().para_id,
-						relay_parent,
-						collator: collator_id,
-						persisted_validation_data_hash: validation_data_hash,
-						pov_hash,
-						erasure_root,
-						signature: collator_signature,
-						para_head: malicious_commitments.head_data.hash(),
-						validation_code_hash,
-					},
-					commitments_hash: malicious_commitments.hash(),
-				};
-				let malicious_candidate_hash = malicious_candidate.hash();
-
-				gum::debug!(
-					target: MALUS,
-					candidate_hash = ?candidate.hash(),
-					?malicious_candidate_hash,
-					"Created malicious candidate"
-				);
-
-				// Map malicious candidate to the original one. We need this mapping to send back the correct seconded statement
-				// to the collators.
-				self.inner
-					.lock()
-					.expect("bad lock")
-					.map
-					.insert(malicious_candidate_hash, candidate.hash());
+					let (validation_data, validation_code, n_validators) = receiver.recv().unwrap();
+
+					let validation_data_hash = validation_data.hash();
+					let validation_code_hash = validation_code.hash();
+					let validation_data_relay_parent_number = validation_data.relay_parent_number;
+
+					gum::trace!(
+						target: MALUS,
+						candidate_hash = ?candidate.hash(),
+						?relay_parent,
+						?n_validators,
+						?validation_data_hash,
+						?validation_code_hash,
+						?validation_data_relay_parent_number,
+						"Fetched validation data."
+					);
 
-				let message = FromOrchestra::Communication {
-					msg: CandidateBackingMessage::Second(relay_parent, malicious_candidate, pov),
-				};
+					let malicious_available_data =
+						AvailableData { pov: Arc::new(pov.clone()), validation_data };
+
+					let pov_hash = pov.hash();
+					let erasure_root = {
+						let chunks = erasure::obtain_chunks_v1(
+							n_validators as usize,
+							&malicious_available_data,
+						)
+						.unwrap();
+
+						let branches = erasure::branches(chunks.as_ref());
+						branches.root()
+					};
+
+					let (collator_id, collator_signature) = {
+						use polkadot_primitives::v2::CollatorPair;
+						use sp_core::crypto::Pair;
+
+						let collator_pair = CollatorPair::generate().0;
+						let signature_payload = polkadot_primitives::v2::collator_signature_payload(
+							&relay_parent,
+							&candidate.descriptor().para_id,
+							&validation_data_hash,
+							&pov_hash,
+							&validation_code_hash,
+						);
+
+						(collator_pair.public(), collator_pair.sign(&signature_payload))
+					};
+
+					let malicious_commitments = create_fake_candidate_commitments(
+						&malicious_available_data.validation_data,
+					);
 
-				Some(message)
+					let malicious_candidate = CandidateReceipt {
+						descriptor: CandidateDescriptor {
+							para_id: candidate.descriptor().para_id,
+							relay_parent,
+							collator: collator_id,
+							persisted_validation_data_hash: validation_data_hash,
+							pov_hash,
+							erasure_root,
+							signature: collator_signature,
+							para_head: malicious_commitments.head_data.hash(),
+							validation_code_hash,
+						},
+						commitments_hash: malicious_commitments.hash(),
+					};
+					let malicious_candidate_hash = malicious_candidate.hash();
+
+					let message = FromOrchestra::Communication {
+						msg: CandidateBackingMessage::Second(
+							relay_parent,
+							malicious_candidate,
+							pov,
+						),
+					};
+
+					gum::info!(
+						target: MALUS,
+						candidate_hash = ?candidate.hash(),
+						"😈 Intercepted CandidateBackingMessage::Second and created malicious candidate with hash: {:?}",
+						&malicious_candidate_hash
+					);
+					Some(message)
+				} else {
+					Some(msg)
+				}
 			},
 			FromOrchestra::Communication { msg } => Some(FromOrchestra::Communication { msg }),
 			FromOrchestra::Signal(signal) => Some(FromOrchestra::Signal(signal)),
 		}
 	}
+}
 
-	fn intercept_outgoing(
-		&self,
-		msg: overseer::CandidateBackingOutgoingMessages,
-	) -> Option<overseer::CandidateBackingOutgoingMessages> {
-		let msg = match msg {
-			overseer::CandidateBackingOutgoingMessages::CollatorProtocolMessage(
-				CollatorProtocolMessage::Seconded(relay_parent, statement),
-			) => {
-				// `parachain::collator-protocol: received an unexpected `CollationSeconded`: unknown statement statement=...`
-				// TODO: Fix this error. We get this on colaltors because `malicious backing` creates a candidate that gets backed/included.
-				// It is harmless for test parachain collators, but it will prevent cumulus based collators to make progress
-				// as they wait for the relay chain to confirm the seconding of the collation.
-				overseer::CandidateBackingOutgoingMessages::CollatorProtocolMessage(
-					CollatorProtocolMessage::Seconded(relay_parent, statement),
-				)
-			},
-			msg => msg,
-		};
-		Some(msg)
-	}
+#[derive(Debug, clap::Parser)]
+#[clap(rename_all = "kebab-case")]
+#[allow(missing_docs)]
+pub struct SuggestGarbageCandidateOptions {
+	/// Determines the percentage of malicious candidates that are suggested by malus,
+	/// based on the total number of intercepted CandidateBacking
+	/// Must be in the range [0..=100].
+	#[clap(short, long, ignore_case = true, default_value_t = 100, value_parser = clap::value_parser!(u8).range(0..=100))]
+	pub percentage: u8,
+
+	#[clap(flatten)]
+	pub cli: Cli,
 }
 
 /// Garbage candidate implementation wrapper which implements `OverseerGen` glue.
-pub(crate) struct BackGarbageCandidateWrapper;
+pub(crate) struct SuggestGarbageCandidates {
+	/// The probability of behaving maliciously.
+	pub percentage: u8,
+}
 
-impl OverseerGen for BackGarbageCandidateWrapper {
+impl OverseerGen for SuggestGarbageCandidates {
 	fn generate<'a, Spawner, RuntimeClient>(
 		&self,
 		connector: OverseerConnector,
@@ -255,14 +257,21 @@ impl OverseerGen for BackGarbageCandidateWrapper {
 		RuntimeClient::Api: ParachainHost<Block> + BabeApi<Block> + AuthorityDiscoveryApi<Block>,
 		Spawner: 'static + SpawnNamed + Clone + Unpin,
 	{
-		let inner = Inner { map: std::collections::HashMap::new() };
-		let inner_mut = Arc::new(Mutex::new(inner));
-		let note_candidate =
-			NoteCandidate { inner: inner_mut.clone(), spawner: SpawnGlue(args.spawner.clone()) };
+		gum::info!(
+			target: MALUS,
+			"😈 Started Malus node with a {:?} percent chance of behaving maliciously for a given candidate.",
+			&self.percentage,
+		);
 
+		let note_candidate = NoteCandidate {
+			spawner: SpawnGlue(args.spawner.clone()),
+			percentage: f64::from(self.percentage),
+		};
+		let fake_valid_probability = 100.0;
 		let validation_filter = ReplaceValidationResult::new(
 			FakeCandidateValidation::BackingAndApprovalValid,
 			FakeCandidateValidationError::InvalidOutputs,
+			fake_valid_probability,
 			SpawnGlue(args.spawner.clone()),
 		);
 
-- 
GitLab