Malus: add disputed block percentage (#6100)

* 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 e4361b6d80
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 f614752c22
Author: girazoki <gorka.irazoki@gmail.com>
Date:   Mon Oct 10 06:39:30 2022 +0200

    Add event to asset claim (#6029)

commit 71197818a4
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 607350449c
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 c8151aed3c
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 8d1c16dc0d
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 910e21847f
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 af6a5cd96a
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 9a3cf4cd1f
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 4aea71a95f
Author: Alexander Theißen <alex.theissen@me.com>
Date:   Wed Oct 5 15:15:07 2022 +0200

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

commit 42c043d7f4
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 b13e07bc47
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 e0e836671f
Author: Robert Klotzner <eskimor@users.noreply.github.com>
Date:   Tue Oct 4 18:47:52 2022 +0200

    Add unknown words (#6105)

commit 938bc96a2c
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 a64cc4a860
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 7114a8cfca
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 ab8f04f827
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>
This commit is contained in:
Mattia L.V. Bradascio
2022-10-13 10:48:48 +01:00
committed by GitHub
parent a7780e0797
commit b3532393b8
8 changed files with 438 additions and 209 deletions
+10 -9
View File
@@ -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"
+2 -1
View File
@@ -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 = []
+97 -10
View File
@@ -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::DisputeAncestor(opts) => {
let DisputeAncestorOptions { fake_validation, fake_validation_error, cli } = opts;
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,
DisputeValidCandidates { fake_validation, fake_validation_error },
SuggestGarbageCandidates { percentage },
finality_delay,
)?
},
NemesisVariant::DisputeAncestor(opts) => {
let DisputeAncestorOptions {
fake_validation,
fake_validation_error,
percentage,
cli,
} = opts;
polkadot_cli::run_node(
cli,
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);
});
}
}
@@ -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),
);
+141 -36
View File
@@ -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::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
gum::info!(
target: MALUS,
?behave_maliciously,
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.
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(
@@ -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()),
);
+2 -2
View File
@@ -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::*;
@@ -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"
"Received request to second candidate",
);
let pov = PoV { block_data: BlockData(MALICIOUS_POV.into()) };
// 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].");
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");
},
}
}),
);
// 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());
let (validation_data, validation_code, n_validators) = receiver.recv().unwrap();
if generate_malicious_candidate == true {
gum::debug!(target: MALUS, "😈 Suggesting malicious candidate.",);
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;
let pov = PoV { block_data: BlockData(MALICIOUS_POV.into()) };
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 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,
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 (validation_data, validation_code, n_validators) = receiver.recv().unwrap();
let malicious_commitments =
create_fake_candidate_commitments(&malicious_available_data.validation_data);
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;
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::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."
);
gum::debug!(
target: MALUS,
candidate_hash = ?candidate.hash(),
?malicious_candidate_hash,
"Created malicious candidate"
);
let malicious_available_data =
AvailableData { pov: Arc::new(pov.clone()), validation_data };
// 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 pov_hash = pov.hash();
let erasure_root = {
let chunks = erasure::obtain_chunks_v1(
n_validators as usize,
&malicious_available_data,
)
.unwrap();
let message = FromOrchestra::Communication {
msg: CandidateBackingMessage::Second(relay_parent, malicious_candidate, pov),
};
let branches = erasure::branches(chunks.as_ref());
branches.root()
};
Some(message)
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,
);
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()),
);