Asynchronous Backing MegaPR (#5022)

* inclusion emulator logic for asynchronous backing (#4790)

* initial stab at candidate_context

* fmt

* docs & more TODOs

* some cleanups

* reframe as inclusion_emulator

* documentations yes

* update types

* add constraint modifications

* watermark

* produce modifications

* v2 primitives: re-export all v1 for consistency

* vstaging primitives

* emulator constraints: handle code upgrades

* produce outbound HRMP modifications

* stack.

* method for applying modifications

* method just for sanity-checking modifications

* fragments produce modifications, not prospectives

* make linear

* add some TODOs

* remove stacking; handle code upgrades

* take `fragment` private

* reintroduce stacking.

* fragment constructor

* add TODO

* allow validating fragments against future constraints

* docs

* relay-parent number and min code size checks

* check code upgrade restriction

* check max hrmp per candidate

* fmt

* remove GoAhead logic because it wasn't helpful

* docs on code upgrade failure

* test stacking

* test modifications against constraints

* fmt

* test fragments

* descending or duplicate test

* fmt

* remove unused imports in vstaging

* wrong primitives

* spellcheck

* Runtime changes for Asynchronous Backing (#4786)

* inclusion: utility for allowed relay-parents

* inclusion: use prev number instead of prev hash

* track most recent context of paras

* inclusion: accept previous relay-parents

* update dmp  advancement rule for async backing

* fmt

* add a comment about validation outputs

* clean up a couple of TODOs

* weights

* fix weights

* fmt

* Resolve dmp todo

* Restore inclusion tests

* Restore paras_inherent tests

* MostRecentContext test

* Benchmark for new paras dispatchable

* Prepare check_validation_outputs for upgrade

* cargo run --quiet --profile=production  --features=runtime-benchmarks -- benchmark --chain=kusama-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/kusama/src/weights/runtime_parachains_paras.rs

* cargo run --quiet --profile=production  --features=runtime-benchmarks -- benchmark --chain=westend-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/westend/src/weights/runtime_parachains_paras.rs

* cargo run --quiet --profile=production  --features=runtime-benchmarks -- benchmark --chain=polkadot-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/polkadot/src/weights/runtime_parachains_paras.rs

* cargo run --quiet --profile=production  --features=runtime-benchmarks -- benchmark --chain=rococo-dev --steps=50 --repeat=20 --pallet=runtime_parachains::paras --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --header=./file_header.txt --output=./runtime/rococo/src/weights/runtime_parachains_paras.rs

* Implementers guide changes

* More tests for allowed relay parents

* Add a github issue link

* Compute group index based on relay parent

* Storage migration

* Move allowed parents tracker to shared

* Compile error

* Get group assigned to core at the next block

* Test group assignment

* fmt

* Error instead of panic

* Update guide

* Extend doc-comment

* Update runtime/parachains/src/shared.rs

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>

Co-authored-by: Chris Sosnin <chris125_@live.com>
Co-authored-by: Parity Bot <admin@parity.io>
Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>

* Prospective Parachains Subsystem (#4913)

* docs and skeleton

* subsystem skeleton

* main loop

* fragment tree basics & fmt

* begin fragment trees & view

* flesh out more of view update logic

* further flesh out update logic

* some refcount functions for fragment trees

* add fatal/non-fatal errors

* use non-fatal results

* clear up some TODOs

* ideal format for scheduling info

* add a bunch of TODOs

* some more fluff

* extract fragment graph to submodule

* begin fragment graph API

* trees, not graphs

* improve docs

* scope and constructor for trees

* add some test TODOs

* limit max ancestors and store constraints

* constructor

* constraints: fix bug in HRMP watermarks

* fragment tree population logic

* set::retain

* extract population logic

* implement add_and_populate

* fmt

* add some TODOs in tests

* implement child-selection

* strip out old stuff based on wrong assumptions

* use fatality

* implement pruning

* remove unused ancestor constraints

* fragment tree instantiation

* remove outdated comment

* add message/request types and skeleton for handling

* fmt

* implement handle_candidate_seconded

* candidate storage: handle backed

* implement handle_candidate_backed

* implement answer_get_backable_candidate

* remove async where not needed

* implement fetch_ancestry

* add logic for run_iteration

* add some docs

* remove global allow(unused), fix warnings

* make spellcheck happy (despite English)

* fmt

* bump Cargo.lock

* replace tracing with gum

* introduce PopulateFrom trait

* implement GetHypotheticalDepths

* revise docs slightly

* first fragment tree scope test

* more scope tests

* test add_candidate

* fmt

* test retain

* refactor test code

* test populate is recursive

* test contiguity of depth 0 is maintained

* add_and_populate tests

* cycle tests

* remove PopulateFrom trait

* fmt

* test hypothetical depths (non-recursive)

* have CandidateSeconded return membership

* tree membership requests

* Add a ProspectiveParachainsSubsystem struct

* add a staging API for base constraints

* add a `From` impl

* add runtime API for staging_validity_constraints

* implement fetch_base_constraints

* implement `fetch_upcoming_paras`

* remove reconstruction of candidate receipt; no obvious usecase

* fmt

* export message to broader module

* remove last TODO

* correctly export

* fix compilation and add GetMinimumRelayParent request

* make provisioner into a real subsystem with proper mesage bounds

* fmt

* fix ChannelsOut in overseer test

* fix overseer tests

* fix again

* fmt

* Integrate prospective parachains subsystem into backing: Part 1 (#5557)

* BEGIN ASYNC candidate-backing CHANGES

* rename & document modes

* answer prospective validation data requests

* GetMinimumRelayParents request is now plural

* implement an implicit view utility for backing subsystems

* implicit-view: get allowed relay parents

* refactorings and improvements to implicit view

* add some TODOs for tests

* split implicit view updates into 2 functions

* backing: define State to prepare for functional refactor

* add some docs

* backing: implement bones of new leaf activation logic

* backing: create per-relay-parent-states

* use new handle_active_leaves_update

* begin extracting logic from CandidateBackingJob

* mostly extract statement import from job logic

* handle statement imports outside of job logic

* do some TODO planning for prospective parachains integration

* finish rewriting backing subsystem in functional style

* add prospective parachains mode to relay parent entries

* fmt

* add a RejectedByProspectiveParachains error

* notify prospective parachains of seconded and backed candidates

* always validate candidates exhaustively in backing.

* return persisted_validation_data from validation

* handle rejections by prospective parachains

* implement seconding sanity check

* invoke validate_and_second

* Alter statement table to allow multiple seconded messages per validator

* refactor backing to have statements carry PVD

* clean up all warnings

* Add tests for implicit view

* Improve doc comments

* Prospective parachains mode based on Runtime API version

* Add a TODO

* Rework seconding_sanity_check

* Iterate over responses

* Update backing tests

* collator-protocol: load PVD from runtime

* Fix validator side tests

* Update statement-distribution to fetch PVD

* Fix statement-distribution tests

* Backing tests with prospective paras #1

* fix per_relay_parent pruning in backing

* Test multiple leaves

* Test seconding sanity check

* Import statement order

Before creating an entry in `PerCandidateState` map
wait for the approval from the prospective parachains

* Add a test for correct state updates

* Second multiple candidates per relay parent test

* Add backing tests with prospective paras

* Second more than one test without prospective paras

* Add a test for prospective para blocks

* Update malus

* typos

Co-authored-by: Chris Sosnin <chris125_@live.com>

* Track occupied depth in backing per parachain (#5778)

* provisioner: async backing changes (#5711)

* Provisioner changes for async backing

* Select candidates based on prospective paras mode

* Revert naming

* Update tests

* Update TODO comment

* review

* provisioner: async backing changes (#5711)

* Provisioner changes for async backing

* Select candidates based on prospective paras mode

* Revert naming

* Update tests

* Update TODO comment

* review

* fmt

* Network bridge changes for asynchronous backing + update subsystems to handle versioned packets (#5991)

* BEGIN STATEMENT DISTRIBUTION WORK

create a vstaging network protocol which is the same as v1

* mostly make network bridge amenable to vstaging

* network-bridge: fully adapt to vstaging

* add some TODOs for tests

* fix fallout in bitfield-distribution

* bitfield distribution tests + TODOs

* fix fallout in gossip-support

* collator-protocol: fix message fallout

* collator-protocol: load PVD from runtime

* add TODO for vstaging tests

* make things compile

* set used network protocol version using a feature

* fmt

* get approval-distribution building

* fix approval-distribution tests

* spellcheck

* nits

* approval distribution net protocol test

* bitfield distribution net protocol test

* Revert "collator-protocol: fix message fallout"

This reverts commit 07cc887303e16c6b3843ecb25cdc7cc2080e2ed1.

* Network bridge tests

Co-authored-by: Chris Sosnin <chris125_@live.com>

* remove max_pov_size requirement from prospective pvd request (#6014)

* remove max_pov_size requirement from prospective pvd request

* fmt

* Extract legacy statement distribution to its own module (#6026)

* add compatibility type to v2 statement distribution message

* warning cleanup

* handle compatibility layer for v2

* clean up an unimplemented!() block

* circulate statements based on version

* extract legacy v1 code into separate module

* remove unimplemented

* clean up naming of from_requester/responder

* remove TODOs

* have backing share seconded statements with PVD

* fmt

* fix warning

* Quick fix unused warning for not yet implemented/used staging messages.

* Fix network bridge test

* Fix wrong merge.

We now have 23 subsystems (network bridge split + prospective
parachains)

Co-authored-by: Robert Klotzner <robert.klotzner@gmx.at>

* Version 3 is already live.

* Fix tests (#6055)

* Fix backing tests

* Fix warnings.

* fmt

* collator-protocol: asynchronous backing changes (#5740)

* Draft collator side changes

* Start working on collations management

* Handle peer's view change

* Versioning on advertising

* Versioned collation fetching request

* Handle versioned messages

* Improve docs for collation requests

* Add spans

* Add request receiver to overseer

* Fix collator side tests

* Extract relay parent mode to lib

* Validator side draft

* Add more checks for advertisement

* Request pvd based on async backing mode

* review

* Validator side improvements

* Make old tests green

* More fixes

* Collator side tests draft

* Send collation test

* fmt

* Collator side network protocol versioning

* cleanup

* merge artifacts

* Validator side net protocol versioning

* Remove fragment tree membership request

* Resolve todo

* Collator side core state test

* Improve net protocol compatibility

* Validator side tests

* more improvements

* style fixes

* downgrade log

* Track implicit assignments

* Limit the number of seconded candidates per para

* Add a sanity check

* Handle fetched candidate

* fix tests

* Retry fetch

* Guard against dequeueing while already fetching

* Reintegrate connection management

* Timeout on advertisements

* fmt

* spellcheck

* update tests after merge

* validator assignment fixes for backing and collator protocol (#6158)

* Rename depth->ancestry len in tests

* Refactor group assignments

* Remove implicit assignments

* backing: consider occupied core assignments

* Track a single para on validator side

* Refactor prospective parachains mode request (#6179)

* Extract prospective parachains mode into util

* Skip activations depending on the mode

* backing: don't send backed candidate to provisioner (#6185)

* backing: introduce `CanSecond` request for advertisements filtering (#6225)

* Drop BoundToRelayParent

* draft changes

* fix backing tests

* Fix genesis ancestry

* Fix validator side tests

* more tests

* cargo generate-lockfile

* Implement `StagingValidityConstraints` Runtime API method (#6258)

* Implement StagingValidityConstraints

* spellcheck

* fix ump params

* Update hrmp comment

* Introduce ump per candidate limit

* hypothetical earliest block

* refactor primitives usage

* hypothetical earliest block number test

* fix build

* Prepare the Runtime for asynchronous backing upgrade (#6287)

* Introduce async backing params to runtime config

* fix cumulus config

* use config

* finish runtimes

* Introduce new staging API

* Update collator protocol

* Update provisioner

* Update prospective parachains

* Update backing

* Move async backing params lower in the config

* make naming consistent

* misc

* Use real prospective parachains subsystem (#6407)

* Backport `HypotheticalFrontier` into the feature branch (#6605)

* implement more general HypotheticalFrontier

* fmt

* drop unneeded request

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>

* Resolve todo about legacy leaf activation (#6447)

* fix bug/warning in handling membership answers

* Remove `HypotheticalDepthRequest` in favor of `HypotheticalFrontierRequest` (#6521)

* Remove `HypotheticalDepthRequest` for `HypotheticalFrontierRequest`

* Update tests

* Fix (removed wrong docstring)

* Fix can_second request

* Patch some dead_code errors

---------

Co-authored-by: Chris Sosnin <chris125_@live.com>

* Async Backing: Send Statement Distribution "Backed" messages (#6634)

* Backing: Send Statement Distribution "Backed" messages

Closes #6590.

**TODO:**

- [ ] Adjust tests

* Fix compile errors

* (Mostly) fix tests

* Fix comment

* Fix test and compile error

* Test that `StatementDistributionMessage::Backed` is sent

* Fix compile error

* Fix some clippy errors

* Add prospective parachains subsystem tests (#6454)

* Add prospective parachains subsystem test

* Add `should_do_no_work_if_async_backing_disabled_for_leaf` test

* Implement `activate_leaf` helper, up to getting ancestry

* Finish implementing `activate_leaf`

* Small refactor in `activate_leaf`

* Get `CandidateSeconded` working

* Finish `send_candidate_and_check_if_found` test

* Refactor; send more leaves & candidates

* Refactor test

* Implement `check_candidate_parent_leaving_view` test

* Start work on `check_candidate_on_multiple_forks` test

* Don’t associate specific parachains with leaf

* Finish `correctly_updates_leaves` test

* Fix cycle due to reused head data

* Fix `check_backable_query` test

* Fix `check_candidate_on_multiple_forks` test

* Add `check_depth_and_pvd_queries` test

* Address review comments

* Remove TODO

* add a new index for output head data to candidate storage

* Resolve test TODOs

* Fix compile errors

* test candidate storage pruning, make sure new index is cleaned up

---------

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>

* Node-side metrics for asynchronous backing (#6549)

* Add metrics for `prune_view_candidate_storage`

* Add metrics for `request_unblocked_collations`

* Fix docstring

* Couple fixes from review comments

* Fix `check_depth_query` test

* inclusion-emulator: mirror advancement rule check (#6361)

* inclusion-emulator: mirror advancement rule check

* fix build

* prospective-parachains: introduce `backed_in_path_only` flag for advertisements (#6649)

* Introduce `backed_in_path_only` flag for depth request

* fmt

* update doc comment

* fmt

* Add async-backing zombienet tests (#6314)

* Async backing: impl guide for statement distribution (#6738)

Co-authored-by: Bradley Olson <34992650+BradleyOlson64@users.noreply.github.com>
Co-authored-by: alexgparity <115470171+alexgparity@users.noreply.github.com>

* Asynchronous backing statement distribution: Take III (#5999)

* add notification types for v2 statement-distribution

* improve protocol docs

* add empty vstaging module

* fmt

* add backed candidate packet request types

* start putting down structure of new logic

* handle activated leaf

* some sanity-checking on outbound statements

* fmt

* update vstaging share to use statements with PVD

* tiny refactor, candidate_hash location

* import local statements

* refactor statement import

* first stab at broadcast logic

* fmt

* fill out some TODOs

* start on handling incoming

* split off session info into separate map

* start in on a knowledge tracker

* address some grumbles

* format

* missed comment

* some docs for direct

* add note on slashing

* amend

* simplify 'direct' code

* finish up the 'direct' logic

* add a bunch of tests for the direct-in-group logic

* rename 'direct' to 'cluster', begin a candidate_entry module

* distill candidate_entry

* start in on a statement-store module

* some utilities for the statement store

* rewrite 'send_statement_direct' using new tools

* filter sending logic on peers which have the relay-parent in their view.

* some more logic for handling incoming statements

* req/res: BackedCandidatePacket -> AttestedCandidate + tweaks

* add a `validated_in_group` bitfield to BackedCandidateInventory

* BackedCandidateInventory -> Manifest

* start in on requester module

* add outgoing request for attested candidate

* add a priority mechanism for requester

* some request dispatch logic

* add seconded mask to tagged-request

* amend manifest to hold group index

* handle errors and set up scaffold for response validation

* validate attested candidate responses

* requester -> requests

* add some utilities for manipulating requests

* begin integrating requester

* start grid module

* tiny

* refactor grid topology to expose more info to subsystems

* fix grid_topology test

* fix overseer test

* implement topology group-based view construction logic

* fmt

* flesh out grid slightly more

* add indexed groups utility

* integrate Groups into per-session info

* refactor statement store to borrow Groups

* implement manifest knowledge utility

* add a test for topology setup

* don't send to group members

* test for conflicting manifests

* manifest knowledge tests

* fmt

* rename field

* garbage collection for grid tracker

* routines for finding correct/incorrect advertisers

* add manifest import logic

* tweak naming

* more tests for manifest import

* add comment

* rework candidates into a view-wide tracker

* fmt

* start writing boilerplate for grid sending

* fmt

* some more group boilerplate

* refactor handling of topology and authority IDs

* fmt

* send statements directly to grid peers where possible

* send to cluster only if statement belongs to cluster

* improve handling of cluster statements

* handle incoming statements along the grid

* API for introduction of candidates into the tree

* backing: use new prospective parachains API

* fmt prospective parachains changes

* fmt statement-dist

* fix condition

* get ready for tracking importable candidates

* prospective parachains: add Cow logic

* incomplete and complete hypothetical candidates

* remove keep_if_unneeded

* fmt

* implement more general HypotheticalFrontier

* fmt, cleanup

* add a by_parent_hash index to candidate tracker

* more framework for future code

* utilities for getting all hypothetical candidates for frontier

* track origin in statement store

* fmt

* requests should return peer

* apply post-confirmation reckoning

* flesh out import/announce/circulate logic on new statements

* adjust

* adjust TODO comment

* fix  backing tests

* update statement-distribution to use new indexedvec

* fmt

* query hypothetical candidates

* implement `note_importable_under`

* extract common utility of fragment tree updates

* add a helper function for getting statements unknown by backing

* import fresh statements to backing

* send announcements and acknowledgements over grid

* provide freshly importable statements

also avoid tracking backed candidates in statement distribution

* do not issue requests on newly importable candidates

* add TODO for later when confirming candidate

* write a routine for handling backed candidate notifications

* simplify grid substantially

* add some test TODOs

* handle confirmed candidates & grid announcements

* finish implementing manifest handling, including follow up statements

* send follow-up statements when acknowledging freshly backed

* fmt

* handle incoming acknowledgements

* a little DRYing

* wire up network messages to handlers

* fmt

* some skeleton code for peer view update handling

* more peer view skeleton stuff

* Fix async backing statement distribution tests (#6621)

* Fix compile errors in tests

* Cargo fmt

* Resolve some todos in async backing statement-distribution branch (#6482)

* Implement `remove_by_relay_parent`

* Extract `minimum_votes` to shared primitives.

* Add `can_send_statements_received_with_prejudice` test

* Fix test

* Update docstrings

* Cargo fmt

* Fix compile error

* Fix compile errors in tests

* Cargo fmt

* Add module docs; write `test_priority_ordering` (first draft)

* Fix `test_priority_ordering`

* Move `insert_or_update_priority`: `Drop` -> `set_cluster_priority`

* Address review comments

* Remove `Entry::get_mut`

* fix test compilation

* add a TODO for a test

* clean up a couple of TODOs

* implement sending pending cluster statements

* refactor utility function for sending acknowledgement and statements

* mostly implement catching peers up via grid

* Fix clippy error

* alter grid to track all pending statements

* fix more TODOs and format

* tweak a TODO in requests

* some logic for dispatching requests

* fmt

* skeleton for response receiving

* Async backing statement distribution: cluster tests (#6678)

* Add `pending_statements_set_when_receiving_fresh_statements`

* Add `pending_statements_updated_when_sending_statements` test

* fix up

* fmt

* update TODO

* rework seconded mask in requests

* change doc

* change unhandledresponse not to borrow request manager

* only accept responses sufficient to back

* finish implementing response handling

* extract statement filter to protocol crate

* rework requests: use statement filter in network protocol

* dispatch cluster requests correctly

* rework cluster statement sending

* implement request answering

* fmt

* only send confirmed candidate statement messages on unified relay-parent

* Fix Tests In Statement Distribution Branch

* Async Backing: Integrate `vstaging` of statement distribution into `lib.rs` (#6715)

* Integrate `handle_active_leaves_update`

* Integrate `share_local_statement`/`handle_backed_candidate_message`

* Start hooking up request/response flow

* Finish hooking up request/response flow

* Limit number of parallel requests in responder

* Fix test compilation errors

* Fix missing check for prospective parachains mode

* Fix some more compile errors

* clean up some review comments

* clean up warnings

* Async backing statement distribution: grid tests (#6673)

* Add `manifest_import_returns_ok_true` test

* cargo fmt

* Add pending_communication_receiving_manifest_on_confirmed_candidate

* Add `senders_can_provide_manifests_in_acknowledgement` test

* Add a couple of tests for pending statements

* Add `pending_statements_cleared_when_sending` test

* Add `pending_statements_respect_remote_knowledge` test

* Refactor group creation in tests

* Clarify docs

* Address some review comments

* Make some clarifications

* Fix post-merge errors

* Clarify test `senders_can_provide_manifests_in_acknowledgement`

* Try writing `pending_statements_are_updated_after_manifest_exchange`

* Document "seconding limit" and `reject_overflowing_manifests` test

* Test that seconding counts are not updated for validators on error

* Fix tests

* Fix manifest exchange test

* Add more tests in `requests.rs` (#6707)

This resolves remaining TODOs in this file.

* remove outdated inventory terminology

* Async backing statement distribution: `Candidates` tests (#6658)

* Async Backing: Fix clippy errors in statement distribution branch (#6720)

* Integrate `handle_active_leaves_update`

* Integrate `share_local_statement`/`handle_backed_candidate_message`

* Start hooking up request/response flow

* Finish hooking up request/response flow

* Limit number of parallel requests in responder

* Fix test compilation errors

* Fix missing check for prospective parachains mode

* Fix some more compile errors

* Async Backing: Fix clippy errors in statement distribution branch

* Fix some more clippy lints

* add tests module

* fix warnings in existing tests

* create basic test harness

* create a test state struct

* fmt

* create empty cluster & grid modules for tests

* some TODOs for cluster test suite

* describe test-suite for grid logic

* describe request test suite

* fix seconding-limit bug

* Remove extraneous `pub`

This somehow made it into my clippy PR.

* Fix some test compile warnings

* Remove some unneeded `allow`s

* adapt some new test helpers from Marcin

* add helper for activating a gossip topology

* add utility for signing statements

* helpers for connecting/disconnecting peers

* round out network utilities

* fmt

* fix bug in initializing validator-meta

* fix compilation

* implement first cluster test

* TODOs for incoming request tests

* Remove unneeded `make_committed_candidate` helper

* fmt

* some more tests for cluster

* add a TODO about grid senders

* integrate inbound req/res into test harness

* polish off initial cluster test suite

* keep introduce candidate request

* fix tests after introduce candidate request

* fmt

* Add grid protocol to module docs

* Fix comments

* Test `backed_in_path_only: true`

* Update node/network/protocol/src/lib.rs

Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>

* Update node/network/protocol/src/request_response/mod.rs

Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>

* Mark receiver with `vstaging`

* validate grid senders based on manifest kind

* fix mask_seconded/valid

* fix unwanted-mask check

* fix build

* resolve todo on leaf mode

* Unify protocol naming to vstaging

* fmt, fix grid test after topology change

* typo

Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>

* address review

* adjust comment, make easier to understand

* Fix typo

---------

Co-authored-by: Marcin S <marcin@bytedude.com>
Co-authored-by: Marcin S <marcin@realemail.net>
Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Co-authored-by: Chris Sosnin <chris125_@live.com>

* miscellaneous fixes to make asynchronous backing work (#6791)

* propagate network-protocol-staging feature

* add feature to adder-collator as well

* allow collation-generation of occupied cores

* prospective parachains: special treatment for pending availability candidates

* runtime: fetch candidates pending availability

* lazily construct PVD for pending candidates

* fix fallout in prospective parachains hypothetical/select_child

* runtime: enact candidates when creating paras-inherent

* make tests compile

* test pending availability in the scope

* add prospective parachains test

* fix validity constraints leftovers

* drop prints

* Fix typos

---------

Co-authored-by: Chris Sosnin <chris125_@live.com>
Co-authored-by: Marcin S <marcin@realemail.net>

* Remove restart from test (#6840)

* Async Backing: Statement Distribution Tests (#6755)

* start on handling incoming

* split off session info into separate map

* start in on a knowledge tracker

* address some grumbles

* format

* missed comment

* some docs for direct

* add note on slashing

* amend

* simplify 'direct' code

* finish up the 'direct' logic

* add a bunch of tests for the direct-in-group logic

* rename 'direct' to 'cluster', begin a candidate_entry module

* distill candidate_entry

* start in on a statement-store module

* some utilities for the statement store

* rewrite 'send_statement_direct' using new tools

* filter sending logic on peers which have the relay-parent in their view.

* some more logic for handling incoming statements

* req/res: BackedCandidatePacket -> AttestedCandidate + tweaks

* add a `validated_in_group` bitfield to BackedCandidateInventory

* BackedCandidateInventory -> Manifest

* start in on requester module

* add outgoing request for attested candidate

* add a priority mechanism for requester

* some request dispatch logic

* add seconded mask to tagged-request

* amend manifest to hold group index

* handle errors and set up scaffold for response validation

* validate attested candidate responses

* requester -> requests

* add some utilities for manipulating requests

* begin integrating requester

* start grid module

* tiny

* refactor grid topology to expose more info to subsystems

* fix grid_topology test

* fix overseer test

* implement topology group-based view construction logic

* fmt

* flesh out grid slightly more

* add indexed groups utility

* integrate Groups into per-session info

* refactor statement store to borrow Groups

* implement manifest knowledge utility

* add a test for topology setup

* don't send to group members

* test for conflicting manifests

* manifest knowledge tests

* fmt

* rename field

* garbage collection for grid tracker

* routines for finding correct/incorrect advertisers

* add manifest import logic

* tweak naming

* more tests for manifest import

* add comment

* rework candidates into a view-wide tracker

* fmt

* start writing boilerplate for grid sending

* fmt

* some more group boilerplate

* refactor handling of topology and authority IDs

* fmt

* send statements directly to grid peers where possible

* send to cluster only if statement belongs to cluster

* improve handling of cluster statements

* handle incoming statements along the grid

* API for introduction of candidates into the tree

* backing: use new prospective parachains API

* fmt prospective parachains changes

* fmt statement-dist

* fix condition

* get ready for tracking importable candidates

* prospective parachains: add Cow logic

* incomplete and complete hypothetical candidates

* remove keep_if_unneeded

* fmt

* implement more general HypotheticalFrontier

* fmt, cleanup

* add a by_parent_hash index to candidate tracker

* more framework for future code

* utilities for getting all hypothetical candidates for frontier

* track origin in statement store

* fmt

* requests should return peer

* apply post-confirmation reckoning

* flesh out import/announce/circulate logic on new statements

* adjust

* adjust TODO comment

* fix  backing tests

* update statement-distribution to use new indexedvec

* fmt

* query hypothetical candidates

* implement `note_importable_under`

* extract common utility of fragment tree updates

* add a helper function for getting statements unknown by backing

* import fresh statements to backing

* send announcements and acknowledgements over grid

* provide freshly importable statements

also avoid tracking backed candidates in statement distribution

* do not issue requests on newly importable candidates

* add TODO for later when confirming candidate

* write a routine for handling backed candidate notifications

* simplify grid substantially

* add some test TODOs

* handle confirmed candidates & grid announcements

* finish implementing manifest handling, including follow up statements

* send follow-up statements when acknowledging freshly backed

* fmt

* handle incoming acknowledgements

* a little DRYing

* wire up network messages to handlers

* fmt

* some skeleton code for peer view update handling

* more peer view skeleton stuff

* Fix async backing statement distribution tests (#6621)

* Fix compile errors in tests

* Cargo fmt

* Resolve some todos in async backing statement-distribution branch (#6482)

* Implement `remove_by_relay_parent`

* Extract `minimum_votes` to shared primitives.

* Add `can_send_statements_received_with_prejudice` test

* Fix test

* Update docstrings

* Cargo fmt

* Fix compile error

* Fix compile errors in tests

* Cargo fmt

* Add module docs; write `test_priority_ordering` (first draft)

* Fix `test_priority_ordering`

* Move `insert_or_update_priority`: `Drop` -> `set_cluster_priority`

* Address review comments

* Remove `Entry::get_mut`

* fix test compilation

* add a TODO for a test

* clean up a couple of TODOs

* implement sending pending cluster statements

* refactor utility function for sending acknowledgement and statements

* mostly implement catching peers up via grid

* Fix clippy error

* alter grid to track all pending statements

* fix more TODOs and format

* tweak a TODO in requests

* some logic for dispatching requests

* fmt

* skeleton for response receiving

* Async backing statement distribution: cluster tests (#6678)

* Add `pending_statements_set_when_receiving_fresh_statements`

* Add `pending_statements_updated_when_sending_statements` test

* fix up

* fmt

* update TODO

* rework seconded mask in requests

* change doc

* change unhandledresponse not to borrow request manager

* only accept responses sufficient to back

* finish implementing response handling

* extract statement filter to protocol crate

* rework requests: use statement filter in network protocol

* dispatch cluster requests correctly

* rework cluster statement sending

* implement request answering

* fmt

* only send confirmed candidate statement messages on unified relay-parent

* Fix Tests In Statement Distribution Branch

* Async Backing: Integrate `vstaging` of statement distribution into `lib.rs` (#6715)

* Integrate `handle_active_leaves_update`

* Integrate `share_local_statement`/`handle_backed_candidate_message`

* Start hooking up request/response flow

* Finish hooking up request/response flow

* Limit number of parallel requests in responder

* Fix test compilation errors

* Fix missing check for prospective parachains mode

* Fix some more compile errors

* clean up some review comments

* clean up warnings

* Async backing statement distribution: grid tests (#6673)

* Add `manifest_import_returns_ok_true` test

* cargo fmt

* Add pending_communication_receiving_manifest_on_confirmed_candidate

* Add `senders_can_provide_manifests_in_acknowledgement` test

* Add a couple of tests for pending statements

* Add `pending_statements_cleared_when_sending` test

* Add `pending_statements_respect_remote_knowledge` test

* Refactor group creation in tests

* Clarify docs

* Address some review comments

* Make some clarifications

* Fix post-merge errors

* Clarify test `senders_can_provide_manifests_in_acknowledgement`

* Try writing `pending_statements_are_updated_after_manifest_exchange`

* Document "seconding limit" and `reject_overflowing_manifests` test

* Test that seconding counts are not updated for validators on error

* Fix tests

* Fix manifest exchange test

* Add more tests in `requests.rs` (#6707)

This resolves remaining TODOs in this file.

* remove outdated inventory terminology

* Async backing statement distribution: `Candidates` tests (#6658)

* Async Backing: Fix clippy errors in statement distribution branch (#6720)

* Integrate `handle_active_leaves_update`

* Integrate `share_local_statement`/`handle_backed_candidate_message`

* Start hooking up request/response flow

* Finish hooking up request/response flow

* Limit number of parallel requests in responder

* Fix test compilation errors

* Fix missing check for prospective parachains mode

* Fix some more compile errors

* Async Backing: Fix clippy errors in statement distribution branch

* Fix some more clippy lints

* add tests module

* fix warnings in existing tests

* create basic test harness

* create a test state struct

* fmt

* create empty cluster & grid modules for tests

* some TODOs for cluster test suite

* describe test-suite for grid logic

* describe request test suite

* fix seconding-limit bug

* Remove extraneous `pub`

This somehow made it into my clippy PR.

* Fix some test compile warnings

* Remove some unneeded `allow`s

* adapt some new test helpers from Marcin

* add helper for activating a gossip topology

* add utility for signing statements

* helpers for connecting/disconnecting peers

* round out network utilities

* fmt

* fix bug in initializing validator-meta

* fix compilation

* implement first cluster test

* TODOs for incoming request tests

* Remove unneeded `make_committed_candidate` helper

* fmt

* Hook up request sender

* Add `valid_statement_without_prior_seconded_is_ignored` test

* Fix `valid_statement_without_prior_seconded_is_ignored` test

* some more tests for cluster

* add a TODO about grid senders

* integrate inbound req/res into test harness

* polish off initial cluster test suite

* keep introduce candidate request

* fix tests after introduce candidate request

* fmt

* Add grid protocol to module docs

* Remove obsolete test

* Fix comments

* Test `backed_in_path_only: true`

* Update node/network/protocol/src/lib.rs

Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>

* Update node/network/protocol/src/request_response/mod.rs

Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>

* Mark receiver with `vstaging`

* First draft of `ensure_seconding_limit_is_respected` test

* validate grid senders based on manifest kind

* fix mask_seconded/valid

* fix unwanted-mask check

* fix build

* resolve todo on leaf mode

* Unify protocol naming to vstaging

* Fix `ensure_seconding_limit_is_respected` test

* Start `backed_candidate_leads_to_advertisement` test

* fmt, fix grid test after topology change

* Send Backed notification

* Finish `backed_candidate_leads_to_advertisement` test

* Finish `peer_reported_for_duplicate_statements` test

* Finish `received_advertisement_before_confirmation_leads_to_request`

* Add `advertisements_rejected_from_incorrect_peers` test

* Add `manifest_rejected_*` tests

* Add `manifest_rejected_when_group_does_not_match_para` test

* Add `local_node_sanity_checks_incoming_requests` test

* Add `local_node_respects_statement_mask` test

* Add tests where peer is reported for providing invalid signatures

* Add `cluster_peer_allowed_to_send_incomplete_statements` test

* Add `received_advertisement_after_backing_leads_to_acknowledgement`

* Add `received_advertisement_after_confirmation_before_backing` test

* peer_reported_for_advertisement_conflicting_with_confirmed_candidate

* Add `peer_reported_for_not_enough_statements` test

* Add `peer_reported_for_providing_statements_meant_to_be_masked_out`

* Add `additional_statements_are_shared_after_manifest_exchange`

* Add `grid_statements_imported_to_backing` test

* Add `relay_parent_entering_peer_view_leads_to_advertisement` test

* Add `advertisement_not_re_sent_when_peer_re_enters_view` test

* Update node/network/statement-distribution/src/vstaging/tests/grid.rs

Co-authored-by: asynchronous rob <rphmeier@gmail.com>

* Resolve TODOs, update test

* Address unused code

* Add check after every test for unhandled requests

* Refactor (`make_dummy_leaf` and `handle_sent_request`)

* Refactor (`make_dummy_topology`)

* Minor refactor

---------

Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Co-authored-by: Chris Sosnin <chris125_@live.com>

* Fix some clippy lints in tests

* Async backing: minor fixes (#6920)

* bitfield-distribution test

* implicit view tests

* Refactor parameters -> params

* scheduler: update storage migration (#6963)

* update scheduler migration

* Adjust weight to account for storage read

* Statement Distribution Guide Edits (#7025)

* Statement distribution guide edits

* Addressed Marcin's comments

* Add attested candidate request retry timeouts (#6833)

Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Co-authored-by: asynchronous rob <rphmeier@gmail.com>
Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
Co-authored-by: Chris Sosnin <chris125_@live.com>
Fix async backing statement distribution tests (#6621)
Resolve some todos in async backing statement-distribution branch (#6482)
Fix clippy errors in statement distribution branch (#6720)

* Async backing: add Prospective Parachains impl guide (#6933)

Co-authored-by: Bradley Olson <34992650+BradleyOlson64@users.noreply.github.com>

* Updates to Provisioner Guide for Async Backing (#7106)

* Initial corrections and clarifications

* Partial first draft

* Finished first draft

* Adding back wrongly removed test bit

* fmt

* Update roadmap/implementers-guide/src/node/utility/provisioner.md

Co-authored-by: Marcin S. <marcin@realemail.net>

* Addressing comments

* Reorganization

* fmt

---------

Co-authored-by: Marcin S. <marcin@realemail.net>

* fmt

* Renaming Parathread Mentions (#7287)

* Renaming parathreads

* Renaming module to pallet

* More updates

* PVF: Refactor workers into separate crates, remove host dependency (#7253)

* PVF: Refactor workers into separate crates, remove host dependency

* Fix compile error

* Remove some leftover code

* Fix compile errors

* Update Cargo.lock

* Remove worker main.rs files

I accidentally copied these from the other PR. This PR isn't intended to
introduce standalone workers yet.

* Address review comments

* cargo fmt

* Update a couple of comments

* Update log targets

* Update quote to 1.0.27 (#7280)

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

* pallets: implement `Default` for `GenesisConfig` in `no_std` (#7271)

* pallets: implement Default for GenesisConfig in no_std

This change is follow-up of: https://github.com/paritytech/substrate/pull/14108

It is a step towards: https://github.com/paritytech/substrate/issues/13334

* Cargo.lock updated

* update lockfile for {"substrate"}

---------

Co-authored-by: parity-processbot <>

* cli: enable BEEFY by default on test networks (#7293)

We consider BEEFY mature enough to run by default on all nodes
for test networks (Rococo/Wococo/Versi).

Right now, most nodes are not running it since it's opt-in using
--beefy flag. Switch to an opt-out model for test networks.

Replace --beefy flag from CLI with --no-beefy and have BEEFY
client start by default on test networks.

Signed-off-by: acatangiu <adrian@parity.io>

* runtime: past session slashing runtime API (#6667)

* runtime/vstaging: unapplied_slashes runtime API

* runtime/vstaging: key_ownership_proof runtime API

* runtime/ParachainHost: submit_report_dispute_lost

* fix key_ownership_proof API

* runtime: submit_report_dispute_lost runtime API

* nits

* Update node/subsystem-types/src/messages.rs

Co-authored-by: Marcin S. <marcin@bytedude.com>

* revert unrelated fmt changes

* post merge fixes

* fix compilation

---------

Co-authored-by: Marcin S. <marcin@bytedude.com>

* Correcting git mishap

* Document usage of `gum` crate (#7294)

* Document usage of gum crate

* Small fix

* Add some more basic info

* Update node/gum/src/lib.rs

Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>

* Update target docs

---------

Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>

* XCM: Fix issue with RequestUnlock (#7278)

* XCM: Fix issue with RequestUnlock

* Leave API changes for v4

* Fix clippy errors

* Fix tests

---------

Co-authored-by: parity-processbot <>

* Companion for Substrate#14228 (#7295)

* Companion for Substrate#14228

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

* update lockfile for {"substrate"}

---------

Co-authored-by: parity-processbot <>

* Companion for #14237: Use latest sp-crates (#7300)

* To revert: Update substrate branch to "lexnv/bump_sp_crates"

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Revert "To revert: Update substrate branch to "lexnv/bump_sp_crates""

This reverts commit 5f1db84eac4a226c37b7f6ce6ee19b49dc7e2008.

* Update cargo lock

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update cargo.lock

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update cargo.lock

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* bounded-collections bump to 0.1.7 (#7305)

* bounded-collections bump to 0.1.7

Companion for: paritytech/substrate#14225

* update lockfile for {"substrate"}

---------

Co-authored-by: parity-processbot <>

* bump to quote 1.0.28 (#7306)

* `RollingSessionWindow` cleanup (#7204)

* Replace `RollingSessionWindow` with `RuntimeInfo` - initial commit

* Fix tests in import

* Fix the rest of the tests

* Remove dead code

* Fix todos

* Simplify session caching

* Comments for `SessionInfoProvider`

* Separate `SessionInfoProvider` from `State`

* `cache_session_info_for_head` becomes freestanding function

* Remove unneeded `mut` usage

* fn session_info -> fn get_session_info() to avoid name clashes. The function also tries to initialize `SessionInfoProvider`

* Fix SessionInfo retrieval

* Code cleanup

* Don't wrap `SessionInfoProvider` in an `Option`

* Remove `earliest_session()`

* Remove pre-caching -> wip

* Fix some tests and code cleanup

* Fix all tests

* Fixes in tests

* Fix comments, variable names and small style changes

* Fix a warning

* impl From<SessionWindowSize> for NonZeroUsize

* Fix logging for `get_session_info` - remove redundant logs and decrease log level to DEBUG

* Code review feedback

* Storage migration removing `COL_SESSION_WINDOW_DATA` from parachains db

* Remove `col_session_data` usages

* Storage migration clearing columns w/o removing them

* Remove session data column usages from `approval-voting` and `dispute-coordinator` tests

* Add some test cases from `RollingSessionWindow` to `dispute-coordinator` tests

* Fix formatting in initialized.rs

* Fix a corner case in `SessionInfo` caching for `dispute-coordinator`

* Remove `RollingSessionWindow` ;(

* Revert "Fix formatting in initialized.rs"

This reverts commit 0f94664ec9f3a7e3737a30291195990e1e7065fc.

* v2 to v3 migration drops `COL_DISPUTE_COORDINATOR_DATA` instead of clearing it

* Fix `NUM_COLUMNS` in `approval-voting`

* Use `columns::v3::NUM_COLUMNS` when opening db

* Update node/service/src/parachains_db/upgrade.rs

Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>

* Don't write in `COL_DISPUTE_COORDINATOR_DATA` for `test_rocksdb_migrate_2_to_3`

* Fix `NUM+COLUMNS` in approval_voting

* Fix formatting

* Fix columns usage

* Clarification comments about the different db versions

---------

Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>

* pallet-para-config: Remove remnant WeightInfo functions (#7308)

* pallet-para-config: Remove remnant WeightInfo functions

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

* set_config_with_weight begone

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

* ".git/.scripts/commands/bench/bench.sh" runtime kusama-dev runtime_parachains::configuration

---------

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

* XCM: PayOverXcm config (#6900)

* Move XCM query functionality to trait

* Fix tests

* Add PayOverXcm implementation

* fix the PayOverXcm trait to compile

* moved doc comment out of trait implmeentation and to the trait

* PayOverXCM documentation

* Change documentation a bit

* Added empty benchmark methods implementation and changed docs

* update PayOverXCM to convert AccountIds to MultiLocations

* Implement benchmarking method

* Change v3 to latest

* Descend origin to an asset sender (#6970)

* descend origin to an asset sender

* sender as tuple of dest and sender

* Add more variants to the QueryResponseStatus enum

* Change Beneficiary to Into<[u8; 32]>

* update PayOverXcm to return concrete errors and use AccountId as sender

* use polkadot-primitives for AccountId

* fix dependency to use polkadot-core-primitives

* force Unpaid instruction to the top of the instructions list

* modify report_outcome to accept interior argument

* use new_query directly for building final xcm query, instead of report_outcome

* fix usage of new_query to use the XcmQueryHandler

* fix usage of new_query to use the XcmQueryHandler

* tiny method calling fix

* xcm query handler (#7198)

* drop redundant query status

* rename ReportQueryStatus to OuterQueryStatus

* revert rename of QueryResponseStatus

* update mapping

* Update xcm/xcm-builder/src/pay.rs

Co-authored-by: Gavin Wood <gavin@parity.io>

* Updates

* Docs

* Fix benchmarking stuff

* Destination can be determined based on asset_kind

* Tweaking API to minimise clones

* Some repotting and docs

---------

Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: Anthony Alaribe <anthony.alaribe@parity.io>
Co-authored-by: Gavin Wood <gavin@parity.io>

* Companion for #14265 (#7307)

* Update Cargo.lock

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update Cargo.lock

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: parity-processbot <>

* bump serde to 1.0.163 (#7315)

* bump serde to 1.0.163

* bump ci

* update lockfile for {"substrate"}

---------

Co-authored-by: parity-processbot <>

* fmt

* Updated fmt

* Removing changes accidentally pulled from master

* fix another master pull issue

* Another master pull fix

* fmt

* Fixing implementers guide build

* Revert "Merge branch 'rh-async-backing-feature-while-frozen' of https://github.com/paritytech/polkadot into brad-rename-parathread"

This reverts commit bebc24af52ab61155e3fe02cb3ce66a592bce49c, reversing
changes made to 1b2de662dfb11173679d6da5bd0da9d149c85547.

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Signed-off-by: acatangiu <adrian@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Marcin S <marcin@realemail.net>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com>
Co-authored-by: Adrian Catangiu <adrian@parity.io>
Co-authored-by: ordian <write@reusable.software>
Co-authored-by: Marcin S. <marcin@bytedude.com>
Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>
Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: Anthony Alaribe <anthony.alaribe@parity.io>
Co-authored-by: Gavin Wood <gavin@parity.io>

* fix bitfield distribution test

* approval distribution tests

* fix bridge tests

* update Cargo.lock

* [async-backing-branch] Optimize collator-protocol validator-side request fetching (#7457)

* Optimize collator-protocol validator-side request fetching

* address feedback: replace tuples with structs

* feedback: add doc comments

* move collation types to subfolder

---------

Signed-off-by: alindima <alin@parity.io>

* Update collation generation for asynchronous backing (#7405)

* break candidate receipt construction and distribution into own function

* update implementers' guide to include SubmitCollation

* implement SubmitCollation for collation-generation

* fmt

* fix test compilation & remove unnecessary submodule

* add some TODOs for a test suite.

* Update roadmap/implementers-guide/src/types/overseer-protocol.md

Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>

* add new test harness and first test

* refactor to avoid requiring background sender

* ensure collation gets packaged and distributed

* tests for the fallback case with no hint

* add parent rp-number hint tests

* fmt

* update uses of CollationGenerationConfig

* fix remaining test

* address review comments

* use subsystemsender for background tasks

* fmt

* remove ValidationCodeHashHint and related tests

---------

Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>

* fix some more fallout from merge

* fmt

* remove staging APIs from Rococo & Westend (#7513)

* send network messages on main protocol name (#7515)

* misc async backing improvements for allowed ancestry blocks (#7532)

* shared: fix acquire_info

* backwards-compat test for prospective parachains

* same relay parent is allowed

* provisioner: request candidate receipt by relay parent (#7527)

* return candidates hash from prospective parachains

* update provisioner

* update tests

* guide changes

* send a single message to backing

* fix test

* revert to old `handle_new_activations` logic in some cases (#7514)

* revert to old `handle_new_activations` logic

* gate sending messages on scheduled cores to max_depth >= 2

* fmt

* 2->1

* Omnibus asynchronous backing bugfix PR (#7529)

* fix a bug in backing

* add some more logs

* prospective parachains: take ancestry only up to session bounds

* add test

* fix zombienet tests (#7614)

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

* fix runtime compilation

* make bitfield distribution tests compile

* attempt to fix zombienet disputes (#7618)

* update metric name

* update some metric names

* avoid cycles when creating fake candidates

* make undying collator more friendly to malformed parents

* fix a bug in malus

* fmt

* clippy

* add RUN_IN_CONTAINER to new ZombieNet tests (#7631)

* remove duplicated migration

happened because of master-merge

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Signed-off-by: acatangiu <adrian@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: alindima <alin@parity.io>
Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Co-authored-by: Chris Sosnin <chris125_@live.com>
Co-authored-by: Parity Bot <admin@parity.io>
Co-authored-by: Chris Sosnin <48099298+slumber@users.noreply.github.com>
Co-authored-by: Robert Klotzner <robert.klotzner@gmx.at>
Co-authored-by: Robert Klotzner <eskimor@users.noreply.github.com>
Co-authored-by: Marcin S <marcin@bytedude.com>
Co-authored-by: Marcin S <marcin@realemail.net>
Co-authored-by: Mattia L.V. Bradascio <28816406+bredamatt@users.noreply.github.com>
Co-authored-by: Bradley Olson <34992650+BradleyOlson64@users.noreply.github.com>
Co-authored-by: alexgparity <115470171+alexgparity@users.noreply.github.com>
Co-authored-by: BradleyOlson64 <lotrftw9@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com>
Co-authored-by: Adrian Catangiu <adrian@parity.io>
Co-authored-by: ordian <write@reusable.software>
Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com>
Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: Tsvetomir Dimitrov <tsvetomir@parity.io>
Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: Anthony Alaribe <anthony.alaribe@parity.io>
Co-authored-by: Gavin Wood <gavin@parity.io>
Co-authored-by: Alin Dima <alin@parity.io>
This commit is contained in:
asynchronous rob
2023-08-18 11:11:56 -05:00
committed by GitHub
parent ad539f0e41
commit 5174b9d2d7
175 changed files with 40882 additions and 6462 deletions
+288 -210
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -96,6 +96,7 @@ members = [
"node/core/chain-selection", "node/core/chain-selection",
"node/core/dispute-coordinator", "node/core/dispute-coordinator",
"node/core/parachains-inherent", "node/core/parachains-inherent",
"node/core/prospective-parachains",
"node/core/provisioner", "node/core/provisioner",
"node/core/pvf", "node/core/pvf",
"node/core/pvf/common", "node/core/pvf/common",
@@ -226,6 +227,8 @@ fast-runtime = [ "polkadot-cli/fast-runtime" ]
runtime-metrics = [ "polkadot-cli/runtime-metrics" ] runtime-metrics = [ "polkadot-cli/runtime-metrics" ]
pyroscope = ["polkadot-cli/pyroscope"] pyroscope = ["polkadot-cli/pyroscope"]
jemalloc-allocator = ["polkadot-node-core-pvf-prepare-worker/jemalloc-allocator", "polkadot-overseer/jemalloc-allocator"] jemalloc-allocator = ["polkadot-node-core-pvf-prepare-worker/jemalloc-allocator", "polkadot-overseer/jemalloc-allocator"]
network-protocol-staging = ["polkadot-cli/network-protocol-staging"]
# Enables timeout-based tests supposed to be run only in CI environment as they may be flaky # Enables timeout-based tests supposed to be run only in CI environment as they may be flaky
# when run locally depending on system load # when run locally depending on system load
ci-only-tests = ["polkadot-node-core-pvf/ci-only-tests"] ci-only-tests = ["polkadot-node-core-pvf/ci-only-tests"]
+1
View File
@@ -75,3 +75,4 @@ rococo-native = ["service/rococo-native"]
malus = ["full-node", "service/malus"] malus = ["full-node", "service/malus"]
runtime-metrics = ["service/runtime-metrics", "polkadot-node-metrics/runtime-metrics"] runtime-metrics = ["service/runtime-metrics", "polkadot-node-metrics/runtime-metrics"]
network-protocol-staging = ["service/network-protocol-staging"]
@@ -21,3 +21,5 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [
[dev-dependencies] [dev-dependencies]
polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" } polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" }
test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" }
assert_matches = "1.4.0"
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
+264 -135
View File
@@ -31,21 +31,25 @@
#![deny(missing_docs)] #![deny(missing_docs)]
use futures::{channel::mpsc, future::FutureExt, join, select, sink::SinkExt, stream::StreamExt}; use futures::{channel::oneshot, future::FutureExt, join, select};
use parity_scale_codec::Encode; use parity_scale_codec::Encode;
use polkadot_node_primitives::{AvailableData, CollationGenerationConfig, PoV}; use polkadot_node_primitives::{
AvailableData, Collation, CollationGenerationConfig, CollationSecondedSignal, PoV,
SubmitCollationParams,
};
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
messages::{CollationGenerationMessage, CollatorProtocolMessage}, messages::{CollationGenerationMessage, CollatorProtocolMessage},
overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, RuntimeApiError, SpawnedSubsystem,
SubsystemContext, SubsystemError, SubsystemResult, SubsystemContext, SubsystemError, SubsystemResult,
}; };
use polkadot_node_subsystem_util::{ use polkadot_node_subsystem_util::{
request_availability_cores, request_persisted_validation_data, request_validation_code, request_availability_cores, request_persisted_validation_data,
request_validation_code_hash, request_validators, request_staging_async_backing_params, request_validation_code, request_validation_code_hash,
request_validators,
}; };
use polkadot_primitives::{ use polkadot_primitives::{
collator_signature_payload, CandidateCommitments, CandidateDescriptor, CandidateReceipt, collator_signature_payload, CandidateCommitments, CandidateDescriptor, CandidateReceipt,
CoreState, Hash, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, CollatorPair, CoreState, Hash, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData,
ValidationCodeHash, ValidationCodeHash,
}; };
use sp_core::crypto::Pair; use sp_core::crypto::Pair;
@@ -86,26 +90,13 @@ impl CollationGenerationSubsystem {
/// If `err_tx` is not `None`, errors are forwarded onto that channel as they occur. /// If `err_tx` is not `None`, errors are forwarded onto that channel as they occur.
/// Otherwise, most are logged and then discarded. /// Otherwise, most are logged and then discarded.
async fn run<Context>(mut self, mut ctx: Context) { async fn run<Context>(mut self, mut ctx: Context) {
// when we activate new leaves, we spawn a bunch of sub-tasks, each of which is
// expected to generate precisely one message. We don't want to block the main loop
// at any point waiting for them all, so instead, we create a channel on which they can
// send those messages. We can then just monitor the channel and forward messages on it
// to the overseer here, via the context.
let (sender, receiver) = mpsc::channel(0);
let mut receiver = receiver.fuse();
loop { loop {
select! { select! {
incoming = ctx.recv().fuse() => { incoming = ctx.recv().fuse() => {
if self.handle_incoming::<Context>(incoming, &mut ctx, &sender).await { if self.handle_incoming::<Context>(incoming, &mut ctx).await {
break; break;
} }
}, },
msg = receiver.next() => {
if let Some(msg) = msg {
ctx.send_message(msg).await;
}
},
} }
} }
} }
@@ -119,7 +110,6 @@ impl CollationGenerationSubsystem {
&mut self, &mut self,
incoming: SubsystemResult<FromOrchestra<<Context as SubsystemContext>::Message>>, incoming: SubsystemResult<FromOrchestra<<Context as SubsystemContext>::Message>>,
ctx: &mut Context, ctx: &mut Context,
sender: &mpsc::Sender<overseer::CollationGenerationOutgoingMessages>,
) -> bool { ) -> bool {
match incoming { match incoming {
Ok(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate { Ok(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
@@ -134,7 +124,6 @@ impl CollationGenerationSubsystem {
activated.into_iter().map(|v| v.hash), activated.into_iter().map(|v| v.hash),
ctx, ctx,
metrics, metrics,
sender,
) )
.await .await
{ {
@@ -155,6 +144,21 @@ impl CollationGenerationSubsystem {
} }
false false
}, },
Ok(FromOrchestra::Communication {
msg: CollationGenerationMessage::SubmitCollation(params),
}) => {
if let Some(config) = &self.config {
if let Err(err) =
handle_submit_collation(params, config, ctx, &self.metrics).await
{
gum::error!(target: LOG_TARGET, ?err, "Failed to submit collation");
}
} else {
gum::error!(target: LOG_TARGET, "Collation submitted before initialization");
}
false
},
Ok(FromOrchestra::Signal(OverseerSignal::BlockFinalized(..))) => false, Ok(FromOrchestra::Signal(OverseerSignal::BlockFinalized(..))) => false,
Err(err) => { Err(err) => {
gum::error!( gum::error!(
@@ -188,23 +192,28 @@ async fn handle_new_activations<Context>(
activated: impl IntoIterator<Item = Hash>, activated: impl IntoIterator<Item = Hash>,
ctx: &mut Context, ctx: &mut Context,
metrics: Metrics, metrics: Metrics,
sender: &mpsc::Sender<overseer::CollationGenerationOutgoingMessages>,
) -> crate::error::Result<()> { ) -> crate::error::Result<()> {
// follow the procedure from the guide: // follow the procedure from the guide:
// https://paritytech.github.io/polkadot/book/node/collators/collation-generation.html // https://paritytech.github.io/polkadot/book/node/collators/collation-generation.html
if config.collator.is_none() {
return Ok(())
}
let _overall_timer = metrics.time_new_activations(); let _overall_timer = metrics.time_new_activations();
for relay_parent in activated { for relay_parent in activated {
let _relay_parent_timer = metrics.time_new_activations_relay_parent(); let _relay_parent_timer = metrics.time_new_activations_relay_parent();
let (availability_cores, validators) = join!( let (availability_cores, validators, async_backing_params) = join!(
request_availability_cores(relay_parent, ctx.sender()).await, request_availability_cores(relay_parent, ctx.sender()).await,
request_validators(relay_parent, ctx.sender()).await, request_validators(relay_parent, ctx.sender()).await,
request_staging_async_backing_params(relay_parent, ctx.sender()).await,
); );
let availability_cores = availability_cores??; let availability_cores = availability_cores??;
let n_validators = validators??.len(); let n_validators = validators??.len();
let async_backing_params = async_backing_params?.ok();
for (core_idx, core) in availability_cores.into_iter().enumerate() { for (core_idx, core) in availability_cores.into_iter().enumerate() {
let _availability_core_timer = metrics.time_new_activations_availability_core(); let _availability_core_timer = metrics.time_new_activations_availability_core();
@@ -212,15 +221,30 @@ async fn handle_new_activations<Context>(
let (scheduled_core, assumption) = match core { let (scheduled_core, assumption) = match core {
CoreState::Scheduled(scheduled_core) => CoreState::Scheduled(scheduled_core) =>
(scheduled_core, OccupiedCoreAssumption::Free), (scheduled_core, OccupiedCoreAssumption::Free),
CoreState::Occupied(_occupied_core) => { CoreState::Occupied(occupied_core) => match async_backing_params {
// TODO: https://github.com/paritytech/polkadot/issues/1573 Some(params) if params.max_candidate_depth >= 1 => {
gum::trace!( // maximum candidate depth when building on top of a block
target: LOG_TARGET, // pending availability is necessarily 1 - the depth of the
core_idx = %core_idx, // pending block is 0 so the child has depth 1.
relay_parent = ?relay_parent,
"core is occupied. Keep going.", // TODO [now]: this assumes that next up == current.
); // in practice we should only set `OccupiedCoreAssumption::Included`
continue // when the candidate occupying the core is also of the same para.
if let Some(scheduled) = occupied_core.next_up_on_available {
(scheduled, OccupiedCoreAssumption::Included)
} else {
continue
}
},
_ => {
gum::trace!(
target: LOG_TARGET,
core_idx = %core_idx,
relay_parent = ?relay_parent,
"core is occupied. Keep going.",
);
continue
},
}, },
CoreState::Free => { CoreState::Free => {
gum::trace!( gum::trace!(
@@ -271,7 +295,7 @@ async fn handle_new_activations<Context>(
}, },
}; };
let validation_code_hash = match obtain_current_validation_code_hash( let validation_code_hash = match obtain_validation_code_hash_with_assumption(
relay_parent, relay_parent,
scheduled_core.para_id, scheduled_core.para_id,
assumption, assumption,
@@ -294,15 +318,18 @@ async fn handle_new_activations<Context>(
}; };
let task_config = config.clone(); let task_config = config.clone();
let mut task_sender = sender.clone();
let metrics = metrics.clone(); let metrics = metrics.clone();
let mut task_sender = ctx.sender().clone();
ctx.spawn( ctx.spawn(
"collation-builder", "collation-builder",
Box::pin(async move { Box::pin(async move {
let persisted_validation_data_hash = validation_data.hash(); let collator_fn = match task_config.collator.as_ref() {
Some(x) => x,
None => return,
};
let (collation, result_sender) = let (collation, result_sender) =
match (task_config.collator)(relay_parent, &validation_data).await { match collator_fn(relay_parent, &validation_data).await {
Some(collation) => collation.into_inner(), Some(collation) => collation.into_inner(),
None => { None => {
gum::debug!( gum::debug!(
@@ -314,104 +341,21 @@ async fn handle_new_activations<Context>(
}, },
}; };
// Apply compression to the block data. construct_and_distribute_receipt(
let pov = { PreparedCollation {
let pov = collation.proof_of_validity.into_compressed(); collation,
let encoded_size = pov.encoded_size();
// As long as `POV_BOMB_LIMIT` is at least `max_pov_size`, this ensures
// that honest collators never produce a PoV which is uncompressed.
//
// As such, honest collators never produce an uncompressed PoV which starts
// with a compression magic number, which would lead validators to reject
// the collation.
if encoded_size > validation_data.max_pov_size as usize {
gum::debug!(
target: LOG_TARGET,
para_id = %scheduled_core.para_id,
size = encoded_size,
max_size = validation_data.max_pov_size,
"PoV exceeded maximum size"
);
return
}
pov
};
let pov_hash = pov.hash();
let signature_payload = collator_signature_payload(
&relay_parent,
&scheduled_core.para_id,
&persisted_validation_data_hash,
&pov_hash,
&validation_code_hash,
);
let erasure_root =
match erasure_root(n_validators, validation_data, pov.clone()) {
Ok(erasure_root) => erasure_root,
Err(err) => {
gum::error!(
target: LOG_TARGET,
para_id = %scheduled_core.para_id,
err = ?err,
"failed to calculate erasure root",
);
return
},
};
let commitments = CandidateCommitments {
upward_messages: collation.upward_messages,
horizontal_messages: collation.horizontal_messages,
new_validation_code: collation.new_validation_code,
head_data: collation.head_data,
processed_downward_messages: collation.processed_downward_messages,
hrmp_watermark: collation.hrmp_watermark,
};
let ccr = CandidateReceipt {
commitments_hash: commitments.hash(),
descriptor: CandidateDescriptor {
signature: task_config.key.sign(&signature_payload),
para_id: scheduled_core.para_id, para_id: scheduled_core.para_id,
relay_parent, relay_parent,
collator: task_config.key.public(), validation_data,
persisted_validation_data_hash,
pov_hash,
erasure_root,
para_head: commitments.head_data.hash(),
validation_code_hash, validation_code_hash,
n_validators,
}, },
}; task_config.key.clone(),
&mut task_sender,
gum::debug!( result_sender,
target: LOG_TARGET, &metrics,
candidate_hash = ?ccr.hash(), )
?pov_hash, .await;
?relay_parent,
para_id = %scheduled_core.para_id,
"candidate is generated",
);
metrics.on_collation_generated();
if let Err(err) = task_sender
.send(
CollatorProtocolMessage::DistributeCollation(ccr, pov, result_sender)
.into(),
)
.await
{
gum::warn!(
target: LOG_TARGET,
para_id = %scheduled_core.para_id,
err = ?err,
"failed to send collation result",
);
}
}), }),
)?; )?;
} }
@@ -420,14 +364,199 @@ async fn handle_new_activations<Context>(
Ok(()) Ok(())
} }
async fn obtain_current_validation_code_hash( #[overseer::contextbounds(CollationGeneration, prefix = self::overseer)]
async fn handle_submit_collation<Context>(
params: SubmitCollationParams,
config: &CollationGenerationConfig,
ctx: &mut Context,
metrics: &Metrics,
) -> crate::error::Result<()> {
let _timer = metrics.time_submit_collation();
let SubmitCollationParams {
relay_parent,
collation,
parent_head,
validation_code_hash,
result_sender,
} = params;
let validators = request_validators(relay_parent, ctx.sender()).await.await??;
let n_validators = validators.len();
// We need to swap the parent-head data, but all other fields here will be correct.
let mut validation_data = match request_persisted_validation_data(
relay_parent,
config.para_id,
OccupiedCoreAssumption::TimedOut,
ctx.sender(),
)
.await
.await??
{
Some(v) => v,
None => {
gum::debug!(
target: LOG_TARGET,
relay_parent = ?relay_parent,
our_para = %config.para_id,
"No validation data for para - does it exist at this relay-parent?",
);
return Ok(())
},
};
validation_data.parent_head = parent_head;
let collation = PreparedCollation {
collation,
relay_parent,
para_id: config.para_id,
validation_data,
validation_code_hash,
n_validators,
};
construct_and_distribute_receipt(
collation,
config.key.clone(),
ctx.sender(),
result_sender,
metrics,
)
.await;
Ok(())
}
struct PreparedCollation {
collation: Collation,
para_id: ParaId,
relay_parent: Hash,
validation_data: PersistedValidationData,
validation_code_hash: ValidationCodeHash,
n_validators: usize,
}
/// Takes a prepared collation, along with its context, and produces a candidate receipt
/// which is distributed to validators.
async fn construct_and_distribute_receipt(
collation: PreparedCollation,
key: CollatorPair,
sender: &mut impl overseer::CollationGenerationSenderTrait,
result_sender: Option<oneshot::Sender<CollationSecondedSignal>>,
metrics: &Metrics,
) {
let PreparedCollation {
collation,
para_id,
relay_parent,
validation_data,
validation_code_hash,
n_validators,
} = collation;
let persisted_validation_data_hash = validation_data.hash();
let parent_head_data_hash = validation_data.parent_head.hash();
// Apply compression to the block data.
let pov = {
let pov = collation.proof_of_validity.into_compressed();
let encoded_size = pov.encoded_size();
// As long as `POV_BOMB_LIMIT` is at least `max_pov_size`, this ensures
// that honest collators never produce a PoV which is uncompressed.
//
// As such, honest collators never produce an uncompressed PoV which starts with
// a compression magic number, which would lead validators to reject the collation.
if encoded_size > validation_data.max_pov_size as usize {
gum::debug!(
target: LOG_TARGET,
para_id = %para_id,
size = encoded_size,
max_size = validation_data.max_pov_size,
"PoV exceeded maximum size"
);
return
}
pov
};
let pov_hash = pov.hash();
let signature_payload = collator_signature_payload(
&relay_parent,
&para_id,
&persisted_validation_data_hash,
&pov_hash,
&validation_code_hash,
);
let erasure_root = match erasure_root(n_validators, validation_data, pov.clone()) {
Ok(erasure_root) => erasure_root,
Err(err) => {
gum::error!(
target: LOG_TARGET,
para_id = %para_id,
err = ?err,
"failed to calculate erasure root",
);
return
},
};
let commitments = CandidateCommitments {
upward_messages: collation.upward_messages,
horizontal_messages: collation.horizontal_messages,
new_validation_code: collation.new_validation_code,
head_data: collation.head_data,
processed_downward_messages: collation.processed_downward_messages,
hrmp_watermark: collation.hrmp_watermark,
};
let ccr = CandidateReceipt {
commitments_hash: commitments.hash(),
descriptor: CandidateDescriptor {
signature: key.sign(&signature_payload),
para_id,
relay_parent,
collator: key.public(),
persisted_validation_data_hash,
pov_hash,
erasure_root,
para_head: commitments.head_data.hash(),
validation_code_hash,
},
};
gum::debug!(
target: LOG_TARGET,
candidate_hash = ?ccr.hash(),
?pov_hash,
?relay_parent,
para_id = %para_id,
"candidate is generated",
);
metrics.on_collation_generated();
sender
.send_message(CollatorProtocolMessage::DistributeCollation(
ccr,
parent_head_data_hash,
pov,
result_sender,
))
.await;
}
async fn obtain_validation_code_hash_with_assumption(
relay_parent: Hash, relay_parent: Hash,
para_id: ParaId, para_id: ParaId,
assumption: OccupiedCoreAssumption, assumption: OccupiedCoreAssumption,
sender: &mut impl overseer::CollationGenerationSenderTrait, sender: &mut impl overseer::CollationGenerationSenderTrait,
) -> Result<Option<ValidationCodeHash>, crate::error::Error> { ) -> crate::error::Result<Option<ValidationCodeHash>> {
use polkadot_node_subsystem::RuntimeApiError;
match request_validation_code_hash(relay_parent, para_id, assumption, sender) match request_validation_code_hash(relay_parent, para_id, assumption, sender)
.await .await
.await? .await?
@@ -22,6 +22,7 @@ pub(crate) struct MetricsInner {
pub(crate) new_activations_overall: prometheus::Histogram, pub(crate) new_activations_overall: prometheus::Histogram,
pub(crate) new_activations_per_relay_parent: prometheus::Histogram, pub(crate) new_activations_per_relay_parent: prometheus::Histogram,
pub(crate) new_activations_per_availability_core: prometheus::Histogram, pub(crate) new_activations_per_availability_core: prometheus::Histogram,
pub(crate) submit_collation: prometheus::Histogram,
} }
/// `CollationGenerationSubsystem` metrics. /// `CollationGenerationSubsystem` metrics.
@@ -57,6 +58,11 @@ impl Metrics {
.as_ref() .as_ref()
.map(|metrics| metrics.new_activations_per_availability_core.start_timer()) .map(|metrics| metrics.new_activations_per_availability_core.start_timer())
} }
/// Provide a timer for submitting a collation which updates on drop.
pub fn time_submit_collation(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| metrics.submit_collation.start_timer())
}
} }
impl metrics::Metrics for Metrics { impl metrics::Metrics for Metrics {
@@ -96,6 +102,15 @@ impl metrics::Metrics for Metrics {
)?, )?,
registry, registry,
)?, )?,
submit_collation: prometheus::register(
prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"polkadot_parachain_collation_generation_submit_collation",
"Time spent preparing and submitting a collation to the network protocol",
)
)?,
registry,
)?,
}; };
Ok(Metrics(Some(metrics))) Ok(Metrics(Some(metrics)))
} }
File diff suppressed because it is too large Load Diff
+31 -12
View File
@@ -19,10 +19,10 @@ use futures::channel::{mpsc, oneshot};
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
messages::{StoreAvailableDataError, ValidationFailed}, messages::{StoreAvailableDataError, ValidationFailed},
SubsystemError, RuntimeApiError, SubsystemError,
}; };
use polkadot_node_subsystem_util::Error as UtilError; use polkadot_node_subsystem_util::{runtime, Error as UtilError};
use polkadot_primitives::BackedCandidate; use polkadot_primitives::{BackedCandidate, ValidationCodeHash};
use crate::LOG_TARGET; use crate::LOG_TARGET;
@@ -33,6 +33,18 @@ pub type FatalResult<T> = std::result::Result<T, FatalError>;
#[allow(missing_docs)] #[allow(missing_docs)]
#[fatality::fatality(splitable)] #[fatality::fatality(splitable)]
pub enum Error { pub enum Error {
#[fatal]
#[error("Failed to spawn background task")]
FailedToSpawnBackgroundTask,
#[fatal(forward)]
#[error("Error while accessing runtime information")]
Runtime(#[from] runtime::Error),
#[fatal]
#[error(transparent)]
BackgroundValidationMpsc(#[from] mpsc::SendError),
#[error("Candidate is not found")] #[error("Candidate is not found")]
CandidateNotFound, CandidateNotFound,
@@ -45,16 +57,27 @@ pub enum Error {
#[error("FetchPoV failed")] #[error("FetchPoV failed")]
FetchPoV, FetchPoV,
#[fatal] #[error("Fetching validation code by hash failed {0:?}, {1:?}")]
#[error("Failed to spawn background task")] FetchValidationCode(ValidationCodeHash, RuntimeApiError),
FailedToSpawnBackgroundTask,
#[error("ValidateFromChainState channel closed before receipt")] #[error("Fetching Runtime API version failed {0:?}")]
ValidateFromChainState(#[source] oneshot::Canceled), FetchRuntimeApiVersion(RuntimeApiError),
#[error("No validation code {0:?}")]
NoValidationCode(ValidationCodeHash),
#[error("Candidate rejected by prospective parachains subsystem")]
RejectedByProspectiveParachains,
#[error("ValidateFromExhaustive channel closed before receipt")]
ValidateFromExhaustive(#[source] oneshot::Canceled),
#[error("StoreAvailableData channel closed before receipt")] #[error("StoreAvailableData channel closed before receipt")]
StoreAvailableDataChannel(#[source] oneshot::Canceled), StoreAvailableDataChannel(#[source] oneshot::Canceled),
#[error("RuntimeAPISubsystem channel closed before receipt")]
RuntimeApiUnavailable(#[source] oneshot::Canceled),
#[error("a channel was closed before receipt in try_join!")] #[error("a channel was closed before receipt in try_join!")]
JoinMultiple(#[source] oneshot::Canceled), JoinMultiple(#[source] oneshot::Canceled),
@@ -64,10 +87,6 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
ValidationFailed(#[from] ValidationFailed), ValidationFailed(#[from] ValidationFailed),
#[fatal]
#[error(transparent)]
BackgroundValidationMpsc(#[from] mpsc::SendError),
#[error(transparent)] #[error(transparent)]
UtilError(#[from] UtilError), UtilError(#[from] UtilError),
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -136,8 +136,7 @@ fn make_candidate_receipt(relay_parent: Hash) -> CandidateReceipt {
para_head: zeros, para_head: zeros,
validation_code_hash: zeros.into(), validation_code_hash: zeros.into(),
}; };
let candidate = CandidateReceipt { descriptor, commitments_hash: zeros }; CandidateReceipt { descriptor, commitments_hash: zeros }
candidate
} }
/// Get a dummy `ActivatedLeaf` for a given block number. /// Get a dummy `ActivatedLeaf` for a given block number.
@@ -0,0 +1,29 @@
[package]
name = "polkadot-node-core-prospective-parachains"
version = "0.9.16"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
[dependencies]
futures = "0.3.19"
gum = { package = "tracing-gum", path = "../../gum" }
parity-scale-codec = "2"
thiserror = "1.0.30"
fatality = "0.0.6"
bitvec = "1"
polkadot-primitives = { path = "../../../primitives" }
polkadot-node-primitives = { path = "../../primitives" }
polkadot-node-subsystem = { path = "../../subsystem" }
polkadot-node-subsystem-util = { path = "../../subsystem-util" }
[dev-dependencies]
assert_matches = "1"
polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
polkadot-node-subsystem-types = { path = "../../subsystem-types" }
polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -0,0 +1,87 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Error types.
use futures::channel::oneshot;
use polkadot_node_subsystem::{
errors::{ChainApiError, RuntimeApiError},
SubsystemError,
};
use polkadot_node_subsystem_util::runtime;
use crate::LOG_TARGET;
use fatality::Nested;
#[allow(missing_docs)]
#[fatality::fatality(splitable)]
pub enum Error {
#[fatal]
#[error("SubsystemError::Context error: {0}")]
SubsystemContext(String),
#[fatal]
#[error("Spawning a task failed: {0}")]
SpawnFailed(SubsystemError),
#[fatal]
#[error("Participation worker receiver exhausted.")]
ParticipationWorkerReceiverExhausted,
#[fatal]
#[error("Receiving message from overseer failed: {0}")]
SubsystemReceive(#[source] SubsystemError),
#[error("Error while accessing runtime information")]
Runtime(#[from] runtime::Error),
#[error(transparent)]
RuntimeApi(#[from] RuntimeApiError),
#[error(transparent)]
ChainApi(#[from] ChainApiError),
#[error(transparent)]
Subsystem(SubsystemError),
#[error("Request to chain API subsystem dropped")]
ChainApiRequestCanceled(oneshot::Canceled),
#[error("Request to runtime API subsystem dropped")]
RuntimeApiRequestCanceled(oneshot::Canceled),
}
/// General `Result` type.
pub type Result<R> = std::result::Result<R, Error>;
/// Result for non-fatal only failures.
pub type JfyiErrorResult<T> = std::result::Result<T, JfyiError>;
/// Result for fatal only failures.
pub type FatalResult<T> = std::result::Result<T, FatalError>;
/// Utility for eating top level errors and log them.
///
/// We basically always want to try and continue on error. This utility function is meant to
/// consume top-level errors by simply logging them
pub fn log_error(result: Result<()>, ctx: &'static str) -> FatalResult<()> {
match result.into_nested()? {
Ok(()) => Ok(()),
Err(jfyi) => {
gum::debug!(target: LOG_TARGET, error = ?jfyi, ctx);
Ok(())
},
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,939 @@
// Copyright 2022-2023 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Implementation of the Prospective Parachains subsystem - this tracks and handles
//! prospective parachain fragments and informs other backing-stage subsystems
//! of work to be done.
//!
//! This is the main coordinator of work within the node for the collation and
//! backing phases of parachain consensus.
//!
//! This is primarily an implementation of "Fragment Trees", as described in
//! [`polkadot_node_subsystem_util::inclusion_emulator::staging`].
//!
//! This subsystem also handles concerns such as the relay-chain being forkful and session changes.
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
};
use futures::{channel::oneshot, prelude::*};
use polkadot_node_subsystem::{
messages::{
ChainApiMessage, FragmentTreeMembership, HypotheticalCandidate,
HypotheticalFrontierRequest, IntroduceCandidateRequest, ProspectiveParachainsMessage,
ProspectiveValidationDataRequest, RuntimeApiMessage, RuntimeApiRequest,
},
overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError,
};
use polkadot_node_subsystem_util::{
inclusion_emulator::staging::{Constraints, RelayChainBlockInfo},
request_session_index_for_child,
runtime::{prospective_parachains_mode, ProspectiveParachainsMode},
};
use polkadot_primitives::vstaging::{
BlockNumber, CandidateHash, CandidatePendingAvailability, CommittedCandidateReceipt, CoreState,
Hash, HeadData, Header, Id as ParaId, PersistedValidationData,
};
use crate::{
error::{FatalError, FatalResult, JfyiError, JfyiErrorResult, Result},
fragment_tree::{
CandidateStorage, CandidateStorageInsertionError, FragmentTree, Scope as TreeScope,
},
};
mod error;
mod fragment_tree;
#[cfg(test)]
mod tests;
mod metrics;
use self::metrics::Metrics;
const LOG_TARGET: &str = "parachain::prospective-parachains";
struct RelayBlockViewData {
// Scheduling info for paras and upcoming paras.
fragment_trees: HashMap<ParaId, FragmentTree>,
pending_availability: HashSet<CandidateHash>,
}
struct View {
// Active or recent relay-chain blocks by block hash.
active_leaves: HashMap<Hash, RelayBlockViewData>,
candidate_storage: HashMap<ParaId, CandidateStorage>,
}
impl View {
fn new() -> Self {
View { active_leaves: HashMap::new(), candidate_storage: HashMap::new() }
}
}
/// The prospective parachains subsystem.
#[derive(Default)]
pub struct ProspectiveParachainsSubsystem {
metrics: Metrics,
}
impl ProspectiveParachainsSubsystem {
/// Create a new instance of the `ProspectiveParachainsSubsystem`.
pub fn new(metrics: Metrics) -> Self {
Self { metrics }
}
}
#[overseer::subsystem(ProspectiveParachains, error = SubsystemError, prefix = self::overseer)]
impl<Context> ProspectiveParachainsSubsystem
where
Context: Send + Sync,
{
fn start(self, ctx: Context) -> SpawnedSubsystem {
SpawnedSubsystem {
future: run(ctx, self.metrics)
.map_err(|e| SubsystemError::with_origin("prospective-parachains", e))
.boxed(),
name: "prospective-parachains-subsystem",
}
}
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn run<Context>(mut ctx: Context, metrics: Metrics) -> FatalResult<()> {
let mut view = View::new();
loop {
crate::error::log_error(
run_iteration(&mut ctx, &mut view, &metrics).await,
"Encountered issue during run iteration",
)?;
}
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn run_iteration<Context>(
ctx: &mut Context,
view: &mut View,
metrics: &Metrics,
) -> Result<()> {
loop {
match ctx.recv().await.map_err(FatalError::SubsystemReceive)? {
FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()),
FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => {
handle_active_leaves_update(&mut *ctx, view, update, metrics).await?;
},
FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {},
FromOrchestra::Communication { msg } => match msg {
ProspectiveParachainsMessage::IntroduceCandidate(request, tx) =>
handle_candidate_introduced(&mut *ctx, view, request, tx).await?,
ProspectiveParachainsMessage::CandidateSeconded(para, candidate_hash) =>
handle_candidate_seconded(view, para, candidate_hash),
ProspectiveParachainsMessage::CandidateBacked(para, candidate_hash) =>
handle_candidate_backed(&mut *ctx, view, para, candidate_hash).await?,
ProspectiveParachainsMessage::GetBackableCandidate(
relay_parent,
para,
required_path,
tx,
) => answer_get_backable_candidate(&view, relay_parent, para, required_path, tx),
ProspectiveParachainsMessage::GetHypotheticalFrontier(request, tx) =>
answer_hypothetical_frontier_request(&view, request, tx),
ProspectiveParachainsMessage::GetTreeMembership(para, candidate, tx) =>
answer_tree_membership_request(&view, para, candidate, tx),
ProspectiveParachainsMessage::GetMinimumRelayParents(relay_parent, tx) =>
answer_minimum_relay_parents_request(&view, relay_parent, tx),
ProspectiveParachainsMessage::GetProspectiveValidationData(request, tx) =>
answer_prospective_validation_data_request(&view, request, tx),
},
}
}
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn handle_active_leaves_update<Context>(
ctx: &mut Context,
view: &mut View,
update: ActiveLeavesUpdate,
metrics: &Metrics,
) -> JfyiErrorResult<()> {
// 1. clean up inactive leaves
// 2. determine all scheduled para at new block
// 3. construct new fragment tree for each para for each new leaf
// 4. prune candidate storage.
for deactivated in &update.deactivated {
view.active_leaves.remove(deactivated);
}
let mut temp_header_cache = HashMap::new();
for activated in update.activated.into_iter() {
let hash = activated.hash;
let mode = prospective_parachains_mode(ctx.sender(), hash)
.await
.map_err(JfyiError::Runtime)?;
let ProspectiveParachainsMode::Enabled { max_candidate_depth, allowed_ancestry_len } = mode
else {
gum::trace!(
target: LOG_TARGET,
block_hash = ?hash,
"Skipping leaf activation since async backing is disabled"
);
// Not a part of any allowed ancestry.
return Ok(())
};
let mut pending_availability = HashSet::new();
let scheduled_paras =
fetch_upcoming_paras(&mut *ctx, hash, &mut pending_availability).await?;
let block_info: RelayChainBlockInfo =
match fetch_block_info(&mut *ctx, &mut temp_header_cache, hash).await? {
None => {
gum::warn!(
target: LOG_TARGET,
block_hash = ?hash,
"Failed to get block info for newly activated leaf block."
);
// `update.activated` is an option, but we can use this
// to exit the 'loop' and skip this block without skipping
// pruning logic.
continue
},
Some(info) => info,
};
let ancestry =
fetch_ancestry(&mut *ctx, &mut temp_header_cache, hash, allowed_ancestry_len).await?;
// Find constraints.
let mut fragment_trees = HashMap::new();
for para in scheduled_paras {
let candidate_storage =
view.candidate_storage.entry(para).or_insert_with(CandidateStorage::new);
let backing_state = fetch_backing_state(&mut *ctx, hash, para).await?;
let (constraints, pending_availability) = match backing_state {
Some(c) => c,
None => {
// This indicates a runtime conflict of some kind.
gum::debug!(
target: LOG_TARGET,
para_id = ?para,
relay_parent = ?hash,
"Failed to get inclusion backing state."
);
continue
},
};
let pending_availability = preprocess_candidates_pending_availability(
ctx,
&mut temp_header_cache,
constraints.required_parent.clone(),
pending_availability,
)
.await?;
let mut compact_pending = Vec::with_capacity(pending_availability.len());
for c in pending_availability {
let res = candidate_storage.add_candidate(c.candidate, c.persisted_validation_data);
let candidate_hash = c.compact.candidate_hash;
compact_pending.push(c.compact);
match res {
Ok(_) | Err(CandidateStorageInsertionError::CandidateAlreadyKnown(_)) => {
// Anything on-chain is guaranteed to be backed.
candidate_storage.mark_backed(&candidate_hash);
},
Err(err) => {
gum::warn!(
target: LOG_TARGET,
?candidate_hash,
para_id = ?para,
?err,
"Scraped invalid candidate pending availability",
);
},
}
}
let scope = TreeScope::with_ancestors(
para,
block_info.clone(),
constraints,
compact_pending,
max_candidate_depth,
ancestry.iter().cloned(),
)
.expect("ancestors are provided in reverse order and correctly; qed");
let tree = FragmentTree::populate(scope, &*candidate_storage);
fragment_trees.insert(para, tree);
}
view.active_leaves
.insert(hash, RelayBlockViewData { fragment_trees, pending_availability });
}
if !update.deactivated.is_empty() {
// This has potential to be a hotspot.
prune_view_candidate_storage(view, metrics);
}
Ok(())
}
fn prune_view_candidate_storage(view: &mut View, metrics: &Metrics) {
metrics.time_prune_view_candidate_storage();
let active_leaves = &view.active_leaves;
let mut live_candidates = HashSet::new();
let mut live_paras = HashSet::new();
for sub_view in active_leaves.values() {
for (para_id, fragment_tree) in &sub_view.fragment_trees {
live_candidates.extend(fragment_tree.candidates());
live_paras.insert(*para_id);
}
live_candidates.extend(sub_view.pending_availability.iter().cloned());
}
view.candidate_storage.retain(|para_id, storage| {
if !live_paras.contains(&para_id) {
return false
}
storage.retain(|h| live_candidates.contains(&h));
// Even if `storage` is now empty, we retain.
// This maintains a convenient invariant that para-id storage exists
// as long as there's an active head which schedules the para.
true
})
}
struct ImportablePendingAvailability {
candidate: CommittedCandidateReceipt,
persisted_validation_data: PersistedValidationData,
compact: crate::fragment_tree::PendingAvailability,
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn preprocess_candidates_pending_availability<Context>(
ctx: &mut Context,
cache: &mut HashMap<Hash, Header>,
required_parent: HeadData,
pending_availability: Vec<CandidatePendingAvailability>,
) -> JfyiErrorResult<Vec<ImportablePendingAvailability>> {
let mut required_parent = required_parent;
let mut importable = Vec::new();
let expected_count = pending_availability.len();
for (i, pending) in pending_availability.into_iter().enumerate() {
let relay_parent =
match fetch_block_info(ctx, cache, pending.descriptor.relay_parent).await? {
None => {
gum::debug!(
target: LOG_TARGET,
?pending.candidate_hash,
?pending.descriptor.para_id,
index = ?i,
?expected_count,
"Had to stop processing pending candidates early due to missing info.",
);
break
},
Some(b) => b,
};
let next_required_parent = pending.commitments.head_data.clone();
importable.push(ImportablePendingAvailability {
candidate: CommittedCandidateReceipt {
descriptor: pending.descriptor,
commitments: pending.commitments,
},
persisted_validation_data: PersistedValidationData {
parent_head: required_parent,
max_pov_size: pending.max_pov_size,
relay_parent_number: relay_parent.number,
relay_parent_storage_root: relay_parent.storage_root,
},
compact: crate::fragment_tree::PendingAvailability {
candidate_hash: pending.candidate_hash,
relay_parent,
},
});
required_parent = next_required_parent;
}
Ok(importable)
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn handle_candidate_introduced<Context>(
_ctx: &mut Context,
view: &mut View,
request: IntroduceCandidateRequest,
tx: oneshot::Sender<FragmentTreeMembership>,
) -> JfyiErrorResult<()> {
let IntroduceCandidateRequest {
candidate_para: para,
candidate_receipt: candidate,
persisted_validation_data: pvd,
} = request;
// Add the candidate to storage.
// Then attempt to add it to all trees.
let storage = match view.candidate_storage.get_mut(&para) {
None => {
gum::warn!(
target: LOG_TARGET,
para_id = ?para,
candidate_hash = ?candidate.hash(),
"Received seconded candidate for inactive para",
);
let _ = tx.send(Vec::new());
return Ok(())
},
Some(storage) => storage,
};
let candidate_hash = match storage.add_candidate(candidate, pvd) {
Ok(c) => c,
Err(CandidateStorageInsertionError::CandidateAlreadyKnown(c)) => {
// Candidate known - return existing fragment tree membership.
let _ = tx.send(fragment_tree_membership(&view.active_leaves, para, c));
return Ok(())
},
Err(CandidateStorageInsertionError::PersistedValidationDataMismatch) => {
// We can't log the candidate hash without either doing more ~expensive
// hashing but this branch indicates something is seriously wrong elsewhere
// so it's doubtful that it would affect debugging.
gum::warn!(
target: LOG_TARGET,
para = ?para,
"Received seconded candidate had mismatching validation data",
);
let _ = tx.send(Vec::new());
return Ok(())
},
};
let mut membership = Vec::new();
for (relay_parent, leaf_data) in &mut view.active_leaves {
if let Some(tree) = leaf_data.fragment_trees.get_mut(&para) {
tree.add_and_populate(candidate_hash, &*storage);
if let Some(depths) = tree.candidate(&candidate_hash) {
membership.push((*relay_parent, depths));
}
}
}
if membership.is_empty() {
storage.remove_candidate(&candidate_hash);
}
let _ = tx.send(membership);
Ok(())
}
fn handle_candidate_seconded(view: &mut View, para: ParaId, candidate_hash: CandidateHash) {
let storage = match view.candidate_storage.get_mut(&para) {
None => {
gum::warn!(
target: LOG_TARGET,
para_id = ?para,
?candidate_hash,
"Received instruction to second unknown candidate",
);
return
},
Some(storage) => storage,
};
if !storage.contains(&candidate_hash) {
gum::warn!(
target: LOG_TARGET,
para_id = ?para,
?candidate_hash,
"Received instruction to second unknown candidate",
);
return
}
storage.mark_seconded(&candidate_hash);
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn handle_candidate_backed<Context>(
_ctx: &mut Context,
view: &mut View,
para: ParaId,
candidate_hash: CandidateHash,
) -> JfyiErrorResult<()> {
let storage = match view.candidate_storage.get_mut(&para) {
None => {
gum::warn!(
target: LOG_TARGET,
para_id = ?para,
?candidate_hash,
"Received instruction to back unknown candidate",
);
return Ok(())
},
Some(storage) => storage,
};
if !storage.contains(&candidate_hash) {
gum::warn!(
target: LOG_TARGET,
para_id = ?para,
?candidate_hash,
"Received instruction to back unknown candidate",
);
return Ok(())
}
if storage.is_backed(&candidate_hash) {
gum::debug!(
target: LOG_TARGET,
para_id = ?para,
?candidate_hash,
"Received redundant instruction to mark candidate as backed",
);
return Ok(())
}
storage.mark_backed(&candidate_hash);
Ok(())
}
fn answer_get_backable_candidate(
view: &View,
relay_parent: Hash,
para: ParaId,
required_path: Vec<CandidateHash>,
tx: oneshot::Sender<Option<(CandidateHash, Hash)>>,
) {
let data = match view.active_leaves.get(&relay_parent) {
None => {
gum::debug!(
target: LOG_TARGET,
?relay_parent,
para_id = ?para,
"Requested backable candidate for inactive relay-parent."
);
let _ = tx.send(None);
return
},
Some(d) => d,
};
let tree = match data.fragment_trees.get(&para) {
None => {
gum::debug!(
target: LOG_TARGET,
?relay_parent,
para_id = ?para,
"Requested backable candidate for inactive para."
);
let _ = tx.send(None);
return
},
Some(tree) => tree,
};
let storage = match view.candidate_storage.get(&para) {
None => {
gum::warn!(
target: LOG_TARGET,
?relay_parent,
para_id = ?para,
"No candidate storage for active para",
);
let _ = tx.send(None);
return
},
Some(s) => s,
};
let Some(child_hash) =
tree.select_child(&required_path, |candidate| storage.is_backed(candidate))
else {
let _ = tx.send(None);
return
};
let Some(candidate_relay_parent) = storage.relay_parent_by_candidate_hash(&child_hash) else {
gum::error!(
target: LOG_TARGET,
?child_hash,
para_id = ?para,
"Candidate is present in fragment tree but not in candidate's storage!",
);
let _ = tx.send(None);
return
};
let _ = tx.send(Some((child_hash, candidate_relay_parent)));
}
fn answer_hypothetical_frontier_request(
view: &View,
request: HypotheticalFrontierRequest,
tx: oneshot::Sender<Vec<(HypotheticalCandidate, FragmentTreeMembership)>>,
) {
let mut response = Vec::with_capacity(request.candidates.len());
for candidate in request.candidates {
response.push((candidate, Vec::new()));
}
let required_active_leaf = request.fragment_tree_relay_parent;
for (active_leaf, leaf_view) in view
.active_leaves
.iter()
.filter(|(h, _)| required_active_leaf.as_ref().map_or(true, |x| h == &x))
{
for &mut (ref c, ref mut membership) in &mut response {
let fragment_tree = match leaf_view.fragment_trees.get(&c.candidate_para()) {
None => continue,
Some(f) => f,
};
let candidate_storage = match view.candidate_storage.get(&c.candidate_para()) {
None => continue,
Some(storage) => storage,
};
let candidate_hash = c.candidate_hash();
let hypothetical = match c {
HypotheticalCandidate::Complete { receipt, persisted_validation_data, .. } =>
fragment_tree::HypotheticalCandidate::Complete {
receipt: Cow::Borrowed(receipt),
persisted_validation_data: Cow::Borrowed(persisted_validation_data),
},
HypotheticalCandidate::Incomplete {
parent_head_data_hash,
candidate_relay_parent,
..
} => fragment_tree::HypotheticalCandidate::Incomplete {
relay_parent: *candidate_relay_parent,
parent_head_data_hash: *parent_head_data_hash,
},
};
let depths = fragment_tree.hypothetical_depths(
candidate_hash,
hypothetical,
candidate_storage,
request.backed_in_path_only,
);
if !depths.is_empty() {
membership.push((*active_leaf, depths));
}
}
}
let _ = tx.send(response);
}
fn fragment_tree_membership(
active_leaves: &HashMap<Hash, RelayBlockViewData>,
para: ParaId,
candidate: CandidateHash,
) -> FragmentTreeMembership {
let mut membership = Vec::new();
for (relay_parent, view_data) in active_leaves {
if let Some(tree) = view_data.fragment_trees.get(&para) {
if let Some(depths) = tree.candidate(&candidate) {
membership.push((*relay_parent, depths));
}
}
}
membership
}
fn answer_tree_membership_request(
view: &View,
para: ParaId,
candidate: CandidateHash,
tx: oneshot::Sender<FragmentTreeMembership>,
) {
let _ = tx.send(fragment_tree_membership(&view.active_leaves, para, candidate));
}
fn answer_minimum_relay_parents_request(
view: &View,
relay_parent: Hash,
tx: oneshot::Sender<Vec<(ParaId, BlockNumber)>>,
) {
let mut v = Vec::new();
if let Some(leaf_data) = view.active_leaves.get(&relay_parent) {
for (para_id, fragment_tree) in &leaf_data.fragment_trees {
v.push((*para_id, fragment_tree.scope().earliest_relay_parent().number));
}
}
let _ = tx.send(v);
}
fn answer_prospective_validation_data_request(
view: &View,
request: ProspectiveValidationDataRequest,
tx: oneshot::Sender<Option<PersistedValidationData>>,
) {
// 1. Try to get the head-data from the candidate store if known.
// 2. Otherwise, it might exist as the base in some relay-parent and we can find it by iterating
// fragment trees.
// 3. Otherwise, it is unknown.
// 4. Also try to find the relay parent block info by scanning fragment trees.
// 5. If head data and relay parent block info are found - success. Otherwise, failure.
let storage = match view.candidate_storage.get(&request.para_id) {
None => {
let _ = tx.send(None);
return
},
Some(s) => s,
};
let mut head_data =
storage.head_data_by_hash(&request.parent_head_data_hash).map(|x| x.clone());
let mut relay_parent_info = None;
let mut max_pov_size = None;
for fragment_tree in view
.active_leaves
.values()
.filter_map(|x| x.fragment_trees.get(&request.para_id))
{
if head_data.is_some() && relay_parent_info.is_some() && max_pov_size.is_some() {
break
}
if relay_parent_info.is_none() {
relay_parent_info =
fragment_tree.scope().ancestor_by_hash(&request.candidate_relay_parent);
}
if head_data.is_none() {
let required_parent = &fragment_tree.scope().base_constraints().required_parent;
if required_parent.hash() == request.parent_head_data_hash {
head_data = Some(required_parent.clone());
}
}
if max_pov_size.is_none() {
let contains_ancestor = fragment_tree
.scope()
.ancestor_by_hash(&request.candidate_relay_parent)
.is_some();
if contains_ancestor {
// We are leaning hard on two assumptions here.
// 1. That the fragment tree never contains allowed relay-parents whose session for
// children is different from that of the base block's.
// 2. That the max_pov_size is only configurable per session.
max_pov_size = Some(fragment_tree.scope().base_constraints().max_pov_size);
}
}
}
let _ = tx.send(match (head_data, relay_parent_info, max_pov_size) {
(Some(h), Some(i), Some(m)) => Some(PersistedValidationData {
parent_head: h,
relay_parent_number: i.number,
relay_parent_storage_root: i.storage_root,
max_pov_size: m as _,
}),
_ => None,
});
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn fetch_backing_state<Context>(
ctx: &mut Context,
relay_parent: Hash,
para_id: ParaId,
) -> JfyiErrorResult<Option<(Constraints, Vec<CandidatePendingAvailability>)>> {
let (tx, rx) = oneshot::channel();
ctx.send_message(RuntimeApiMessage::Request(
relay_parent,
RuntimeApiRequest::StagingParaBackingState(para_id, tx),
))
.await;
Ok(rx
.await
.map_err(JfyiError::RuntimeApiRequestCanceled)??
.map(|s| (From::from(s.constraints), s.pending_availability)))
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn fetch_upcoming_paras<Context>(
ctx: &mut Context,
relay_parent: Hash,
pending_availability: &mut HashSet<CandidateHash>,
) -> JfyiErrorResult<Vec<ParaId>> {
let (tx, rx) = oneshot::channel();
// This'll have to get more sophisticated with parathreads,
// but for now we can just use the `AvailabilityCores`.
ctx.send_message(RuntimeApiMessage::Request(
relay_parent,
RuntimeApiRequest::AvailabilityCores(tx),
))
.await;
let cores = rx.await.map_err(JfyiError::RuntimeApiRequestCanceled)??;
let mut upcoming = HashSet::new();
for core in cores {
match core {
CoreState::Occupied(occupied) => {
pending_availability.insert(occupied.candidate_hash);
if let Some(next_up_on_available) = occupied.next_up_on_available {
upcoming.insert(next_up_on_available.para_id);
}
if let Some(next_up_on_time_out) = occupied.next_up_on_time_out {
upcoming.insert(next_up_on_time_out.para_id);
}
},
CoreState::Scheduled(scheduled) => {
upcoming.insert(scheduled.para_id);
},
CoreState::Free => {},
}
}
Ok(upcoming.into_iter().collect())
}
// Fetch ancestors in descending order, up to the amount requested.
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn fetch_ancestry<Context>(
ctx: &mut Context,
cache: &mut HashMap<Hash, Header>,
relay_hash: Hash,
ancestors: usize,
) -> JfyiErrorResult<Vec<RelayChainBlockInfo>> {
if ancestors == 0 {
return Ok(Vec::new())
}
let (tx, rx) = oneshot::channel();
ctx.send_message(ChainApiMessage::Ancestors {
hash: relay_hash,
k: ancestors,
response_channel: tx,
})
.await;
let hashes = rx.map_err(JfyiError::ChainApiRequestCanceled).await??;
let required_session = request_session_index_for_child(relay_hash, ctx.sender())
.await
.await
.map_err(JfyiError::RuntimeApiRequestCanceled)??;
let mut block_info = Vec::with_capacity(hashes.len());
for hash in hashes {
let info = match fetch_block_info(ctx, cache, hash).await? {
None => {
gum::warn!(
target: LOG_TARGET,
relay_hash = ?hash,
"Failed to fetch info for hash returned from ancestry.",
);
// Return, however far we got.
break
},
Some(info) => info,
};
// The relay chain cannot accept blocks backed from previous sessions, with
// potentially previous validators. This is a technical limitation we need to
// respect here.
let session = request_session_index_for_child(hash, ctx.sender())
.await
.await
.map_err(JfyiError::RuntimeApiRequestCanceled)??;
if session == required_session {
block_info.push(info);
} else {
break
}
}
Ok(block_info)
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn fetch_block_header_with_cache<Context>(
ctx: &mut Context,
cache: &mut HashMap<Hash, Header>,
relay_hash: Hash,
) -> JfyiErrorResult<Option<Header>> {
if let Some(h) = cache.get(&relay_hash) {
return Ok(Some(h.clone()))
}
let (tx, rx) = oneshot::channel();
ctx.send_message(ChainApiMessage::BlockHeader(relay_hash, tx)).await;
let header = rx.map_err(JfyiError::ChainApiRequestCanceled).await??;
if let Some(ref h) = header {
cache.insert(relay_hash, h.clone());
}
Ok(header)
}
#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)]
async fn fetch_block_info<Context>(
ctx: &mut Context,
cache: &mut HashMap<Hash, Header>,
relay_hash: Hash,
) -> JfyiErrorResult<Option<RelayChainBlockInfo>> {
let header = fetch_block_header_with_cache(ctx, cache, relay_hash).await?;
Ok(header.map(|header| RelayChainBlockInfo {
hash: relay_hash,
number: header.number,
storage_root: header.state_root,
}))
}
@@ -0,0 +1,52 @@
// Copyright 2023 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use polkadot_node_subsystem_util::metrics::{self, prometheus};
#[derive(Clone)]
pub(crate) struct MetricsInner {
pub(crate) prune_view_candidate_storage: prometheus::Histogram,
}
/// Candidate backing metrics.
#[derive(Default, Clone)]
pub struct Metrics(pub(crate) Option<MetricsInner>);
impl Metrics {
/// Provide a timer for handling `prune_view_candidate_storage` which observes on drop.
pub fn time_prune_view_candidate_storage(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0
.as_ref()
.map(|metrics| metrics.prune_view_candidate_storage.start_timer())
}
}
impl metrics::Metrics for Metrics {
fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError> {
let metrics = MetricsInner {
prune_view_candidate_storage: prometheus::register(
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
"polkadot_parachain_prospective_parachains_prune_view_candidate_storage",
"Time spent within `prospective_parachains::prune_view_candidate_storage`",
))?,
registry,
)?,
};
Ok(Metrics(Some(metrics)))
}
}
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -28,6 +28,10 @@ pub type Result<T> = std::result::Result<T, Error>;
#[allow(missing_docs)] #[allow(missing_docs)]
#[fatality::fatality(splitable)] #[fatality::fatality(splitable)]
pub enum Error { pub enum Error {
#[fatal(forward)]
#[error("Error while accessing runtime information")]
Runtime(#[from] util::runtime::Error),
#[error(transparent)] #[error(transparent)]
Util(#[from] util::Error), Util(#[from] util::Error),
@@ -46,11 +50,14 @@ pub enum Error {
#[error("failed to get votes on dispute")] #[error("failed to get votes on dispute")]
CanceledCandidateVotes(#[source] oneshot::Canceled), CanceledCandidateVotes(#[source] oneshot::Canceled),
#[error("failed to get backable candidate from prospective parachains")]
CanceledBackableCandidate(#[source] oneshot::Canceled),
#[error(transparent)] #[error(transparent)]
ChainApi(#[from] ChainApiError), ChainApi(#[from] ChainApiError),
#[error(transparent)] #[error(transparent)]
Runtime(#[from] RuntimeApiError), RuntimeApi(#[from] RuntimeApiError),
#[error("failed to send message to ChainAPI")] #[error("failed to send message to ChainAPI")]
ChainApiMessageSend(#[source] mpsc::SendError), ChainApiMessageSend(#[source] mpsc::SendError),
+155 -21
View File
@@ -28,18 +28,20 @@ use futures_timer::Delay;
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
jaeger, jaeger,
messages::{ messages::{
CandidateBackingMessage, ChainApiMessage, ProvisionableData, ProvisionerInherentData, CandidateBackingMessage, ChainApiMessage, ProspectiveParachainsMessage, ProvisionableData,
ProvisionerMessage, RuntimeApiMessage, RuntimeApiRequest, ProvisionerInherentData, ProvisionerMessage, RuntimeApiMessage, RuntimeApiRequest,
}, },
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, LeafStatus, OverseerSignal, overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, LeafStatus, OverseerSignal,
PerLeafSpan, RuntimeApiError, SpawnedSubsystem, SubsystemError, PerLeafSpan, RuntimeApiError, SpawnedSubsystem, SubsystemError,
}; };
use polkadot_node_subsystem_util::{ use polkadot_node_subsystem_util::{
request_availability_cores, request_persisted_validation_data, TimeoutExt, request_availability_cores, request_persisted_validation_data,
runtime::{prospective_parachains_mode, ProspectiveParachainsMode},
TimeoutExt,
}; };
use polkadot_primitives::{ use polkadot_primitives::{
BackedCandidate, BlockNumber, CandidateReceipt, CoreState, Hash, OccupiedCoreAssumption, BackedCandidate, BlockNumber, CandidateHash, CandidateReceipt, CoreState, Hash, Id as ParaId,
SignedAvailabilityBitfield, ValidatorIndex, OccupiedCoreAssumption, SignedAvailabilityBitfield, ValidatorIndex,
}; };
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
@@ -79,6 +81,7 @@ impl ProvisionerSubsystem {
pub struct PerRelayParent { pub struct PerRelayParent {
leaf: ActivatedLeaf, leaf: ActivatedLeaf,
backed_candidates: Vec<CandidateReceipt>, backed_candidates: Vec<CandidateReceipt>,
prospective_parachains_mode: ProspectiveParachainsMode,
signed_bitfields: Vec<SignedAvailabilityBitfield>, signed_bitfields: Vec<SignedAvailabilityBitfield>,
is_inherent_ready: bool, is_inherent_ready: bool,
awaiting_inherent: Vec<oneshot::Sender<ProvisionerInherentData>>, awaiting_inherent: Vec<oneshot::Sender<ProvisionerInherentData>>,
@@ -86,12 +89,13 @@ pub struct PerRelayParent {
} }
impl PerRelayParent { impl PerRelayParent {
fn new(leaf: ActivatedLeaf) -> Self { fn new(leaf: ActivatedLeaf, prospective_parachains_mode: ProspectiveParachainsMode) -> Self {
let span = PerLeafSpan::new(leaf.span.clone(), "provisioner"); let span = PerLeafSpan::new(leaf.span.clone(), "provisioner");
Self { Self {
leaf, leaf,
backed_candidates: Vec::new(), backed_candidates: Vec::new(),
prospective_parachains_mode,
signed_bitfields: Vec::new(), signed_bitfields: Vec::new(),
is_inherent_ready: false, is_inherent_ready: false,
awaiting_inherent: Vec::new(), awaiting_inherent: Vec::new(),
@@ -147,7 +151,7 @@ async fn run_iteration<Context>(
// Map the error to ensure that the subsystem exits when the overseer is gone. // Map the error to ensure that the subsystem exits when the overseer is gone.
match from_overseer.map_err(Error::OverseerExited)? { match from_overseer.map_err(Error::OverseerExited)? {
FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) =>
handle_active_leaves_update(update, per_relay_parent, inherent_delays), handle_active_leaves_update(ctx.sender(), update, per_relay_parent, inherent_delays).await?,
FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {}, FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {},
FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()), FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()),
FromOrchestra::Communication { msg } => { FromOrchestra::Communication { msg } => {
@@ -175,11 +179,12 @@ async fn run_iteration<Context>(
} }
} }
fn handle_active_leaves_update( async fn handle_active_leaves_update(
sender: &mut impl overseer::ProvisionerSenderTrait,
update: ActiveLeavesUpdate, update: ActiveLeavesUpdate,
per_relay_parent: &mut HashMap<Hash, PerRelayParent>, per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
inherent_delays: &mut InherentDelays, inherent_delays: &mut InherentDelays,
) { ) -> Result<(), Error> {
gum::trace!(target: LOG_TARGET, "Handle ActiveLeavesUpdate"); gum::trace!(target: LOG_TARGET, "Handle ActiveLeavesUpdate");
for deactivated in &update.deactivated { for deactivated in &update.deactivated {
per_relay_parent.remove(deactivated); per_relay_parent.remove(deactivated);
@@ -187,10 +192,13 @@ fn handle_active_leaves_update(
if let Some(leaf) = update.activated { if let Some(leaf) = update.activated {
gum::trace!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Adding delay"); gum::trace!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Adding delay");
let prospective_parachains_mode = prospective_parachains_mode(sender, leaf.hash).await?;
let delay_fut = Delay::new(PRE_PROPOSE_TIMEOUT).map(move |_| leaf.hash).boxed(); let delay_fut = Delay::new(PRE_PROPOSE_TIMEOUT).map(move |_| leaf.hash).boxed();
per_relay_parent.insert(leaf.hash, PerRelayParent::new(leaf)); per_relay_parent.insert(leaf.hash, PerRelayParent::new(leaf, prospective_parachains_mode));
inherent_delays.push(delay_fut); inherent_delays.push(delay_fut);
} }
Ok(())
} }
#[overseer::contextbounds(Provisioner, prefix = self::overseer)] #[overseer::contextbounds(Provisioner, prefix = self::overseer)]
@@ -244,6 +252,7 @@ async fn send_inherent_data_bg<Context>(
let leaf = per_relay_parent.leaf.clone(); let leaf = per_relay_parent.leaf.clone();
let signed_bitfields = per_relay_parent.signed_bitfields.clone(); let signed_bitfields = per_relay_parent.signed_bitfields.clone();
let backed_candidates = per_relay_parent.backed_candidates.clone(); let backed_candidates = per_relay_parent.backed_candidates.clone();
let mode = per_relay_parent.prospective_parachains_mode;
let span = per_relay_parent.span.child("req-inherent-data"); let span = per_relay_parent.span.child("req-inherent-data");
let mut sender = ctx.sender().clone(); let mut sender = ctx.sender().clone();
@@ -262,6 +271,7 @@ async fn send_inherent_data_bg<Context>(
&leaf, &leaf,
&signed_bitfields, &signed_bitfields,
&backed_candidates, &backed_candidates,
mode,
return_senders, return_senders,
&mut sender, &mut sender,
&metrics, &metrics,
@@ -290,7 +300,6 @@ async fn send_inherent_data_bg<Context>(
gum::debug!( gum::debug!(
target: LOG_TARGET, target: LOG_TARGET,
signed_bitfield_count = signed_bitfields.len(), signed_bitfield_count = signed_bitfields.len(),
backed_candidates_count = backed_candidates.len(),
leaf_hash = ?leaf.hash, leaf_hash = ?leaf.hash,
"inherent data sent successfully" "inherent data sent successfully"
); );
@@ -325,7 +334,7 @@ fn note_provisionable_data(
.child("provisionable-backed") .child("provisionable-backed")
.with_candidate(candidate_hash) .with_candidate(candidate_hash)
.with_para_id(backed_candidate.descriptor().para_id); .with_para_id(backed_candidate.descriptor().para_id);
per_relay_parent.backed_candidates.push(backed_candidate) per_relay_parent.backed_candidates.push(backed_candidate);
}, },
// We choose not to punish these forms of misbehavior for the time being. // We choose not to punish these forms of misbehavior for the time being.
// Risks from misbehavior are sufficiently mitigated at the protocol level // Risks from misbehavior are sufficiently mitigated at the protocol level
@@ -373,6 +382,7 @@ async fn send_inherent_data(
leaf: &ActivatedLeaf, leaf: &ActivatedLeaf,
bitfields: &[SignedAvailabilityBitfield], bitfields: &[SignedAvailabilityBitfield],
candidates: &[CandidateReceipt], candidates: &[CandidateReceipt],
prospective_parachains_mode: ProspectiveParachainsMode,
return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>, return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>,
from_job: &mut impl overseer::ProvisionerSenderTrait, from_job: &mut impl overseer::ProvisionerSenderTrait,
metrics: &Metrics, metrics: &Metrics,
@@ -424,8 +434,16 @@ async fn send_inherent_data(
relay_parent = ?leaf.hash, relay_parent = ?leaf.hash,
"Selected bitfields" "Selected bitfields"
); );
let candidates =
select_candidates(&availability_cores, &bitfields, candidates, leaf.hash, from_job).await?; let candidates = select_candidates(
&availability_cores,
&bitfields,
candidates,
prospective_parachains_mode,
leaf.hash,
from_job,
)
.await?;
gum::trace!( gum::trace!(
target: LOG_TARGET, target: LOG_TARGET,
@@ -532,15 +550,16 @@ fn select_availability_bitfields(
selected.into_values().collect() selected.into_values().collect()
} }
/// Determine which cores are free, and then to the degree possible, pick a candidate appropriate to /// Selects candidates from tracked ones to note in a relay chain block.
/// each free core. ///
async fn select_candidates( /// Should be called when prospective parachains are disabled.
async fn select_candidate_hashes_from_tracked(
availability_cores: &[CoreState], availability_cores: &[CoreState],
bitfields: &[SignedAvailabilityBitfield], bitfields: &[SignedAvailabilityBitfield],
candidates: &[CandidateReceipt], candidates: &[CandidateReceipt],
relay_parent: Hash, relay_parent: Hash,
sender: &mut impl overseer::ProvisionerSenderTrait, sender: &mut impl overseer::ProvisionerSenderTrait,
) -> Result<Vec<BackedCandidate>, Error> { ) -> Result<Vec<(CandidateHash, Hash)>, Error> {
let block_number = get_block_number_under_construction(relay_parent, sender).await?; let block_number = get_block_number_under_construction(relay_parent, sender).await?;
let mut selected_candidates = let mut selected_candidates =
@@ -611,18 +630,112 @@ async fn select_candidates(
"Selected candidate receipt", "Selected candidate receipt",
); );
selected_candidates.push(candidate_hash); selected_candidates.push((candidate_hash, candidate.descriptor.relay_parent));
} }
} }
Ok(selected_candidates)
}
/// Requests backable candidates from Prospective Parachains subsystem
/// based on core states.
///
/// Should be called when prospective parachains are enabled.
async fn request_backable_candidates(
availability_cores: &[CoreState],
bitfields: &[SignedAvailabilityBitfield],
relay_parent: Hash,
sender: &mut impl overseer::ProvisionerSenderTrait,
) -> Result<Vec<(CandidateHash, Hash)>, Error> {
let block_number = get_block_number_under_construction(relay_parent, sender).await?;
let mut selected_candidates = Vec::with_capacity(availability_cores.len());
for (core_idx, core) in availability_cores.iter().enumerate() {
let (para_id, required_path) = match core {
CoreState::Scheduled(scheduled_core) => {
// The core is free, pick the first eligible candidate from
// the fragment tree.
(scheduled_core.para_id, Vec::new())
},
CoreState::Occupied(occupied_core) => {
if bitfields_indicate_availability(core_idx, bitfields, &occupied_core.availability)
{
if let Some(ref scheduled_core) = occupied_core.next_up_on_available {
// The candidate occupying the core is available, choose its
// child in the fragment tree.
//
// TODO: doesn't work for on-demand parachains. We lean hard on the
// assumption that cores are fixed to specific parachains within a session.
// https://github.com/paritytech/polkadot/issues/5492
(scheduled_core.para_id, vec![occupied_core.candidate_hash])
} else {
continue
}
} else {
if occupied_core.time_out_at != block_number {
continue
}
if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out {
// Candidate's availability timed out, practically same as scheduled.
(scheduled_core.para_id, Vec::new())
} else {
continue
}
}
},
CoreState::Free => continue,
};
let response = get_backable_candidate(relay_parent, para_id, required_path, sender).await?;
match response {
Some((hash, relay_parent)) => selected_candidates.push((hash, relay_parent)),
None => {
gum::debug!(
target: LOG_TARGET,
leaf_hash = ?relay_parent,
core = core_idx,
"No backable candidate returned by prospective parachains",
);
},
}
}
Ok(selected_candidates)
}
/// Determine which cores are free, and then to the degree possible, pick a candidate appropriate to
/// each free core.
async fn select_candidates(
availability_cores: &[CoreState],
bitfields: &[SignedAvailabilityBitfield],
candidates: &[CandidateReceipt],
prospective_parachains_mode: ProspectiveParachainsMode,
relay_parent: Hash,
sender: &mut impl overseer::ProvisionerSenderTrait,
) -> Result<Vec<BackedCandidate>, Error> {
gum::trace!(target: LOG_TARGET, gum::trace!(target: LOG_TARGET,
leaf_hash=?relay_parent, leaf_hash=?relay_parent,
"before GetBackedCandidates"); "before GetBackedCandidates");
let selected_candidates = match prospective_parachains_mode {
ProspectiveParachainsMode::Enabled { .. } =>
request_backable_candidates(availability_cores, bitfields, relay_parent, sender).await?,
ProspectiveParachainsMode::Disabled =>
select_candidate_hashes_from_tracked(
availability_cores,
bitfields,
&candidates,
relay_parent,
sender,
)
.await?,
};
// now get the backed candidates corresponding to these candidate receipts // now get the backed candidates corresponding to these candidate receipts
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
sender.send_unbounded_message(CandidateBackingMessage::GetBackedCandidates( sender.send_unbounded_message(CandidateBackingMessage::GetBackedCandidates(
relay_parent,
selected_candidates.clone(), selected_candidates.clone(),
tx, tx,
)); ));
@@ -638,7 +751,7 @@ async fn select_candidates(
// checking them in order, we can ensure that the backed candidates are also in order. // checking them in order, we can ensure that the backed candidates are also in order.
let mut backed_idx = 0; let mut backed_idx = 0;
for selected in selected_candidates { for selected in selected_candidates {
if selected == if selected.0 ==
candidates.get(backed_idx).ok_or(Error::BackedCandidateOrderingProblem)?.hash() candidates.get(backed_idx).ok_or(Error::BackedCandidateOrderingProblem)?.hash()
{ {
backed_idx += 1; backed_idx += 1;
@@ -689,6 +802,27 @@ async fn get_block_number_under_construction(
} }
} }
/// Requests backable candidate from Prospective Parachains based on
/// the given path in the fragment tree.
async fn get_backable_candidate(
relay_parent: Hash,
para_id: ParaId,
required_path: Vec<CandidateHash>,
sender: &mut impl overseer::ProvisionerSenderTrait,
) -> Result<Option<(CandidateHash, Hash)>, Error> {
let (tx, rx) = oneshot::channel();
sender
.send_message(ProspectiveParachainsMessage::GetBackableCandidate(
relay_parent,
para_id,
required_path,
tx,
))
.await;
rx.await.map_err(Error::CanceledBackableCandidate)
}
/// The availability bitfield for a given core is the transpose /// The availability bitfield for a given core is the transpose
/// of a set of signed availability bitfields. It goes like this: /// of a set of signed availability bitfields. It goes like this:
/// ///
+220 -30
View File
@@ -19,6 +19,8 @@ use ::test_helpers::{dummy_candidate_descriptor, dummy_hash};
use bitvec::bitvec; use bitvec::bitvec;
use polkadot_primitives::{OccupiedCore, ScheduledCore}; use polkadot_primitives::{OccupiedCore, ScheduledCore};
const MOCK_GROUP_SIZE: usize = 5;
pub fn occupied_core(para_id: u32) -> CoreState { pub fn occupied_core(para_id: u32) -> CoreState {
CoreState::Occupied(OccupiedCore { CoreState::Occupied(OccupiedCore {
group_responsible: para_id.into(), group_responsible: para_id.into(),
@@ -46,8 +48,8 @@ where
CoreState::Occupied(core) CoreState::Occupied(core)
} }
pub fn default_bitvec(n_cores: usize) -> CoreAvailability { pub fn default_bitvec(size: usize) -> CoreAvailability {
bitvec![u8, bitvec::order::Lsb0; 0; n_cores] bitvec![u8, bitvec::order::Lsb0; 0; size]
} }
pub fn scheduled_core(id: u32) -> ScheduledCore { pub fn scheduled_core(id: u32) -> ScheduledCore {
@@ -237,7 +239,7 @@ pub(crate) mod common {
mod select_candidates { mod select_candidates {
use super::{ use super::{
super::*, build_occupied_core, common::test_harness, default_bitvec, occupied_core, super::*, build_occupied_core, common::test_harness, default_bitvec, occupied_core,
scheduled_core, scheduled_core, MOCK_GROUP_SIZE,
}; };
use ::test_helpers::{dummy_candidate_descriptor, dummy_hash}; use ::test_helpers::{dummy_candidate_descriptor, dummy_hash};
use futures::channel::mpsc; use futures::channel::mpsc;
@@ -248,6 +250,7 @@ mod select_candidates {
}, },
}; };
use polkadot_node_subsystem_test_helpers::TestSubsystemSender; use polkadot_node_subsystem_test_helpers::TestSubsystemSender;
use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode;
use polkadot_primitives::{ use polkadot_primitives::{
BlockNumber, CandidateCommitments, CommittedCandidateReceipt, PersistedValidationData, BlockNumber, CandidateCommitments, CommittedCandidateReceipt, PersistedValidationData,
}; };
@@ -333,10 +336,17 @@ mod select_candidates {
async fn mock_overseer( async fn mock_overseer(
mut receiver: mpsc::UnboundedReceiver<AllMessages>, mut receiver: mpsc::UnboundedReceiver<AllMessages>,
expected: Vec<BackedCandidate>, expected: Vec<BackedCandidate>,
prospective_parachains_mode: ProspectiveParachainsMode,
) { ) {
use ChainApiMessage::BlockNumber; use ChainApiMessage::BlockNumber;
use RuntimeApiMessage::Request; use RuntimeApiMessage::Request;
let mut candidates_iter = expected
.iter()
.map(|candidate| (candidate.hash(), candidate.descriptor().relay_parent));
let mut backed_iter = expected.clone().into_iter();
while let Some(from_job) = receiver.next().await { while let Some(from_job) = receiver.next().await {
match from_job { match from_job {
AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) => AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) =>
@@ -348,11 +358,28 @@ mod select_candidates {
AllMessages::RuntimeApi(Request(_parent_hash, AvailabilityCores(tx))) => AllMessages::RuntimeApi(Request(_parent_hash, AvailabilityCores(tx))) =>
tx.send(Ok(mock_availability_cores())).unwrap(), tx.send(Ok(mock_availability_cores())).unwrap(),
AllMessages::CandidateBacking(CandidateBackingMessage::GetBackedCandidates( AllMessages::CandidateBacking(CandidateBackingMessage::GetBackedCandidates(
_, hashes,
_,
sender, sender,
)) => { )) => {
let _ = sender.send(expected.clone()); let response: Vec<BackedCandidate> =
backed_iter.by_ref().take(hashes.len()).collect();
let expected_hashes: Vec<(CandidateHash, Hash)> = response
.iter()
.map(|candidate| (candidate.hash(), candidate.descriptor().relay_parent))
.collect();
assert_eq!(expected_hashes, hashes);
let _ = sender.send(response);
},
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetBackableCandidate(.., tx),
) => match prospective_parachains_mode {
ProspectiveParachainsMode::Enabled { .. } => {
let _ = tx.send(candidates_iter.next());
},
ProspectiveParachainsMode::Disabled =>
panic!("unexpected prospective parachains request"),
}, },
_ => panic!("Unexpected message: {:?}", from_job), _ => panic!("Unexpected message: {:?}", from_job),
} }
@@ -362,9 +389,19 @@ mod select_candidates {
#[test] #[test]
fn can_succeed() { fn can_succeed() {
test_harness( test_harness(
|r| mock_overseer(r, Vec::new()), |r| mock_overseer(r, Vec::new(), ProspectiveParachainsMode::Disabled),
|mut tx: TestSubsystemSender| async move { |mut tx: TestSubsystemSender| async move {
select_candidates(&[], &[], &[], Default::default(), &mut tx).await.unwrap(); let prospective_parachains_mode = ProspectiveParachainsMode::Disabled;
select_candidates(
&[],
&[],
&[],
prospective_parachains_mode,
Default::default(),
&mut tx,
)
.await
.unwrap();
}, },
) )
} }
@@ -375,7 +412,6 @@ mod select_candidates {
#[test] #[test]
fn selects_correct_candidates() { fn selects_correct_candidates() {
let mock_cores = mock_availability_cores(); let mock_cores = mock_availability_cores();
let n_cores = mock_cores.len();
let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash(); let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
@@ -415,6 +451,7 @@ mod select_candidates {
// why those particular indices? see the comments on mock_availability_cores() // why those particular indices? see the comments on mock_availability_cores()
let expected_candidates: Vec<_> = let expected_candidates: Vec<_> =
[1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect(); [1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect();
let prospective_parachains_mode = ProspectiveParachainsMode::Disabled;
let expected_backed = expected_candidates let expected_backed = expected_candidates
.iter() .iter()
@@ -424,17 +461,23 @@ mod select_candidates {
commitments: Default::default(), commitments: Default::default(),
}, },
validity_votes: Vec::new(), validity_votes: Vec::new(),
validator_indices: default_bitvec(n_cores), validator_indices: default_bitvec(MOCK_GROUP_SIZE),
}) })
.collect(); .collect();
test_harness( test_harness(
|r| mock_overseer(r, expected_backed), |r| mock_overseer(r, expected_backed, prospective_parachains_mode),
|mut tx: TestSubsystemSender| async move { |mut tx: TestSubsystemSender| async move {
let result = let result = select_candidates(
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx) &mock_cores,
.await &[],
.unwrap(); &candidates,
prospective_parachains_mode,
Default::default(),
&mut tx,
)
.await
.unwrap();
result.into_iter().for_each(|c| { result.into_iter().for_each(|c| {
assert!( assert!(
@@ -450,15 +493,16 @@ mod select_candidates {
#[test] #[test]
fn selects_max_one_code_upgrade() { fn selects_max_one_code_upgrade() {
let mock_cores = mock_availability_cores(); let mock_cores = mock_availability_cores();
let n_cores = mock_cores.len();
let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash(); let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
// why those particular indices? see the comments on mock_availability_cores() // why those particular indices? see the comments on mock_availability_cores()
// the first candidate with code is included out of [1, 4, 7, 8, 10]. // the first candidate with code is included out of [1, 4, 7, 8, 10].
let cores = [1, 7, 10]; let cores = [1, 4, 7, 8, 10];
let cores_with_code = [1, 4, 8]; let cores_with_code = [1, 4, 8];
let expected_cores = [1, 7, 10];
let committed_receipts: Vec<_> = (0..mock_cores.len()) let committed_receipts: Vec<_> = (0..mock_cores.len())
.map(|i| { .map(|i| {
let mut descriptor = dummy_candidate_descriptor(dummy_hash()); let mut descriptor = dummy_candidate_descriptor(dummy_hash());
@@ -478,27 +522,173 @@ mod select_candidates {
}) })
.collect(); .collect();
// Input to select_candidates
let candidates: Vec<_> = committed_receipts.iter().map(|r| r.to_plain()).collect(); let candidates: Vec<_> = committed_receipts.iter().map(|r| r.to_plain()).collect();
// Build possible outputs from select_candidates
let expected_candidates: Vec<_> = let backed_candidates: Vec<_> = committed_receipts
cores.iter().map(|&idx| candidates[idx].clone()).collect();
let expected_backed: Vec<_> = cores
.iter() .iter()
.map(|&idx| BackedCandidate { .map(|committed_receipt| BackedCandidate {
candidate: committed_receipts[idx].clone(), candidate: committed_receipt.clone(),
validity_votes: Vec::new(), validity_votes: Vec::new(),
validator_indices: default_bitvec(n_cores), validator_indices: default_bitvec(MOCK_GROUP_SIZE),
})
.collect();
// First, provisioner will request backable candidates for each scheduled core.
// Then, some of them get filtered due to new validation code rule.
let expected_backed: Vec<_> =
cores.iter().map(|&idx| backed_candidates[idx].clone()).collect();
let expected_backed_filtered: Vec<_> =
expected_cores.iter().map(|&idx| candidates[idx].clone()).collect();
let prospective_parachains_mode = ProspectiveParachainsMode::Disabled;
test_harness(
|r| mock_overseer(r, expected_backed, prospective_parachains_mode),
|mut tx: TestSubsystemSender| async move {
let result = select_candidates(
&mock_cores,
&[],
&candidates,
prospective_parachains_mode,
Default::default(),
&mut tx,
)
.await
.unwrap();
assert_eq!(result.len(), 3);
result.into_iter().for_each(|c| {
assert!(
expected_backed_filtered.iter().any(|c2| c.candidate.corresponds_to(c2)),
"Failed to find candidate: {:?}",
c,
)
});
},
)
}
#[test]
fn request_from_prospective_parachains() {
let mock_cores = mock_availability_cores();
let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
let mut descriptor_template = dummy_candidate_descriptor(dummy_hash());
descriptor_template.persisted_validation_data_hash = empty_hash;
let candidate_template = CandidateReceipt {
descriptor: descriptor_template,
commitments_hash: CandidateCommitments::default().hash(),
};
let candidates: Vec<_> = std::iter::repeat(candidate_template)
.take(mock_cores.len())
.enumerate()
.map(|(idx, mut candidate)| {
candidate.descriptor.para_id = idx.into();
candidate
})
.collect();
// why those particular indices? see the comments on mock_availability_cores()
let expected_candidates: Vec<_> =
[1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect();
// Expect prospective parachains subsystem requests.
let prospective_parachains_mode =
ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 };
let expected_backed = expected_candidates
.iter()
.map(|c| BackedCandidate {
candidate: CommittedCandidateReceipt {
descriptor: c.descriptor.clone(),
commitments: Default::default(),
},
validity_votes: Vec::new(),
validator_indices: default_bitvec(MOCK_GROUP_SIZE),
}) })
.collect(); .collect();
test_harness( test_harness(
|r| mock_overseer(r, expected_backed), |r| mock_overseer(r, expected_backed, prospective_parachains_mode),
|mut tx: TestSubsystemSender| async move { |mut tx: TestSubsystemSender| async move {
let result = let result = select_candidates(
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx) &mock_cores,
.await &[],
.unwrap(); &[],
prospective_parachains_mode,
Default::default(),
&mut tx,
)
.await
.unwrap();
result.into_iter().for_each(|c| {
assert!(
expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)),
"Failed to find candidate: {:?}",
c,
)
});
},
)
}
#[test]
fn request_receipts_based_on_relay_parent() {
let mock_cores = mock_availability_cores();
let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
let mut descriptor_template = dummy_candidate_descriptor(dummy_hash());
descriptor_template.persisted_validation_data_hash = empty_hash;
let candidate_template = CandidateReceipt {
descriptor: descriptor_template,
commitments_hash: CandidateCommitments::default().hash(),
};
let candidates: Vec<_> = std::iter::repeat(candidate_template)
.take(mock_cores.len())
.enumerate()
.map(|(idx, mut candidate)| {
candidate.descriptor.para_id = idx.into();
candidate.descriptor.relay_parent = Hash::repeat_byte(idx as u8);
candidate
})
.collect();
// why those particular indices? see the comments on mock_availability_cores()
let expected_candidates: Vec<_> =
[1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect();
// Expect prospective parachains subsystem requests.
let prospective_parachains_mode =
ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, allowed_ancestry_len: 0 };
let expected_backed = expected_candidates
.iter()
.map(|c| BackedCandidate {
candidate: CommittedCandidateReceipt {
descriptor: c.descriptor.clone(),
commitments: Default::default(),
},
validity_votes: Vec::new(),
validator_indices: default_bitvec(MOCK_GROUP_SIZE),
})
.collect();
test_harness(
|r| mock_overseer(r, expected_backed, prospective_parachains_mode),
|mut tx: TestSubsystemSender| async move {
let result = select_candidates(
&mock_cores,
&[],
&[],
prospective_parachains_mode,
Default::default(),
&mut tx,
)
.await
.unwrap();
result.into_iter().for_each(|c| { result.into_iter().for_each(|c| {
assert!( assert!(
@@ -68,6 +68,9 @@ pub(crate) struct RequestResultCache {
LruCache<Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>>, LruCache<Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>>,
key_ownership_proof: key_ownership_proof:
LruCache<(Hash, ValidatorId), Option<vstaging::slashing::OpaqueKeyOwnershipProof>>, LruCache<(Hash, ValidatorId), Option<vstaging::slashing::OpaqueKeyOwnershipProof>>,
staging_para_backing_state: LruCache<(Hash, ParaId), Option<vstaging::BackingState>>,
staging_async_backing_params: LruCache<Hash, vstaging::AsyncBackingParams>,
} }
impl Default for RequestResultCache { impl Default for RequestResultCache {
@@ -97,6 +100,9 @@ impl Default for RequestResultCache {
disputes: LruCache::new(DEFAULT_CACHE_CAP), disputes: LruCache::new(DEFAULT_CACHE_CAP),
unapplied_slashes: LruCache::new(DEFAULT_CACHE_CAP), unapplied_slashes: LruCache::new(DEFAULT_CACHE_CAP),
key_ownership_proof: LruCache::new(DEFAULT_CACHE_CAP), key_ownership_proof: LruCache::new(DEFAULT_CACHE_CAP),
staging_para_backing_state: LruCache::new(DEFAULT_CACHE_CAP),
staging_async_backing_params: LruCache::new(DEFAULT_CACHE_CAP),
} }
} }
} }
@@ -430,6 +436,36 @@ impl RequestResultCache {
) -> Option<&Option<()>> { ) -> Option<&Option<()>> {
None None
} }
pub(crate) fn staging_para_backing_state(
&mut self,
key: (Hash, ParaId),
) -> Option<&Option<vstaging::BackingState>> {
self.staging_para_backing_state.get(&key)
}
pub(crate) fn cache_staging_para_backing_state(
&mut self,
key: (Hash, ParaId),
value: Option<vstaging::BackingState>,
) {
self.staging_para_backing_state.put(key, value);
}
pub(crate) fn staging_async_backing_params(
&mut self,
key: &Hash,
) -> Option<&vstaging::AsyncBackingParams> {
self.staging_async_backing_params.get(key)
}
pub(crate) fn cache_staging_async_backing_params(
&mut self,
key: Hash,
value: vstaging::AsyncBackingParams,
) {
self.staging_async_backing_params.put(key, value);
}
} }
pub(crate) enum RequestResult { pub(crate) enum RequestResult {
@@ -476,4 +512,7 @@ pub(crate) enum RequestResult {
vstaging::slashing::OpaqueKeyOwnershipProof, vstaging::slashing::OpaqueKeyOwnershipProof,
Option<()>, Option<()>,
), ),
StagingParaBackingState(Hash, ParaId, Option<vstaging::BackingState>),
StagingAsyncBackingParams(Hash, vstaging::AsyncBackingParams),
} }
+30
View File
@@ -163,6 +163,12 @@ where
.requests_cache .requests_cache
.cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof), .cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof),
SubmitReportDisputeLost(_, _, _, _) => {}, SubmitReportDisputeLost(_, _, _, _) => {},
StagingParaBackingState(relay_parent, para_id, constraints) => self
.requests_cache
.cache_staging_para_backing_state((relay_parent, para_id), constraints),
StagingAsyncBackingParams(relay_parent, params) =>
self.requests_cache.cache_staging_async_backing_params(relay_parent, params),
} }
} }
@@ -288,6 +294,13 @@ where
Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender) Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender)
}, },
), ),
Request::StagingParaBackingState(para, sender) =>
query!(staging_para_backing_state(para), sender)
.map(|sender| Request::StagingParaBackingState(para, sender)),
Request::StagingAsyncBackingParams(sender) =>
query!(staging_async_backing_params(), sender)
.map(|sender| Request::StagingAsyncBackingParams(sender)),
} }
} }
@@ -538,5 +551,22 @@ where
ver = Request::SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT, ver = Request::SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT,
sender sender
), ),
Request::StagingParaBackingState(para, sender) => {
query!(
StagingParaBackingState,
staging_para_backing_state(para),
ver = Request::STAGING_BACKING_STATE,
sender
)
},
Request::StagingAsyncBackingParams(sender) => {
query!(
StagingAsyncBackingParams,
staging_async_backing_params(),
ver = Request::STAGING_BACKING_STATE,
sender
)
},
} }
} }
@@ -249,6 +249,21 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient {
async fn authorities(&self, _: Hash) -> Result<Vec<AuthorityDiscoveryId>, ApiError> { async fn authorities(&self, _: Hash) -> Result<Vec<AuthorityDiscoveryId>, ApiError> {
Ok(self.authorities.clone()) Ok(self.authorities.clone())
} }
async fn staging_async_backing_params(
&self,
_: Hash,
) -> Result<vstaging::AsyncBackingParams, ApiError> {
todo!("Not required for tests")
}
async fn staging_para_backing_state(
&self,
_: Hash,
_: ParaId,
) -> Result<Option<vstaging::BackingState>, ApiError> {
todo!("Not required for tests")
}
} }
#[test] #[test]
@@ -16,14 +16,14 @@ bob: reports block height is at least 2
bob: reports peers count is at least 2 bob: reports peers count is at least 2
charlie: reports block height is at least 2 charlie: reports block height is at least 2
charlie: reports peers count is at least 2 charlie: reports peers count is at least 2
alice: reports parachain_candidate_disputes_total is at least 1 within 250 seconds alice: reports polkadot_parachain_candidate_disputes_total is at least 1 within 250 seconds
bob: reports parachain_candidate_disputes_total is at least 1 within 90 seconds bob: reports polkadot_parachain_candidate_disputes_total is at least 1 within 90 seconds
charlie: reports parachain_candidate_disputes_total is at least 1 within 90 seconds charlie: reports polkadot_parachain_candidate_disputes_total is at least 1 within 90 seconds
alice: reports parachain_candidate_dispute_votes{validity="valid"} is at least 1 within 90 seconds alice: reports polkadot_parachain_candidate_dispute_votes{validity="valid"} is at least 1 within 90 seconds
bob: reports parachain_candidate_dispute_votes{validity="valid"} is at least 2 within 90 seconds bob: reports polkadot_parachain_candidate_dispute_votes{validity="valid"} is at least 2 within 90 seconds
charlie: reports parachain_candidate_dispute_votes{validity="valid"} is at least 2 within 90 seconds charlie: reports polkadot_parachain_candidate_dispute_votes{validity="valid"} is at least 2 within 90 seconds
alice: reports parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds alice: reports polkadot_parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds
alice: reports parachain_candidate_dispute_concluded{validity="invalid"} is 0 within 90 seconds alice: reports polkadot_parachain_candidate_dispute_concluded{validity="invalid"} is 0 within 90 seconds
bob: reports parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds bob: reports polkadot_parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds
charlie: reports parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds charlie: reports polkadot_parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds
charlie: reports parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds charlie: reports polkadot_parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds
+64 -9
View File
@@ -30,6 +30,7 @@ use polkadot_node_subsystem::{
use polkadot_primitives::{ use polkadot_primitives::{
CandidateCommitments, CandidateDescriptor, CandidateReceipt, PersistedValidationData, CandidateCommitments, CandidateDescriptor, CandidateReceipt, PersistedValidationData,
PvfExecTimeoutKind,
}; };
use futures::channel::oneshot; use futures::channel::oneshot;
@@ -48,6 +49,55 @@ pub enum FakeCandidateValidation {
BackingAndApprovalValid, BackingAndApprovalValid,
} }
impl FakeCandidateValidation {
fn misbehaves_valid(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
BackingValid | ApprovalValid | BackingAndApprovalValid => true,
_ => false,
}
}
fn misbehaves_invalid(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
BackingInvalid | ApprovalInvalid | BackingAndApprovalInvalid => true,
_ => false,
}
}
fn includes_backing(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
BackingInvalid | BackingAndApprovalInvalid | BackingValid | BackingAndApprovalValid =>
true,
_ => false,
}
}
fn includes_approval(&self) -> bool {
use FakeCandidateValidation::*;
match *self {
ApprovalInvalid |
BackingAndApprovalInvalid |
ApprovalValid |
BackingAndApprovalValid => true,
_ => false,
}
}
fn should_misbehave(&self, timeout: PvfExecTimeoutKind) -> bool {
match timeout {
PvfExecTimeoutKind::Backing => self.includes_backing(),
PvfExecTimeoutKind::Approval => self.includes_approval(),
}
}
}
/// Candidate invalidity details /// Candidate invalidity details
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)] #[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)]
#[value(rename_all = "kebab-case")] #[value(rename_all = "kebab-case")]
@@ -162,11 +212,20 @@ where
pub fn create_fake_candidate_commitments( pub fn create_fake_candidate_commitments(
persisted_validation_data: &PersistedValidationData, persisted_validation_data: &PersistedValidationData,
) -> CandidateCommitments { ) -> CandidateCommitments {
// Backing rejects candidates which output the same head as the parent,
// therefore we must create a new head which is not equal to the parent.
let mut head_data = persisted_validation_data.parent_head.clone();
if head_data.0.is_empty() {
head_data.0.push(0);
} else {
head_data.0[0] = head_data.0[0].wrapping_add(1);
};
CandidateCommitments { CandidateCommitments {
upward_messages: Default::default(), upward_messages: Default::default(),
horizontal_messages: Default::default(), horizontal_messages: Default::default(),
new_validation_code: None, new_validation_code: None,
head_data: persisted_validation_data.parent_head.clone(), head_data,
processed_downward_messages: 0, processed_downward_messages: 0,
hrmp_watermark: persisted_validation_data.relay_parent_number, hrmp_watermark: persisted_validation_data.relay_parent_number,
} }
@@ -224,8 +283,7 @@ where
), ),
} => { } => {
match self.fake_validation { match self.fake_validation {
FakeCandidateValidation::ApprovalValid | x if x.misbehaves_valid() && x.should_misbehave(timeout) => {
FakeCandidateValidation::BackingAndApprovalValid => {
// Behave normally if the `PoV` is not known to be malicious. // Behave normally if the `PoV` is not known to be malicious.
if pov.block_data.0.as_slice() != MALICIOUS_POV { if pov.block_data.0.as_slice() != MALICIOUS_POV {
return Some(FromOrchestra::Communication { return Some(FromOrchestra::Communication {
@@ -278,8 +336,7 @@ where
}, },
} }
}, },
FakeCandidateValidation::ApprovalInvalid | x if x.misbehaves_invalid() && x.should_misbehave(timeout) => {
FakeCandidateValidation::BackingAndApprovalInvalid => {
// Set the validation result to invalid with probability `p` and trigger a // Set the validation result to invalid with probability `p` and trigger a
// dispute // dispute
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng()); let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
@@ -342,8 +399,7 @@ where
), ),
} => { } => {
match self.fake_validation { match self.fake_validation {
FakeCandidateValidation::BackingValid | x if x.misbehaves_valid() && x.should_misbehave(timeout) => {
FakeCandidateValidation::BackingAndApprovalValid => {
// Behave normally if the `PoV` is not known to be malicious. // Behave normally if the `PoV` is not known to be malicious.
if pov.block_data.0.as_slice() != MALICIOUS_POV { if pov.block_data.0.as_slice() != MALICIOUS_POV {
return Some(FromOrchestra::Communication { return Some(FromOrchestra::Communication {
@@ -385,8 +441,7 @@ where
}), }),
} }
}, },
FakeCandidateValidation::BackingInvalid | x if x.misbehaves_invalid() && x.should_misbehave(timeout) => {
FakeCandidateValidation::BackingAndApprovalInvalid => {
// Maliciously set the validation result to invalid for a valid candidate // Maliciously set the validation result to invalid for a valid candidate
// with probability `p` // with probability `p`
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng()); let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
@@ -79,7 +79,13 @@ where
) -> Option<FromOrchestra<Self::Message>> { ) -> Option<FromOrchestra<Self::Message>> {
match msg { match msg {
FromOrchestra::Communication { FromOrchestra::Communication {
msg: CandidateBackingMessage::Second(relay_parent, ref candidate, ref _pov), msg:
CandidateBackingMessage::Second(
relay_parent,
ref candidate,
ref _validation_data,
ref _pov,
),
} => { } => {
gum::debug!( gum::debug!(
target: MALUS, target: MALUS,
@@ -156,8 +162,10 @@ where
"Fetched validation data." "Fetched validation data."
); );
let malicious_available_data = let malicious_available_data = AvailableData {
AvailableData { pov: Arc::new(pov.clone()), validation_data }; pov: Arc::new(pov.clone()),
validation_data: validation_data.clone(),
};
let pov_hash = pov.hash(); let pov_hash = pov.hash();
let erasure_root = { let erasure_root = {
@@ -211,6 +219,7 @@ where
msg: CandidateBackingMessage::Second( msg: CandidateBackingMessage::Second(
relay_parent, relay_parent,
malicious_candidate, malicious_candidate,
validation_data,
pov, pov,
), ),
}; };
@@ -25,8 +25,9 @@ use polkadot_node_jaeger as jaeger;
use polkadot_node_network_protocol::{ use polkadot_node_network_protocol::{
self as net_protocol, self as net_protocol,
grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology}, grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology},
peer_set::MAX_NOTIFICATION_SIZE, peer_set::{ValidationVersion, MAX_NOTIFICATION_SIZE},
v1 as protocol_v1, PeerId, UnifiedReputationChange as Rep, Versioned, View, v1 as protocol_v1, vstaging as protocol_vstaging, PeerId, UnifiedReputationChange as Rep,
Versioned, VersionedValidationProtocol, View,
}; };
use polkadot_node_primitives::approval::{ use polkadot_node_primitives::approval::{
AssignmentCert, BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote, AssignmentCert, BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote,
@@ -159,6 +160,15 @@ enum Resend {
No, No,
} }
/// Data stored on a per-peer basis.
#[derive(Debug)]
struct PeerData {
/// The peer's view.
view: View,
/// The peer's protocol version.
version: ValidationVersion,
}
/// The [`State`] struct is responsible for tracking the overall state of the subsystem. /// The [`State`] struct is responsible for tracking the overall state of the subsystem.
/// ///
/// It tracks metadata about our view of the unfinalized chain, /// It tracks metadata about our view of the unfinalized chain,
@@ -179,7 +189,7 @@ struct State {
pending_known: HashMap<Hash, Vec<(PeerId, PendingMessage)>>, pending_known: HashMap<Hash, Vec<(PeerId, PendingMessage)>>,
/// Peer data is partially stored here, and partially inline within the [`BlockEntry`]s /// Peer data is partially stored here, and partially inline within the [`BlockEntry`]s
peer_views: HashMap<PeerId, View>, peer_data: HashMap<PeerId, PeerData>,
/// Keeps a topology for various different sessions. /// Keeps a topology for various different sessions.
topologies: SessionGridTopologies, topologies: SessionGridTopologies,
@@ -349,14 +359,30 @@ impl State {
rng: &mut (impl CryptoRng + Rng), rng: &mut (impl CryptoRng + Rng),
) { ) {
match event { match event {
NetworkBridgeEvent::PeerConnected(peer_id, role, _, _) => { NetworkBridgeEvent::PeerConnected(peer_id, role, version, _) => {
// insert a blank view if none already present // insert a blank view if none already present
gum::trace!(target: LOG_TARGET, ?peer_id, ?role, "Peer connected"); gum::trace!(target: LOG_TARGET, ?peer_id, ?role, "Peer connected");
self.peer_views.entry(peer_id).or_default(); let version = match ValidationVersion::try_from(version).ok() {
Some(v) => v,
None => {
// sanity: network bridge is supposed to detect this already.
gum::error!(
target: LOG_TARGET,
?peer_id,
?version,
"Unsupported protocol version"
);
return
},
};
self.peer_data
.entry(peer_id)
.or_insert_with(|| PeerData { version, view: Default::default() });
}, },
NetworkBridgeEvent::PeerDisconnected(peer_id) => { NetworkBridgeEvent::PeerDisconnected(peer_id) => {
gum::trace!(target: LOG_TARGET, ?peer_id, "Peer disconnected"); gum::trace!(target: LOG_TARGET, ?peer_id, "Peer disconnected");
self.peer_views.remove(&peer_id); self.peer_data.remove(&peer_id);
self.blocks.iter_mut().for_each(|(_hash, entry)| { self.blocks.iter_mut().for_each(|(_hash, entry)| {
entry.known_by.remove(&peer_id); entry.known_by.remove(&peer_id);
}) })
@@ -393,12 +419,12 @@ impl State {
live live
}); });
}, },
NetworkBridgeEvent::PeerMessage(peer_id, msg) => {
self.process_incoming_peer_message(ctx, metrics, peer_id, msg, rng).await;
},
NetworkBridgeEvent::UpdatedAuthorityIds { .. } => { NetworkBridgeEvent::UpdatedAuthorityIds { .. } => {
// The approval-distribution subsystem doesn't deal with `AuthorityDiscoveryId`s. // The approval-distribution subsystem doesn't deal with `AuthorityDiscoveryId`s.
}, },
NetworkBridgeEvent::PeerMessage(peer_id, Versioned::V1(msg)) => {
self.process_incoming_peer_message(ctx, metrics, peer_id, msg, rng).await;
},
} }
} }
@@ -455,16 +481,18 @@ impl State {
{ {
let sender = ctx.sender(); let sender = ctx.sender();
for (peer_id, view) in self.peer_views.iter() { for (peer_id, data) in self.peer_data.iter() {
let intersection = view.iter().filter(|h| new_hashes.contains(h)); let intersection = data.view.iter().filter(|h| new_hashes.contains(h));
let view_intersection = View::new(intersection.cloned(), view.finalized_number); let view_intersection =
View::new(intersection.cloned(), data.view.finalized_number);
Self::unify_with_peer( Self::unify_with_peer(
sender, sender,
metrics, metrics,
&mut self.blocks, &mut self.blocks,
&self.topologies, &self.topologies,
self.peer_views.len(), self.peer_data.len(),
*peer_id, *peer_id,
data.version,
view_intersection, view_intersection,
rng, rng,
) )
@@ -547,6 +575,7 @@ impl State {
adjust_required_routing_and_propagate( adjust_required_routing_and_propagate(
ctx, ctx,
&self.peer_data,
&mut self.blocks, &mut self.blocks,
&self.topologies, &self.topologies,
|block_entry| block_entry.session == session, |block_entry| block_entry.session == session,
@@ -566,13 +595,16 @@ impl State {
ctx: &mut Context, ctx: &mut Context,
metrics: &Metrics, metrics: &Metrics,
peer_id: PeerId, peer_id: PeerId,
msg: protocol_v1::ApprovalDistributionMessage, msg: net_protocol::ApprovalDistributionMessage,
rng: &mut R, rng: &mut R,
) where ) where
R: CryptoRng + Rng, R: CryptoRng + Rng,
{ {
match msg { match msg {
protocol_v1::ApprovalDistributionMessage::Assignments(assignments) => { Versioned::V1(protocol_v1::ApprovalDistributionMessage::Assignments(assignments)) |
Versioned::VStaging(protocol_vstaging::ApprovalDistributionMessage::Assignments(
assignments,
)) => {
gum::trace!( gum::trace!(
target: LOG_TARGET, target: LOG_TARGET,
peer_id = %peer_id, peer_id = %peer_id,
@@ -611,7 +643,10 @@ impl State {
.await; .await;
} }
}, },
protocol_v1::ApprovalDistributionMessage::Approvals(approvals) => { Versioned::V1(protocol_v1::ApprovalDistributionMessage::Approvals(approvals)) |
Versioned::VStaging(protocol_vstaging::ApprovalDistributionMessage::Approvals(
approvals,
)) => {
gum::trace!( gum::trace!(
target: LOG_TARGET, target: LOG_TARGET,
peer_id = %peer_id, peer_id = %peer_id,
@@ -664,9 +699,14 @@ impl State {
{ {
gum::trace!(target: LOG_TARGET, ?view, "Peer view change"); gum::trace!(target: LOG_TARGET, ?view, "Peer view change");
let finalized_number = view.finalized_number; let finalized_number = view.finalized_number;
let old_view = let (peer_protocol_version, old_finalized_number) = match self
self.peer_views.get_mut(&peer_id).map(|d| std::mem::replace(d, view.clone())); .peer_data
let old_finalized_number = old_view.map(|v| v.finalized_number).unwrap_or(0); .get_mut(&peer_id)
.map(|d| (d.version, std::mem::replace(&mut d.view, view.clone())))
{
Some((v, view)) => (v, view.finalized_number),
None => return, // unknown peer
};
// we want to prune every block known_by peer up to (including) view.finalized_number // we want to prune every block known_by peer up to (including) view.finalized_number
let blocks = &mut self.blocks; let blocks = &mut self.blocks;
@@ -691,8 +731,9 @@ impl State {
metrics, metrics,
&mut self.blocks, &mut self.blocks,
&self.topologies, &self.topologies,
self.peer_views.len(), self.peer_data.len(),
peer_id, peer_id,
peer_protocol_version,
view, view,
rng, rng,
) )
@@ -992,7 +1033,7 @@ impl State {
// then messages will be sent when we get it. // then messages will be sent when we get it.
let assignments = vec![(assignment, claimed_candidate_index)]; let assignments = vec![(assignment, claimed_candidate_index)];
let n_peers_total = self.peer_views.len(); let n_peers_total = self.peer_data.len();
let source_peer = source.peer_id(); let source_peer = source.peer_id();
let mut peer_filter = move |peer| { let mut peer_filter = move |peer| {
@@ -1019,31 +1060,53 @@ impl State {
route_random route_random
}; };
let peers = entry.known_by.keys().filter(|p| peer_filter(p)).cloned().collect::<Vec<_>>(); let (v1_peers, vstaging_peers) = {
let peer_data = &self.peer_data;
let peers = entry
.known_by
.keys()
.filter_map(|p| peer_data.get_key_value(p))
.filter(|(p, _)| peer_filter(p))
.map(|(p, peer_data)| (*p, peer_data.version))
.collect::<Vec<_>>();
// Add the metadata of the assignment to the knowledge of each peer. // Add the metadata of the assignment to the knowledge of each peer.
for peer in peers.iter() { for (peer, _) in peers.iter() {
// we already filtered peers above, so this should always be Some // we already filtered peers above, so this should always be Some
if let Some(peer_knowledge) = entry.known_by.get_mut(peer) { if let Some(peer_knowledge) = entry.known_by.get_mut(peer) {
peer_knowledge.sent.insert(message_subject.clone(), message_kind); peer_knowledge.sent.insert(message_subject.clone(), message_kind);
}
} }
if !peers.is_empty() {
gum::trace!(
target: LOG_TARGET,
?block_hash,
?claimed_candidate_index,
local = source.peer_id().is_none(),
num_peers = peers.len(),
"Sending an assignment to peers",
);
}
let v1_peers = filter_peers_by_version(&peers, ValidationVersion::V1);
let vstaging_peers = filter_peers_by_version(&peers, ValidationVersion::VStaging);
(v1_peers, vstaging_peers)
};
if !v1_peers.is_empty() {
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
v1_peers,
versioned_assignments_packet(ValidationVersion::V1, assignments.clone()),
))
.await;
} }
if !peers.is_empty() { if !vstaging_peers.is_empty() {
gum::trace!(
target: LOG_TARGET,
?block_hash,
?claimed_candidate_index,
local = source.peer_id().is_none(),
num_peers = peers.len(),
"Sending an assignment to peers",
);
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
peers, vstaging_peers,
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( versioned_assignments_packet(ValidationVersion::VStaging, assignments.clone()),
protocol_v1::ApprovalDistributionMessage::Assignments(assignments),
)),
)) ))
.await; .await;
} }
@@ -1332,38 +1395,55 @@ impl State {
in_topology || knowledge.sent.contains(message_subject, MessageKind::Assignment) in_topology || knowledge.sent.contains(message_subject, MessageKind::Assignment)
}; };
let peers = entry let (v1_peers, vstaging_peers) = {
.known_by let peer_data = &self.peer_data;
.iter() let peers = entry
.filter(|(p, k)| peer_filter(p, k)) .known_by
.map(|(p, _)| p) .iter()
.cloned() .filter_map(|(p, k)| peer_data.get(&p).map(|pd| (p, k, pd.version)))
.collect::<Vec<_>>(); .filter(|(p, k, _)| peer_filter(p, k))
.map(|(p, _, v)| (*p, v))
.collect::<Vec<_>>();
// Add the metadata of the assignment to the knowledge of each peer. // Add the metadata of the assignment to the knowledge of each peer.
for peer in peers.iter() { for (peer, _) in peers.iter() {
// we already filtered peers above, so this should always be Some // we already filtered peers above, so this should always be Some
if let Some(entry) = entry.known_by.get_mut(peer) { if let Some(peer_knowledge) = entry.known_by.get_mut(peer) {
entry.sent.insert(message_subject.clone(), message_kind); peer_knowledge.sent.insert(message_subject.clone(), message_kind);
}
} }
if !peers.is_empty() {
gum::trace!(
target: LOG_TARGET,
?block_hash,
?candidate_index,
local = source.peer_id().is_none(),
num_peers = peers.len(),
"Sending an approval to peers",
);
}
let v1_peers = filter_peers_by_version(&peers, ValidationVersion::V1);
let vstaging_peers = filter_peers_by_version(&peers, ValidationVersion::VStaging);
(v1_peers, vstaging_peers)
};
let approvals = vec![vote];
if !v1_peers.is_empty() {
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
v1_peers,
versioned_approvals_packet(ValidationVersion::V1, approvals.clone()),
))
.await;
} }
if !peers.is_empty() { if !vstaging_peers.is_empty() {
let approvals = vec![vote];
gum::trace!(
target: LOG_TARGET,
?block_hash,
?candidate_index,
local = source.peer_id().is_none(),
num_peers = peers.len(),
"Sending an approval to peers",
);
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
peers, vstaging_peers,
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( versioned_approvals_packet(ValidationVersion::VStaging, approvals),
protocol_v1::ApprovalDistributionMessage::Approvals(approvals),
)),
)) ))
.await; .await;
} }
@@ -1427,6 +1507,7 @@ impl State {
topologies: &SessionGridTopologies, topologies: &SessionGridTopologies,
total_peers: usize, total_peers: usize,
peer_id: PeerId, peer_id: PeerId,
peer_protocol_version: ValidationVersion,
view: View, view: View,
rng: &mut (impl CryptoRng + Rng), rng: &mut (impl CryptoRng + Rng),
) { ) {
@@ -1536,7 +1617,8 @@ impl State {
"Sending assignments to unified peer", "Sending assignments to unified peer",
); );
send_assignments_batched(sender, assignments_to_send, peer_id).await; send_assignments_batched(sender, assignments_to_send, peer_id, peer_protocol_version)
.await;
} }
if !approvals_to_send.is_empty() { if !approvals_to_send.is_empty() {
@@ -1547,7 +1629,7 @@ impl State {
"Sending approvals to unified peer", "Sending approvals to unified peer",
); );
send_approvals_batched(sender, approvals_to_send, peer_id).await; send_approvals_batched(sender, approvals_to_send, peer_id, peer_protocol_version).await;
} }
} }
@@ -1583,6 +1665,7 @@ impl State {
adjust_required_routing_and_propagate( adjust_required_routing_and_propagate(
ctx, ctx,
&self.peer_data,
&mut self.blocks, &mut self.blocks,
&self.topologies, &self.topologies,
|block_entry| { |block_entry| {
@@ -1610,6 +1693,7 @@ impl State {
adjust_required_routing_and_propagate( adjust_required_routing_and_propagate(
ctx, ctx,
&self.peer_data,
&mut self.blocks, &mut self.blocks,
&self.topologies, &self.topologies,
|block_entry| { |block_entry| {
@@ -1669,6 +1753,7 @@ impl State {
#[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)] #[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)]
async fn adjust_required_routing_and_propagate<Context, BlockFilter, RoutingModifier>( async fn adjust_required_routing_and_propagate<Context, BlockFilter, RoutingModifier>(
ctx: &mut Context, ctx: &mut Context,
peer_data: &HashMap<PeerId, PeerData>,
blocks: &mut HashMap<Hash, BlockEntry>, blocks: &mut HashMap<Hash, BlockEntry>,
topologies: &SessionGridTopologies, topologies: &SessionGridTopologies,
block_filter: BlockFilter, block_filter: BlockFilter,
@@ -1758,11 +1843,22 @@ async fn adjust_required_routing_and_propagate<Context, BlockFilter, RoutingModi
// Send messages in accumulated packets, assignments preceding approvals. // Send messages in accumulated packets, assignments preceding approvals.
for (peer, assignments_packet) in peer_assignments { for (peer, assignments_packet) in peer_assignments {
send_assignments_batched(ctx.sender(), assignments_packet, peer).await; let peer_protocol_version = match peer_data.get(&peer).map(|pd| pd.version) {
None => continue,
Some(v) => v,
};
send_assignments_batched(ctx.sender(), assignments_packet, peer, peer_protocol_version)
.await;
} }
for (peer, approvals_packet) in peer_approvals { for (peer, approvals_packet) in peer_approvals {
send_approvals_batched(ctx.sender(), approvals_packet, peer).await; let peer_protocol_version = match peer_data.get(&peer).map(|pd| pd.version) {
None => continue,
Some(v) => v,
};
send_approvals_batched(ctx.sender(), approvals_packet, peer, peer_protocol_version).await;
} }
} }
@@ -1912,6 +2008,49 @@ impl ApprovalDistribution {
} }
} }
fn versioned_approvals_packet(
version: ValidationVersion,
approvals: Vec<IndirectSignedApprovalVote>,
) -> VersionedValidationProtocol {
match version {
ValidationVersion::V1 =>
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
protocol_v1::ApprovalDistributionMessage::Approvals(approvals),
)),
ValidationVersion::VStaging =>
Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution(
protocol_vstaging::ApprovalDistributionMessage::Approvals(approvals),
)),
}
}
fn versioned_assignments_packet(
version: ValidationVersion,
assignments: Vec<(IndirectAssignmentCert, CandidateIndex)>,
) -> VersionedValidationProtocol {
match version {
ValidationVersion::V1 =>
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
protocol_v1::ApprovalDistributionMessage::Assignments(assignments),
)),
ValidationVersion::VStaging =>
Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution(
protocol_vstaging::ApprovalDistributionMessage::Assignments(assignments),
)),
}
}
fn filter_peers_by_version(
peers: &[(PeerId, ValidationVersion)],
version: ValidationVersion,
) -> Vec<PeerId> {
peers
.iter()
.filter(|(_, v)| v == &version)
.map(|(peer_id, _)| *peer_id)
.collect()
}
#[overseer::subsystem(ApprovalDistribution, error=SubsystemError, prefix=self::overseer)] #[overseer::subsystem(ApprovalDistribution, error=SubsystemError, prefix=self::overseer)]
impl<Context> ApprovalDistribution { impl<Context> ApprovalDistribution {
fn start(self, ctx: Context) -> SpawnedSubsystem { fn start(self, ctx: Context) -> SpawnedSubsystem {
@@ -1954,19 +2093,16 @@ pub(crate) async fn send_assignments_batched(
sender: &mut impl overseer::ApprovalDistributionSenderTrait, sender: &mut impl overseer::ApprovalDistributionSenderTrait,
assignments: Vec<(IndirectAssignmentCert, CandidateIndex)>, assignments: Vec<(IndirectAssignmentCert, CandidateIndex)>,
peer: PeerId, peer: PeerId,
protocol_version: ValidationVersion,
) { ) {
let mut batches = assignments.into_iter().peekable(); let mut batches = assignments.into_iter().peekable();
while batches.peek().is_some() { while batches.peek().is_some() {
let batch: Vec<_> = batches.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect(); let batch: Vec<_> = batches.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect();
let versioned = versioned_assignments_packet(protocol_version, batch);
sender sender
.send_message(NetworkBridgeTxMessage::SendValidationMessage( .send_message(NetworkBridgeTxMessage::SendValidationMessage(vec![peer], versioned))
vec![peer],
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
protocol_v1::ApprovalDistributionMessage::Assignments(batch),
)),
))
.await; .await;
} }
} }
@@ -1976,19 +2112,16 @@ pub(crate) async fn send_approvals_batched(
sender: &mut impl overseer::ApprovalDistributionSenderTrait, sender: &mut impl overseer::ApprovalDistributionSenderTrait,
approvals: Vec<IndirectSignedApprovalVote>, approvals: Vec<IndirectSignedApprovalVote>,
peer: PeerId, peer: PeerId,
protocol_version: ValidationVersion,
) { ) {
let mut batches = approvals.into_iter().peekable(); let mut batches = approvals.into_iter().peekable();
while batches.peek().is_some() { while batches.peek().is_some() {
let batch: Vec<_> = batches.by_ref().take(MAX_APPROVAL_BATCH_SIZE).collect(); let batch: Vec<_> = batches.by_ref().take(MAX_APPROVAL_BATCH_SIZE).collect();
let versioned = versioned_approvals_packet(protocol_version, batch);
sender sender
.send_message(NetworkBridgeTxMessage::SendValidationMessage( .send_message(NetworkBridgeTxMessage::SendValidationMessage(vec![peer], versioned))
vec![peer],
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
protocol_v1::ApprovalDistributionMessage::Approvals(batch),
)),
))
.await; .await;
} }
} }
@@ -219,6 +219,7 @@ async fn setup_gossip_topology(
async fn setup_peer_with_view( async fn setup_peer_with_view(
virtual_overseer: &mut VirtualOverseer, virtual_overseer: &mut VirtualOverseer,
peer_id: &PeerId, peer_id: &PeerId,
validation_version: ValidationVersion,
view: View, view: View,
) { ) {
overseer_send( overseer_send(
@@ -226,7 +227,7 @@ async fn setup_peer_with_view(
ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected(
*peer_id, *peer_id,
ObservedRole::Full, ObservedRole::Full,
ValidationVersion::V1.into(), validation_version.into(),
None, None,
)), )),
) )
@@ -243,13 +244,12 @@ async fn setup_peer_with_view(
async fn send_message_from_peer( async fn send_message_from_peer(
virtual_overseer: &mut VirtualOverseer, virtual_overseer: &mut VirtualOverseer,
peer_id: &PeerId, peer_id: &PeerId,
msg: protocol_v1::ApprovalDistributionMessage, msg: net_protocol::ApprovalDistributionMessage,
) { ) {
overseer_send( overseer_send(
virtual_overseer, virtual_overseer,
ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage( ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(
*peer_id, *peer_id, msg,
Versioned::V1(msg),
)), )),
) )
.await; .await;
@@ -331,9 +331,9 @@ fn try_import_the_same_assignment() {
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
let overseer = &mut virtual_overseer; let overseer = &mut virtual_overseer;
// setup peers // setup peers
setup_peer_with_view(overseer, &peer_a, view![]).await; setup_peer_with_view(overseer, &peer_a, ValidationVersion::V1, view![]).await;
setup_peer_with_view(overseer, &peer_b, view![hash]).await; setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await;
setup_peer_with_view(overseer, &peer_c, view![hash]).await; setup_peer_with_view(overseer, &peer_c, ValidationVersion::V1, view![hash]).await;
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
let meta = BlockApprovalMeta { let meta = BlockApprovalMeta {
@@ -353,7 +353,7 @@ fn try_import_the_same_assignment() {
let assignments = vec![(cert.clone(), 0u32)]; let assignments = vec![(cert.clone(), 0u32)];
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone());
send_message_from_peer(overseer, &peer_a, msg).await; send_message_from_peer(overseer, &peer_a, Versioned::V1(msg)).await;
expect_reputation_change(overseer, &peer_a, COST_UNEXPECTED_MESSAGE).await; expect_reputation_change(overseer, &peer_a, COST_UNEXPECTED_MESSAGE).await;
@@ -386,11 +386,11 @@ fn try_import_the_same_assignment() {
); );
// setup new peer // setup new peer
setup_peer_with_view(overseer, &peer_d, view![]).await; setup_peer_with_view(overseer, &peer_d, ValidationVersion::V1, view![]).await;
// send the same assignment from peer_d // send the same assignment from peer_d
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments);
send_message_from_peer(overseer, &peer_d, msg).await; send_message_from_peer(overseer, &peer_d, Versioned::V1(msg)).await;
expect_reputation_change(overseer, &peer_d, COST_UNEXPECTED_MESSAGE).await; expect_reputation_change(overseer, &peer_d, COST_UNEXPECTED_MESSAGE).await;
expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE).await; expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE).await;
@@ -413,7 +413,7 @@ fn delay_reputation_change() {
let overseer = &mut virtual_overseer; let overseer = &mut virtual_overseer;
// Setup peers // Setup peers
setup_peer_with_view(overseer, &peer, view![]).await; setup_peer_with_view(overseer, &peer, ValidationVersion::V1, view![]).await;
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
let meta = BlockApprovalMeta { let meta = BlockApprovalMeta {
@@ -433,7 +433,7 @@ fn delay_reputation_change() {
let assignments = vec![(cert.clone(), 0u32)]; let assignments = vec![(cert.clone(), 0u32)];
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone());
send_message_from_peer(overseer, &peer, msg).await; send_message_from_peer(overseer, &peer, Versioned::V1(msg)).await;
// send an `Accept` message from the Approval Voting subsystem // send an `Accept` message from the Approval Voting subsystem
assert_matches!( assert_matches!(
@@ -474,7 +474,7 @@ fn spam_attack_results_in_negative_reputation_change() {
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
let overseer = &mut virtual_overseer; let overseer = &mut virtual_overseer;
let peer = &peer_a; let peer = &peer_a;
setup_peer_with_view(overseer, peer, view![]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![]).await;
// new block `hash_b` with 20 candidates // new block `hash_b` with 20 candidates
let candidates_count = 20; let candidates_count = 20;
@@ -501,7 +501,7 @@ fn spam_attack_results_in_negative_reputation_change() {
.collect(); .collect();
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone());
send_message_from_peer(overseer, peer, msg.clone()).await; send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await;
for i in 0..candidates_count { for i in 0..candidates_count {
expect_reputation_change(overseer, peer, COST_UNEXPECTED_MESSAGE).await; expect_reputation_change(overseer, peer, COST_UNEXPECTED_MESSAGE).await;
@@ -533,7 +533,7 @@ fn spam_attack_results_in_negative_reputation_change() {
.await; .await;
// send the assignments again // send the assignments again
send_message_from_peer(overseer, peer, msg.clone()).await; send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await;
// each of them will incur `COST_UNEXPECTED_MESSAGE`, not only the first one // each of them will incur `COST_UNEXPECTED_MESSAGE`, not only the first one
for _ in 0..candidates_count { for _ in 0..candidates_count {
@@ -558,7 +558,7 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() {
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
let overseer = &mut virtual_overseer; let overseer = &mut virtual_overseer;
let peer = &peer_a; let peer = &peer_a;
setup_peer_with_view(overseer, peer, view![]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![]).await;
// new block `hash` with 1 candidates // new block `hash` with 1 candidates
let meta = BlockApprovalMeta { let meta = BlockApprovalMeta {
@@ -610,12 +610,12 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() {
// the peer could send us it as well // the peer could send us it as well
let assignments = vec![(cert, candidate_index)]; let assignments = vec![(cert, candidate_index)];
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments);
send_message_from_peer(overseer, peer, msg.clone()).await; send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await;
assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "we should not punish the peer"); assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "we should not punish the peer");
// send the assignments again // send the assignments again
send_message_from_peer(overseer, peer, msg).await; send_message_from_peer(overseer, peer, Versioned::V1(msg)).await;
// now we should // now we should
expect_reputation_change(overseer, peer, COST_DUPLICATE_MESSAGE).await; expect_reputation_change(overseer, peer, COST_DUPLICATE_MESSAGE).await;
@@ -634,9 +634,9 @@ fn import_approval_happy_path() {
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
let overseer = &mut virtual_overseer; let overseer = &mut virtual_overseer;
// setup peers // setup peers
setup_peer_with_view(overseer, &peer_a, view![]).await; setup_peer_with_view(overseer, &peer_a, ValidationVersion::V1, view![]).await;
setup_peer_with_view(overseer, &peer_b, view![hash]).await; setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await;
setup_peer_with_view(overseer, &peer_c, view![hash]).await; setup_peer_with_view(overseer, &peer_c, ValidationVersion::V1, view![hash]).await;
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
let meta = BlockApprovalMeta { let meta = BlockApprovalMeta {
@@ -681,7 +681,7 @@ fn import_approval_happy_path() {
signature: dummy_signature(), signature: dummy_signature(),
}; };
let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]);
send_message_from_peer(overseer, &peer_b, msg).await; send_message_from_peer(overseer, &peer_b, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
@@ -722,8 +722,8 @@ fn import_approval_bad() {
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
let overseer = &mut virtual_overseer; let overseer = &mut virtual_overseer;
// setup peers // setup peers
setup_peer_with_view(overseer, &peer_a, view![]).await; setup_peer_with_view(overseer, &peer_a, ValidationVersion::V1, view![]).await;
setup_peer_with_view(overseer, &peer_b, view![hash]).await; setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await;
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
let meta = BlockApprovalMeta { let meta = BlockApprovalMeta {
@@ -749,14 +749,14 @@ fn import_approval_bad() {
signature: dummy_signature(), signature: dummy_signature(),
}; };
let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]);
send_message_from_peer(overseer, &peer_b, msg).await; send_message_from_peer(overseer, &peer_b, Versioned::V1(msg)).await;
expect_reputation_change(overseer, &peer_b, COST_UNEXPECTED_MESSAGE).await; expect_reputation_change(overseer, &peer_b, COST_UNEXPECTED_MESSAGE).await;
// now import an assignment from peer_b // now import an assignment from peer_b
let assignments = vec![(cert.clone(), candidate_index)]; let assignments = vec![(cert.clone(), candidate_index)];
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments);
send_message_from_peer(overseer, &peer_b, msg).await; send_message_from_peer(overseer, &peer_b, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
@@ -775,7 +775,7 @@ fn import_approval_bad() {
// and try again // and try again
let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]);
send_message_from_peer(overseer, &peer_b, msg).await; send_message_from_peer(overseer, &peer_b, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
@@ -916,7 +916,7 @@ fn update_peer_view() {
overseer_send(overseer, ApprovalDistributionMessage::DistributeAssignment(cert_b, 0)).await; overseer_send(overseer, ApprovalDistributionMessage::DistributeAssignment(cert_b, 0)).await;
// connect a peer // connect a peer
setup_peer_with_view(overseer, peer, view![hash_a]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash_a]).await;
// we should send relevant assignments to the peer // we should send relevant assignments to the peer
assert_matches!( assert_matches!(
@@ -934,7 +934,7 @@ fn update_peer_view() {
virtual_overseer virtual_overseer
}); });
assert_eq!(state.peer_views.get(peer).map(|v| v.finalized_number), Some(0)); assert_eq!(state.peer_data.get(peer).map(|data| data.view.finalized_number), Some(0));
assert_eq!( assert_eq!(
state state
.blocks .blocks
@@ -986,7 +986,7 @@ fn update_peer_view() {
virtual_overseer virtual_overseer
}); });
assert_eq!(state.peer_views.get(peer).map(|v| v.finalized_number), Some(2)); assert_eq!(state.peer_data.get(peer).map(|data| data.view.finalized_number), Some(2));
assert_eq!( assert_eq!(
state state
.blocks .blocks
@@ -1016,7 +1016,10 @@ fn update_peer_view() {
virtual_overseer virtual_overseer
}); });
assert_eq!(state.peer_views.get(peer).map(|v| v.finalized_number), Some(finalized_number)); assert_eq!(
state.peer_data.get(peer).map(|data| data.view.finalized_number),
Some(finalized_number)
);
assert!(state.blocks.get(&hash_c).unwrap().known_by.get(peer).is_none()); assert!(state.blocks.get(&hash_c).unwrap().known_by.get(peer).is_none());
} }
@@ -1031,7 +1034,7 @@ fn import_remotely_then_locally() {
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
let overseer = &mut virtual_overseer; let overseer = &mut virtual_overseer;
// setup the peer // setup the peer
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
let meta = BlockApprovalMeta { let meta = BlockApprovalMeta {
@@ -1051,7 +1054,7 @@ fn import_remotely_then_locally() {
let cert = fake_assignment_cert(hash, validator_index); let cert = fake_assignment_cert(hash, validator_index);
let assignments = vec![(cert.clone(), candidate_index)]; let assignments = vec![(cert.clone(), candidate_index)];
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone());
send_message_from_peer(overseer, peer, msg).await; send_message_from_peer(overseer, peer, Versioned::V1(msg)).await;
// send an `Accept` message from the Approval Voting subsystem // send an `Accept` message from the Approval Voting subsystem
assert_matches!( assert_matches!(
@@ -1086,7 +1089,7 @@ fn import_remotely_then_locally() {
signature: dummy_signature(), signature: dummy_signature(),
}; };
let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]);
send_message_from_peer(overseer, peer, msg).await; send_message_from_peer(overseer, peer, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
@@ -1152,7 +1155,7 @@ fn sends_assignments_even_when_state_is_approved() {
.await; .await;
// connect the peer. // connect the peer.
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
let assignments = vec![(cert.clone(), candidate_index)]; let assignments = vec![(cert.clone(), candidate_index)];
let approvals = vec![approval.clone()]; let approvals = vec![approval.clone()];
@@ -1216,7 +1219,7 @@ fn race_condition_in_local_vs_remote_view_update() {
}; };
// This will send a peer view that is ahead of our view // This will send a peer view that is ahead of our view
setup_peer_with_view(overseer, peer, view![hash_b]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash_b]).await;
// Send our view update to include a new head // Send our view update to include a new head
overseer_send( overseer_send(
@@ -1237,7 +1240,7 @@ fn race_condition_in_local_vs_remote_view_update() {
.collect(); .collect();
let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone());
send_message_from_peer(overseer, peer, msg.clone()).await; send_message_from_peer(overseer, peer, Versioned::V1(msg.clone())).await;
// This will handle pending messages being processed // This will handle pending messages being processed
let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]);
@@ -1280,7 +1283,7 @@ fn propagates_locally_generated_assignment_to_both_dimensions() {
// Connect all peers. // Connect all peers.
for (peer, _) in &peers { for (peer, _) in &peers {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
// Set up a gossip topology. // Set up a gossip topology.
@@ -1385,7 +1388,7 @@ fn propagates_assignments_along_unshared_dimension() {
// Connect all peers. // Connect all peers.
for (peer, _) in &peers { for (peer, _) in &peers {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
// Set up a gossip topology. // Set up a gossip topology.
@@ -1421,7 +1424,7 @@ fn propagates_assignments_along_unshared_dimension() {
// Issuer of the message is important, not the peer we receive from. // Issuer of the message is important, not the peer we receive from.
// 99 deliberately chosen because it's not in X or Y. // 99 deliberately chosen because it's not in X or Y.
send_message_from_peer(overseer, &peers[99].0, msg).await; send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
@@ -1470,7 +1473,7 @@ fn propagates_assignments_along_unshared_dimension() {
// Issuer of the message is important, not the peer we receive from. // Issuer of the message is important, not the peer we receive from.
// 99 deliberately chosen because it's not in X or Y. // 99 deliberately chosen because it's not in X or Y.
send_message_from_peer(overseer, &peers[99].0, msg).await; send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
@@ -1527,7 +1530,7 @@ fn propagates_to_required_after_connect() {
// Connect all peers except omitted. // Connect all peers except omitted.
for (i, (peer, _)) in peers.iter().enumerate() { for (i, (peer, _)) in peers.iter().enumerate() {
if !omitted.contains(&i) { if !omitted.contains(&i) {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
} }
@@ -1616,7 +1619,7 @@ fn propagates_to_required_after_connect() {
); );
for i in omitted.iter().copied() { for i in omitted.iter().copied() {
setup_peer_with_view(overseer, &peers[i].0, view![hash]).await; setup_peer_with_view(overseer, &peers[i].0, ValidationVersion::V1, view![hash]).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
@@ -1665,7 +1668,7 @@ fn sends_to_more_peers_after_getting_topology() {
// Connect all peers except omitted. // Connect all peers except omitted.
for (peer, _) in &peers { for (peer, _) in &peers {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
@@ -1817,7 +1820,7 @@ fn originator_aggression_l1() {
// Connect all peers except omitted. // Connect all peers except omitted.
for (peer, _) in &peers { for (peer, _) in &peers {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
@@ -1976,7 +1979,7 @@ fn non_originator_aggression_l1() {
// Connect all peers except omitted. // Connect all peers except omitted.
for (peer, _) in &peers { for (peer, _) in &peers {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
@@ -2010,7 +2013,7 @@ fn non_originator_aggression_l1() {
// Issuer of the message is important, not the peer we receive from. // Issuer of the message is important, not the peer we receive from.
// 99 deliberately chosen because it's not in X or Y. // 99 deliberately chosen because it's not in X or Y.
send_message_from_peer(overseer, &peers[99].0, msg).await; send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
@@ -2081,7 +2084,7 @@ fn non_originator_aggression_l2() {
// Connect all peers except omitted. // Connect all peers except omitted.
for (peer, _) in &peers { for (peer, _) in &peers {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
// new block `hash_a` with 1 candidates // new block `hash_a` with 1 candidates
@@ -2115,7 +2118,7 @@ fn non_originator_aggression_l2() {
// Issuer of the message is important, not the peer we receive from. // Issuer of the message is important, not the peer we receive from.
// 99 deliberately chosen because it's not in X or Y. // 99 deliberately chosen because it's not in X or Y.
send_message_from_peer(overseer, &peers[99].0, msg).await; send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
@@ -2246,7 +2249,7 @@ fn resends_messages_periodically() {
// Connect all peers. // Connect all peers.
for (peer, _) in &peers { for (peer, _) in &peers {
setup_peer_with_view(overseer, peer, view![hash]).await; setup_peer_with_view(overseer, peer, ValidationVersion::V1, view![hash]).await;
} }
// Set up a gossip topology. // Set up a gossip topology.
@@ -2281,7 +2284,7 @@ fn resends_messages_periodically() {
// Issuer of the message is important, not the peer we receive from. // Issuer of the message is important, not the peer we receive from.
// 99 deliberately chosen because it's not in X or Y. // 99 deliberately chosen because it's not in X or Y.
send_message_from_peer(overseer, &peers[99].0, msg).await; send_message_from_peer(overseer, &peers[99].0, Versioned::V1(msg)).await;
assert_matches!( assert_matches!(
overseer_recv(overseer).await, overseer_recv(overseer).await,
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
@@ -2372,6 +2375,126 @@ fn resends_messages_periodically() {
}); });
} }
/// Tests that peers correctly receive versioned messages.
#[test]
fn import_versioned_approval() {
let peer_a = PeerId::random();
let peer_b = PeerId::random();
let peer_c = PeerId::random();
let parent_hash = Hash::repeat_byte(0xFF);
let hash = Hash::repeat_byte(0xAA);
let state = state_without_reputation_delay();
let _ = test_harness(state, |mut virtual_overseer| async move {
let overseer = &mut virtual_overseer;
// All peers are aware of relay parent.
setup_peer_with_view(overseer, &peer_a, ValidationVersion::VStaging, view![hash]).await;
setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await;
setup_peer_with_view(overseer, &peer_c, ValidationVersion::VStaging, view![hash]).await;
// new block `hash_a` with 1 candidates
let meta = BlockApprovalMeta {
hash,
parent_hash,
number: 1,
candidates: vec![Default::default(); 1],
slot: 1.into(),
session: 1,
};
let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]);
overseer_send(overseer, msg).await;
// import an assignment related to `hash` locally
let validator_index = ValidatorIndex(0);
let candidate_index = 0u32;
let cert = fake_assignment_cert(hash, validator_index);
overseer_send(
overseer,
ApprovalDistributionMessage::DistributeAssignment(cert, candidate_index),
)
.await;
assert_matches!(
overseer_recv(overseer).await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(
peers,
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
protocol_v1::ApprovalDistributionMessage::Assignments(assignments)
))
)) => {
assert_eq!(peers, vec![peer_b]);
assert_eq!(assignments.len(), 1);
}
);
assert_matches!(
overseer_recv(overseer).await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(
peers,
Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution(
protocol_vstaging::ApprovalDistributionMessage::Assignments(assignments)
))
)) => {
assert_eq!(peers.len(), 2);
assert!(peers.contains(&peer_a));
assert!(peers.contains(&peer_c));
assert_eq!(assignments.len(), 1);
}
);
// send the an approval from peer_a
let approval = IndirectSignedApprovalVote {
block_hash: hash,
candidate_index,
validator: validator_index,
signature: dummy_signature(),
};
let msg = protocol_vstaging::ApprovalDistributionMessage::Approvals(vec![approval.clone()]);
send_message_from_peer(overseer, &peer_a, Versioned::VStaging(msg)).await;
assert_matches!(
overseer_recv(overseer).await,
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportApproval(
vote,
tx,
)) => {
assert_eq!(vote, approval);
tx.send(ApprovalCheckResult::Accepted).unwrap();
}
);
expect_reputation_change(overseer, &peer_a, BENEFIT_VALID_MESSAGE_FIRST).await;
// Peers b and c receive versioned approval messages.
assert_matches!(
overseer_recv(overseer).await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(
peers,
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
protocol_v1::ApprovalDistributionMessage::Approvals(approvals)
))
)) => {
assert_eq!(peers, vec![peer_b]);
assert_eq!(approvals.len(), 1);
}
);
assert_matches!(
overseer_recv(overseer).await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(
peers,
Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution(
protocol_vstaging::ApprovalDistributionMessage::Approvals(approvals)
))
)) => {
assert_eq!(peers, vec![peer_c]);
assert_eq!(approvals.len(), 1);
}
);
virtual_overseer
});
}
fn batch_test_round(message_count: usize) { fn batch_test_round(message_count: usize) {
use polkadot_node_subsystem::SubsystemContext; use polkadot_node_subsystem::SubsystemContext;
let pool = sp_core::testing::TaskExecutor::new(); let pool = sp_core::testing::TaskExecutor::new();
@@ -2402,8 +2525,9 @@ fn batch_test_round(message_count: usize) {
.collect(); .collect();
let peer = PeerId::random(); let peer = PeerId::random();
send_assignments_batched(&mut sender, assignments.clone(), peer).await; send_assignments_batched(&mut sender, assignments.clone(), peer, ValidationVersion::V1)
send_approvals_batched(&mut sender, approvals.clone(), peer).await; .await;
send_approvals_batched(&mut sender, approvals.clone(), peer, ValidationVersion::V1).await;
// Check expected assignments batches. // Check expected assignments batches.
for assignment_index in (0..assignments.len()).step_by(super::MAX_ASSIGNMENT_BATCH_SIZE) { for assignment_index in (0..assignments.len()).step_by(super::MAX_ASSIGNMENT_BATCH_SIZE) {
@@ -6,6 +6,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
always-assert = "0.1"
futures = "0.3.21" futures = "0.3.21"
futures-timer = "3.0.2" futures-timer = "3.0.2"
gum = { package = "tracing-gum", path = "../../gum" } gum = { package = "tracing-gum", path = "../../gum" }
@@ -22,6 +22,7 @@
#![deny(unused_crate_dependencies)] #![deny(unused_crate_dependencies)]
use always_assert::never;
use futures::{channel::oneshot, FutureExt}; use futures::{channel::oneshot, FutureExt};
use polkadot_node_network_protocol::{ use polkadot_node_network_protocol::{
@@ -29,7 +30,9 @@ use polkadot_node_network_protocol::{
grid_topology::{ grid_topology::{
GridNeighbors, RandomRouting, RequiredRouting, SessionBoundGridTopologyStorage, GridNeighbors, RandomRouting, RequiredRouting, SessionBoundGridTopologyStorage,
}, },
v1 as protocol_v1, OurView, PeerId, UnifiedReputationChange as Rep, Versioned, View, peer_set::{ProtocolVersion, ValidationVersion},
v1 as protocol_v1, vstaging as protocol_vstaging, OurView, PeerId,
UnifiedReputationChange as Rep, Versioned, View,
}; };
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
jaeger, messages::*, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, PerLeafSpan, jaeger, messages::*, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, PerLeafSpan,
@@ -76,25 +79,63 @@ struct BitfieldGossipMessage {
} }
impl BitfieldGossipMessage { impl BitfieldGossipMessage {
fn into_validation_protocol(self) -> net_protocol::VersionedValidationProtocol { fn into_validation_protocol(
self.into_network_message().into() self,
recipient_version: ProtocolVersion,
) -> net_protocol::VersionedValidationProtocol {
self.into_network_message(recipient_version).into()
} }
fn into_network_message(self) -> net_protocol::BitfieldDistributionMessage { fn into_network_message(
Versioned::V1(protocol_v1::BitfieldDistributionMessage::Bitfield( self,
self.relay_parent, recipient_version: ProtocolVersion,
self.signed_availability.into(), ) -> net_protocol::BitfieldDistributionMessage {
)) match ValidationVersion::try_from(recipient_version).ok() {
Some(ValidationVersion::V1) =>
Versioned::V1(protocol_v1::BitfieldDistributionMessage::Bitfield(
self.relay_parent,
self.signed_availability.into(),
)),
Some(ValidationVersion::VStaging) =>
Versioned::VStaging(protocol_vstaging::BitfieldDistributionMessage::Bitfield(
self.relay_parent,
self.signed_availability.into(),
)),
None => {
never!("Peers should only have supported protocol versions.");
gum::warn!(
target: LOG_TARGET,
version = ?recipient_version,
"Unknown protocol version provided for message recipient"
);
// fall back to v1 to avoid
Versioned::V1(protocol_v1::BitfieldDistributionMessage::Bitfield(
self.relay_parent,
self.signed_availability.into(),
))
},
}
} }
} }
/// Data stored on a per-peer basis.
#[derive(Debug)]
pub struct PeerData {
/// The peer's view.
view: View,
/// The peer's protocol version.
version: ProtocolVersion,
}
/// Data used to track information of peers and relay parents the /// Data used to track information of peers and relay parents the
/// overseer ordered us to work on. /// overseer ordered us to work on.
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct ProtocolState { struct ProtocolState {
/// Track all active peers and their views /// Track all active peers and their views
/// to determine what is relevant to them. /// to determine what is relevant to them.
peer_views: HashMap<PeerId, View>, peer_data: HashMap<PeerId, PeerData>,
/// The current and previous gossip topologies /// The current and previous gossip topologies
topologies: SessionBoundGridTopologyStorage, topologies: SessionBoundGridTopologyStorage,
@@ -357,7 +398,7 @@ async fn handle_bitfield_distribution<Context>(
ctx, ctx,
job_data, job_data,
topology, topology,
&mut state.peer_views, &mut state.peer_data,
validator, validator,
msg, msg,
required_routing, required_routing,
@@ -376,7 +417,7 @@ async fn relay_message<Context>(
ctx: &mut Context, ctx: &mut Context,
job_data: &mut PerRelayParentData, job_data: &mut PerRelayParentData,
topology_neighbors: &GridNeighbors, topology_neighbors: &GridNeighbors,
peer_views: &mut HashMap<PeerId, View>, peers: &mut HashMap<PeerId, PeerData>,
validator: ValidatorId, validator: ValidatorId,
message: BitfieldGossipMessage, message: BitfieldGossipMessage,
required_routing: RequiredRouting, required_routing: RequiredRouting,
@@ -394,16 +435,16 @@ async fn relay_message<Context>(
.await; .await;
drop(_span); drop(_span);
let total_peers = peer_views.len(); let total_peers = peers.len();
let mut random_routing: RandomRouting = Default::default(); let mut random_routing: RandomRouting = Default::default();
let _span = span.child("interested-peers"); let _span = span.child("interested-peers");
// pass on the bitfield distribution to all interested peers // pass on the bitfield distribution to all interested peers
let interested_peers = peer_views let interested_peers = peers
.iter() .iter()
.filter_map(|(peer, view)| { .filter_map(|(peer, data)| {
// check interest in the peer in this message's relay parent // check interest in the peer in this message's relay parent
if view.contains(&message.relay_parent) { if data.view.contains(&message.relay_parent) {
let message_needed = let message_needed =
job_data.message_from_validator_needed_by_peer(&peer, &validator); job_data.message_from_validator_needed_by_peer(&peer, &validator);
if message_needed { if message_needed {
@@ -418,7 +459,7 @@ async fn relay_message<Context>(
}; };
if need_routing { if need_routing {
Some(*peer) Some((*peer, data.version))
} else { } else {
None None
} }
@@ -429,9 +470,9 @@ async fn relay_message<Context>(
None None
} }
}) })
.collect::<Vec<PeerId>>(); .collect::<Vec<(PeerId, ProtocolVersion)>>();
interested_peers.iter().for_each(|peer| { interested_peers.iter().for_each(|(peer, _)| {
// track the message as sent for this peer // track the message as sent for this peer
job_data job_data
.message_sent_to_peer .message_sent_to_peer
@@ -450,11 +491,35 @@ async fn relay_message<Context>(
); );
} else { } else {
let _span = span.child("gossip"); let _span = span.child("gossip");
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
interested_peers, let filter_by_version = |peers: &[(PeerId, ProtocolVersion)],
message.into_validation_protocol(), version: ValidationVersion| {
)) peers
.await; .iter()
.filter(|(_, v)| v == &version.into())
.map(|(peer_id, _)| *peer_id)
.collect::<Vec<_>>()
};
let v1_interested_peers = filter_by_version(&interested_peers, ValidationVersion::V1);
let vstaging_interested_peers =
filter_by_version(&interested_peers, ValidationVersion::VStaging);
if !v1_interested_peers.is_empty() {
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
v1_interested_peers,
message.clone().into_validation_protocol(ValidationVersion::V1.into()),
))
.await;
}
if !vstaging_interested_peers.is_empty() {
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
vstaging_interested_peers,
message.into_validation_protocol(ValidationVersion::VStaging.into()),
))
.await
}
} }
} }
@@ -465,10 +530,20 @@ async fn process_incoming_peer_message<Context>(
state: &mut ProtocolState, state: &mut ProtocolState,
metrics: &Metrics, metrics: &Metrics,
origin: PeerId, origin: PeerId,
message: protocol_v1::BitfieldDistributionMessage, message: net_protocol::BitfieldDistributionMessage,
rng: &mut (impl CryptoRng + Rng), rng: &mut (impl CryptoRng + Rng),
) { ) {
let protocol_v1::BitfieldDistributionMessage::Bitfield(relay_parent, bitfield) = message; let (relay_parent, bitfield) = match message {
Versioned::V1(protocol_v1::BitfieldDistributionMessage::Bitfield(
relay_parent,
bitfield,
)) => (relay_parent, bitfield),
Versioned::VStaging(protocol_vstaging::BitfieldDistributionMessage::Bitfield(
relay_parent,
bitfield,
)) => (relay_parent, bitfield),
};
gum::trace!( gum::trace!(
target: LOG_TARGET, target: LOG_TARGET,
peer = %origin, peer = %origin,
@@ -616,7 +691,7 @@ async fn process_incoming_peer_message<Context>(
ctx, ctx,
job_data, job_data,
topology, topology,
&mut state.peer_views, &mut state.peer_data,
validator, validator,
message, message,
required_routing, required_routing,
@@ -647,15 +722,18 @@ async fn handle_network_msg<Context>(
let _timer = metrics.time_handle_network_msg(); let _timer = metrics.time_handle_network_msg();
match bridge_message { match bridge_message {
NetworkBridgeEvent::PeerConnected(peer, role, _, _) => { NetworkBridgeEvent::PeerConnected(peer, role, version, _) => {
gum::trace!(target: LOG_TARGET, ?peer, ?role, "Peer connected"); gum::trace!(target: LOG_TARGET, ?peer, ?role, "Peer connected");
// insert if none already present // insert if none already present
state.peer_views.entry(peer).or_default(); state
.peer_data
.entry(peer)
.or_insert_with(|| PeerData { view: View::default(), version });
}, },
NetworkBridgeEvent::PeerDisconnected(peer) => { NetworkBridgeEvent::PeerDisconnected(peer) => {
gum::trace!(target: LOG_TARGET, ?peer, "Peer disconnected"); gum::trace!(target: LOG_TARGET, ?peer, "Peer disconnected");
// get rid of superfluous data // get rid of superfluous data
state.peer_views.remove(&peer); state.peer_data.remove(&peer);
}, },
NetworkBridgeEvent::NewGossipTopology(gossip_topology) => { NetworkBridgeEvent::NewGossipTopology(gossip_topology) => {
let session_index = gossip_topology.session; let session_index = gossip_topology.session;
@@ -680,12 +758,21 @@ async fn handle_network_msg<Context>(
); );
for new_peer in newly_added { for new_peer in newly_added {
// in case we already knew that peer in the past let old_view = match state.peer_data.get_mut(&new_peer) {
// it might have had an existing view, we use to initialize Some(d) => {
// and minimize the delta on `PeerViewChange` to be sent // in case we already knew that peer in the past
if let Some(old_view) = state.peer_views.remove(&new_peer) { // it might have had an existing view, we use to initialize
handle_peer_view_change(ctx, state, new_peer, old_view, rng).await; // and minimize the delta on `PeerViewChange` to be sent
} std::mem::replace(&mut d.view, Default::default())
},
None => {
// For peers which are currently unknown, we'll send topology-related
// messages to them when they connect and send their first view update.
continue
},
};
handle_peer_view_change(ctx, state, new_peer, old_view, rng).await;
} }
}, },
NetworkBridgeEvent::PeerViewChange(peerid, new_view) => { NetworkBridgeEvent::PeerViewChange(peerid, new_view) => {
@@ -696,7 +783,7 @@ async fn handle_network_msg<Context>(
gum::trace!(target: LOG_TARGET, ?new_view, "Our view change"); gum::trace!(target: LOG_TARGET, ?new_view, "Our view change");
handle_our_view_change(state, new_view); handle_our_view_change(state, new_view);
}, },
NetworkBridgeEvent::PeerMessage(remote, Versioned::V1(message)) => NetworkBridgeEvent::PeerMessage(remote, message) =>
process_incoming_peer_message(ctx, state, metrics, remote, message, rng).await, process_incoming_peer_message(ctx, state, metrics, remote, message, rng).await,
NetworkBridgeEvent::UpdatedAuthorityIds { .. } => { NetworkBridgeEvent::UpdatedAuthorityIds { .. } => {
// The bitfield-distribution subsystem doesn't deal with `AuthorityDiscoveryId`s. // The bitfield-distribution subsystem doesn't deal with `AuthorityDiscoveryId`s.
@@ -728,6 +815,9 @@ fn handle_our_view_change(state: &mut ProtocolState, view: OurView) {
// Send the difference between two views which were not sent // Send the difference between two views which were not sent
// to that particular peer. // to that particular peer.
//
// This requires that there is an entry in the `peer_data` field for the
// peer.
#[overseer::contextbounds(BitfieldDistribution, prefix=self::overseer)] #[overseer::contextbounds(BitfieldDistribution, prefix=self::overseer)]
async fn handle_peer_view_change<Context>( async fn handle_peer_view_change<Context>(
ctx: &mut Context, ctx: &mut Context,
@@ -736,13 +826,20 @@ async fn handle_peer_view_change<Context>(
view: View, view: View,
rng: &mut (impl CryptoRng + Rng), rng: &mut (impl CryptoRng + Rng),
) { ) {
let added = state let peer_data = match state.peer_data.get_mut(&origin) {
.peer_views None => {
.entry(origin) gum::warn!(
.or_default() target: LOG_TARGET,
.replace_difference(view) peer = ?origin,
.cloned() "Attempted to update peer view for unknown peer."
.collect::<Vec<_>>(); );
return
},
Some(pd) => pd,
};
let added = peer_data.view.replace_difference(view).cloned().collect::<Vec<_>>();
let topology = state.topologies.get_current_topology().local_grid_neighbors(); let topology = state.topologies.get_current_topology().local_grid_neighbors();
let is_gossip_peer = topology.route_to_peer(RequiredRouting::GridXY, &origin); let is_gossip_peer = topology.route_to_peer(RequiredRouting::GridXY, &origin);
@@ -808,11 +905,14 @@ async fn send_tracked_gossip_message<Context>(
"Sending gossip message" "Sending gossip message"
); );
let version =
if let Some(peer_data) = state.peer_data.get(&dest) { peer_data.version } else { return };
job_data.message_sent_to_peer.entry(dest).or_default().insert(validator.clone()); job_data.message_sent_to_peer.entry(dest).or_default().insert(validator.clone());
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
vec![dest], vec![dest],
message.into_validation_protocol(), message.into_validation_protocol(version),
)) ))
.await; .await;
} }
@@ -56,6 +56,10 @@ fn dummy_rng() -> ChaCha12Rng {
rand_chacha::ChaCha12Rng::seed_from_u64(12345) rand_chacha::ChaCha12Rng::seed_from_u64(12345)
} }
fn peer_data_v1(view: View) -> PeerData {
PeerData { view, version: ValidationVersion::V1.into() }
}
/// A very limited state, only interested in the relay parent of the /// A very limited state, only interested in the relay parent of the
/// given message, which must be signed by `validator` and a set of peers /// given message, which must be signed by `validator` and a set of peers
/// which are also only interested in that relay parent. /// which are also only interested in that relay parent.
@@ -85,7 +89,11 @@ fn prewarmed_state(
span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"),
}, },
}, },
peer_views: peers.iter().cloned().map(|peer| (peer, view!(relay_parent))).collect(), peer_data: peers
.iter()
.cloned()
.map(|peer| (peer, peer_data_v1(view![relay_parent])))
.collect(),
topologies, topologies,
view: our_view!(relay_parent), view: our_view!(relay_parent),
reputation: ReputationAggregator::new(|_| true), reputation: ReputationAggregator::new(|_| true),
@@ -211,7 +219,10 @@ fn receive_invalid_signature() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, invalid_msg.into_network_message()), NetworkBridgeEvent::PeerMessage(
peer_b,
invalid_msg.into_network_message(ValidationVersion::V1.into())
),
&mut rng, &mut rng,
)); ));
@@ -222,7 +233,10 @@ fn receive_invalid_signature() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, invalid_msg_2.into_network_message()), NetworkBridgeEvent::PeerMessage(
peer_b,
invalid_msg_2.into_network_message(ValidationVersion::V1.into())
),
&mut rng, &mut rng,
)); ));
// reputation change due to invalid signature // reputation change due to invalid signature
@@ -256,7 +270,7 @@ fn receive_invalid_validator_index() {
let (mut state, signing_context, keystore, validator) = let (mut state, signing_context, keystore, validator) =
state_with_view(our_view![hash_a, hash_b], hash_a, ReputationAggregator::new(|_| true)); state_with_view(our_view![hash_a, hash_b], hash_a, ReputationAggregator::new(|_| true));
state.peer_views.insert(peer_b, view![hash_a]); state.peer_data.insert(peer_b, peer_data_v1(view![hash_a]));
let payload = AvailabilityBitfield(bitvec![u8, bitvec::order::Lsb0; 1u8; 32]); let payload = AvailabilityBitfield(bitvec![u8, bitvec::order::Lsb0; 1u8; 32]);
let signed = Signed::<AvailabilityBitfield>::sign( let signed = Signed::<AvailabilityBitfield>::sign(
@@ -281,7 +295,10 @@ fn receive_invalid_validator_index() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, msg.into_network_message()), NetworkBridgeEvent::PeerMessage(
peer_b,
msg.into_network_message(ValidationVersion::V1.into())
),
&mut rng, &mut rng,
)); ));
@@ -344,7 +361,10 @@ fn receive_duplicate_messages() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peer_b,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -377,7 +397,10 @@ fn receive_duplicate_messages() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_a, msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peer_a,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -396,7 +419,10 @@ fn receive_duplicate_messages() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peer_b,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -463,7 +489,10 @@ fn delay_reputation_change() {
handle handle
.send(FromOrchestra::Communication { .send(FromOrchestra::Communication {
msg: BitfieldDistributionMessage::NetworkBridgeUpdate( msg: BitfieldDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(peer, msg.clone().into_network_message()), NetworkBridgeEvent::PeerMessage(
peer,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
), ),
}) })
.await; .await;
@@ -486,7 +515,10 @@ fn delay_reputation_change() {
handle handle
.send(FromOrchestra::Communication { .send(FromOrchestra::Communication {
msg: BitfieldDistributionMessage::NetworkBridgeUpdate( msg: BitfieldDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(peer, msg.clone().into_network_message()), NetworkBridgeEvent::PeerMessage(
peer,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
), ),
}) })
.await; .await;
@@ -546,8 +578,8 @@ fn do_not_relay_message_twice() {
.flatten() .flatten()
.expect("should be signed"); .expect("should be signed");
state.peer_views.insert(peer_b, view![hash]); state.peer_data.insert(peer_b, peer_data_v1(view![hash]));
state.peer_views.insert(peer_a, view![hash]); state.peer_data.insert(peer_a, peer_data_v1(view![hash]));
let msg = let msg =
BitfieldGossipMessage { relay_parent: hash, signed_availability: signed_bitfield.clone() }; BitfieldGossipMessage { relay_parent: hash, signed_availability: signed_bitfield.clone() };
@@ -564,7 +596,7 @@ fn do_not_relay_message_twice() {
&mut ctx, &mut ctx,
state.per_relay_parent.get_mut(&hash).unwrap(), state.per_relay_parent.get_mut(&hash).unwrap(),
&gossip_peers, &gossip_peers,
&mut state.peer_views, &mut state.peer_data,
validator.clone(), validator.clone(),
msg.clone(), msg.clone(),
RequiredRouting::GridXY, RequiredRouting::GridXY,
@@ -591,7 +623,7 @@ fn do_not_relay_message_twice() {
assert_eq!(2, peers.len()); assert_eq!(2, peers.len());
assert!(peers.contains(&peer_a)); assert!(peers.contains(&peer_a));
assert!(peers.contains(&peer_b)); assert!(peers.contains(&peer_b));
assert_eq!(send_msg, msg.clone().into_validation_protocol()); assert_eq!(send_msg, msg.clone().into_validation_protocol(ValidationVersion::V1.into()));
} }
); );
@@ -600,7 +632,7 @@ fn do_not_relay_message_twice() {
&mut ctx, &mut ctx,
state.per_relay_parent.get_mut(&hash).unwrap(), state.per_relay_parent.get_mut(&hash).unwrap(),
&gossip_peers, &gossip_peers,
&mut state.peer_views, &mut state.peer_data,
validator.clone(), validator.clone(),
msg.clone(), msg.clone(),
RequiredRouting::GridXY, RequiredRouting::GridXY,
@@ -687,14 +719,17 @@ fn changing_view() {
&mut rng, &mut rng,
)); ));
assert!(state.peer_views.contains_key(&peer_b)); assert!(state.peer_data.contains_key(&peer_b));
// recv a first message from the network // recv a first message from the network
launch!(handle_network_msg( launch!(handle_network_msg(
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peer_b,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -729,8 +764,11 @@ fn changing_view() {
&mut rng, &mut rng,
)); ));
assert!(state.peer_views.contains_key(&peer_b)); assert!(state.peer_data.contains_key(&peer_b));
assert_eq!(state.peer_views.get(&peer_b).expect("Must contain value for peer B"), &view![]); assert_eq!(
&state.peer_data.get(&peer_b).expect("Must contain value for peer B").view,
&view![]
);
// on rx of the same message, since we are not interested, // on rx of the same message, since we are not interested,
// should give penalty // should give penalty
@@ -738,7 +776,10 @@ fn changing_view() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peer_b,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -770,7 +811,10 @@ fn changing_view() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_a, msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peer_a,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -817,8 +861,8 @@ fn do_not_send_message_back_to_origin() {
.flatten() .flatten()
.expect("should be signed"); .expect("should be signed");
state.peer_views.insert(peer_b, view![hash]); state.peer_data.insert(peer_b, peer_data_v1(view![hash]));
state.peer_views.insert(peer_a, view![hash]); state.peer_data.insert(peer_a, peer_data_v1(view![hash]));
let msg = let msg =
BitfieldGossipMessage { relay_parent: hash, signed_availability: signed_bitfield.clone() }; BitfieldGossipMessage { relay_parent: hash, signed_availability: signed_bitfield.clone() };
@@ -833,7 +877,10 @@ fn do_not_send_message_back_to_origin() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peer_b, msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peer_b,
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -855,7 +902,7 @@ fn do_not_send_message_back_to_origin() {
) => { ) => {
assert_eq!(1, peers.len()); assert_eq!(1, peers.len());
assert!(peers.contains(&peer_a)); assert!(peers.contains(&peer_a));
assert_eq!(send_msg, msg.clone().into_validation_protocol()); assert_eq!(send_msg, msg.clone().into_validation_protocol(ValidationVersion::V1.into()));
} }
); );
@@ -932,7 +979,7 @@ fn topology_test() {
.expect("should be signed"); .expect("should be signed");
peers_x.iter().chain(peers_y.iter()).for_each(|peer| { peers_x.iter().chain(peers_y.iter()).for_each(|peer| {
state.peer_views.insert(*peer, view![hash]); state.peer_data.insert(*peer, peer_data_v1(view![hash]));
}); });
let msg = let msg =
@@ -948,7 +995,10 @@ fn topology_test() {
&mut ctx, &mut ctx,
&mut state, &mut state,
&Default::default(), &Default::default(),
NetworkBridgeEvent::PeerMessage(peers_x[0], msg.clone().into_network_message(),), NetworkBridgeEvent::PeerMessage(
peers_x[0],
msg.clone().into_network_message(ValidationVersion::V1.into()),
),
&mut rng, &mut rng,
)); ));
@@ -975,7 +1025,7 @@ fn topology_test() {
assert!(topology.peers_x.iter().filter(|peer| peers.contains(&peer)).count() == 4); assert!(topology.peers_x.iter().filter(|peer| peers.contains(&peer)).count() == 4);
// Must never include originator // Must never include originator
assert!(!peers.contains(&peers_x[0])); assert!(!peers.contains(&peers_x[0]));
assert_eq!(send_msg, msg.clone().into_validation_protocol()); assert_eq!(send_msg, msg.clone().into_validation_protocol(ValidationVersion::V1.into()));
} }
); );
@@ -1050,3 +1100,127 @@ fn need_message_works() {
// also not ok for Bob // also not ok for Bob
assert!(!pretend_send(&mut state, peer_b, &validator_set[1])); assert!(!pretend_send(&mut state, peer_b, &validator_set[1]));
} }
#[test]
fn network_protocol_versioning() {
let hash_a: Hash = [0; 32].into();
let hash_b: Hash = [1; 32].into();
let peer_a = PeerId::random();
let peer_b = PeerId::random();
let peer_c = PeerId::random();
let peers = [
(peer_a, ValidationVersion::VStaging),
(peer_b, ValidationVersion::V1),
(peer_c, ValidationVersion::VStaging),
];
// validator 0 key pair
let (mut state, signing_context, keystore, validator) =
state_with_view(our_view![hash_a, hash_b], hash_a, ReputationAggregator::new(|_| true));
let pool = sp_core::testing::TaskExecutor::new();
let (mut ctx, mut handle) = make_subsystem_context::<BitfieldDistributionMessage, _>(pool);
let mut rng = dummy_rng();
executor::block_on(async move {
// create a signed message by validator 0
let payload = AvailabilityBitfield(bitvec![u8, bitvec::order::Lsb0; 1u8; 32]);
let signed_bitfield = Signed::<AvailabilityBitfield>::sign(
&keystore,
payload,
&signing_context,
ValidatorIndex(0),
&validator,
)
.ok()
.flatten()
.expect("should be signed");
let msg = BitfieldGossipMessage {
relay_parent: hash_a,
signed_availability: signed_bitfield.clone(),
};
for (peer, protocol_version) in peers {
launch!(handle_network_msg(
&mut ctx,
&mut state,
&Default::default(),
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
protocol_version.into(),
None
),
&mut rng,
));
launch!(handle_network_msg(
&mut ctx,
&mut state,
&Default::default(),
NetworkBridgeEvent::PeerViewChange(peer, view![hash_a, hash_b]),
&mut rng,
));
assert!(state.peer_data.contains_key(&peer));
}
launch!(handle_network_msg(
&mut ctx,
&mut state,
&Default::default(),
NetworkBridgeEvent::PeerMessage(
peer_a,
msg.clone().into_network_message(ValidationVersion::VStaging.into()),
),
&mut rng,
));
// gossip to the overseer
assert_matches!(
handle.recv().await,
AllMessages::Provisioner(ProvisionerMessage::ProvisionableData(
_,
ProvisionableData::Bitfield(hash, signed)
)) => {
assert_eq!(hash, hash_a);
assert_eq!(signed, signed_bitfield)
}
);
// v1 gossip
assert_matches!(
handle.recv().await,
AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessage(peers, send_msg),
) => {
assert_eq!(peers, vec![peer_b]);
assert_eq!(send_msg, msg.clone().into_validation_protocol(ValidationVersion::V1.into()));
}
);
// vstaging gossip
assert_matches!(
handle.recv().await,
AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::SendValidationMessage(peers, send_msg),
) => {
assert_eq!(peers, vec![peer_c]);
assert_eq!(send_msg, msg.clone().into_validation_protocol(ValidationVersion::VStaging.into()));
}
);
// reputation change
assert_matches!(
handle.recv().await,
AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer, rep))
) => {
assert_eq!(peer, peer_a);
assert_eq!(rep, BENEFIT_VALID_MESSAGE_FIRST.into())
}
);
});
}
+8 -2
View File
@@ -55,6 +55,9 @@ pub(crate) fn send_message<M>(
) where ) where
M: Encode + Clone, M: Encode + Clone,
{ {
if peers.is_empty() {
return
}
let message = { let message = {
let encoded = message.encode(); let encoded = message.encode();
metrics.on_notification_sent(peer_set, version, encoded.len(), peers.len()); metrics.on_notification_sent(peer_set, version, encoded.len(), peers.len());
@@ -65,8 +68,11 @@ pub(crate) fn send_message<M>(
// list. The message payload can be quite large. If the underlying // list. The message payload can be quite large. If the underlying
// network used `Bytes` this would not be necessary. // network used `Bytes` this would not be necessary.
let last_peer = peers.pop(); let last_peer = peers.pop();
// optimization: generate the protocol name once.
let protocol_name = protocol_names.get_name(peer_set, version); // We always send messages on the "main" name even when a negotiated
// fallback is used. The libp2p implementation handles the fallback
// under the hood.
let protocol_name = protocol_names.get_main_name(peer_set);
peers.into_iter().for_each(|peer| { peers.into_iter().for_each(|peer| {
net.write_notification(peer, protocol_name.clone(), message.clone()); net.write_notification(peer, protocol_name.clone(), message.clone());
}); });
+193 -68
View File
@@ -33,7 +33,8 @@ use polkadot_node_network_protocol::{
CollationVersion, PeerSet, PeerSetProtocolNames, PerPeerSet, ProtocolVersion, CollationVersion, PeerSet, PeerSetProtocolNames, PerPeerSet, ProtocolVersion,
ValidationVersion, ValidationVersion,
}, },
v1 as protocol_v1, ObservedRole, OurView, PeerId, UnifiedReputationChange as Rep, View, v1 as protocol_v1, vstaging as protocol_vstaging, ObservedRole, OurView, PeerId,
UnifiedReputationChange as Rep, View,
}; };
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
@@ -246,15 +247,32 @@ where
) )
.await; .await;
send_message( match ValidationVersion::try_from(version)
&mut network_service, .expect("try_get_protocol has already checked version is known; qed")
vec![peer], {
PeerSet::Validation, ValidationVersion::V1 => send_message(
version, &mut network_service,
&peerset_protocol_names, vec![peer],
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(local_view), PeerSet::Validation,
&metrics, version,
); &peerset_protocol_names,
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(
local_view,
),
&metrics,
),
ValidationVersion::VStaging => send_message(
&mut network_service,
vec![peer],
PeerSet::Validation,
version,
&peerset_protocol_names,
WireMessage::<protocol_vstaging::ValidationProtocol>::ViewUpdate(
local_view,
),
&metrics,
),
}
}, },
PeerSet::Collation => { PeerSet::Collation => {
dispatch_collation_events_to_all( dispatch_collation_events_to_all(
@@ -271,15 +289,32 @@ where
) )
.await; .await;
send_message( match CollationVersion::try_from(version)
&mut network_service, .expect("try_get_protocol has already checked version is known; qed")
vec![peer], {
PeerSet::Collation, CollationVersion::V1 => send_message(
version, &mut network_service,
&peerset_protocol_names, vec![peer],
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(local_view), PeerSet::Collation,
&metrics, version,
); &peerset_protocol_names,
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(
local_view,
),
&metrics,
),
CollationVersion::VStaging => send_message(
&mut network_service,
vec![peer],
PeerSet::Collation,
version,
&peerset_protocol_names,
WireMessage::<protocol_vstaging::CollationProtocol>::ViewUpdate(
local_view,
),
&metrics,
),
}
}, },
} }
}, },
@@ -417,30 +452,39 @@ where
); );
if !v_messages.is_empty() { if !v_messages.is_empty() {
let (events, reports) = let (events, reports) = if expected_versions[PeerSet::Validation] ==
if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V1.into())
Some(ValidationVersion::V1.into()) {
{ handle_peer_messages::<protocol_v1::ValidationProtocol, _>(
handle_v1_peer_messages::<protocol_v1::ValidationProtocol, _>( remote,
remote, PeerSet::Validation,
PeerSet::Validation, &mut shared.0.lock().validation_peers,
&mut shared.0.lock().validation_peers, v_messages,
v_messages, &metrics,
&metrics, )
) } else if expected_versions[PeerSet::Validation] ==
} else { Some(ValidationVersion::VStaging.into())
gum::warn!( {
target: LOG_TARGET, handle_peer_messages::<protocol_vstaging::ValidationProtocol, _>(
version = ?expected_versions[PeerSet::Validation], remote,
"Major logic bug. Peer somehow has unsupported validation protocol version." PeerSet::Validation,
); &mut shared.0.lock().validation_peers,
v_messages,
&metrics,
)
} else {
gum::warn!(
target: LOG_TARGET,
version = ?expected_versions[PeerSet::Validation],
"Major logic bug. Peer somehow has unsupported validation protocol version."
);
never!("Only version 1 is supported; peer set connection checked above; qed"); never!("Only versions 1 and 2 are supported; peer set connection checked above; qed");
// If a peer somehow triggers this, we'll disconnect them // If a peer somehow triggers this, we'll disconnect them
// eventually. // eventually.
(Vec::new(), vec![UNCONNECTED_PEERSET_COST]) (Vec::new(), vec![UNCONNECTED_PEERSET_COST])
}; };
for report in reports { for report in reports {
network_service.report_peer(remote, report.into()); network_service.report_peer(remote, report.into());
@@ -450,30 +494,39 @@ where
} }
if !c_messages.is_empty() { if !c_messages.is_empty() {
let (events, reports) = let (events, reports) = if expected_versions[PeerSet::Collation] ==
if expected_versions[PeerSet::Collation] == Some(CollationVersion::V1.into())
Some(CollationVersion::V1.into()) {
{ handle_peer_messages::<protocol_v1::CollationProtocol, _>(
handle_v1_peer_messages::<protocol_v1::CollationProtocol, _>( remote,
remote, PeerSet::Collation,
PeerSet::Collation, &mut shared.0.lock().collation_peers,
&mut shared.0.lock().collation_peers, c_messages,
c_messages, &metrics,
&metrics, )
) } else if expected_versions[PeerSet::Collation] ==
} else { Some(CollationVersion::VStaging.into())
gum::warn!( {
target: LOG_TARGET, handle_peer_messages::<protocol_vstaging::CollationProtocol, _>(
version = ?expected_versions[PeerSet::Collation], remote,
"Major logic bug. Peer somehow has unsupported collation protocol version." PeerSet::Collation,
); &mut shared.0.lock().collation_peers,
c_messages,
&metrics,
)
} else {
gum::warn!(
target: LOG_TARGET,
version = ?expected_versions[PeerSet::Collation],
"Major logic bug. Peer somehow has unsupported collation protocol version."
);
never!("Only version 1 is supported; peer set connection checked above; qed"); never!("Only versions 1 and 2 are supported; peer set connection checked above; qed");
// If a peer somehow triggers this, we'll disconnect them // If a peer somehow triggers this, we'll disconnect them
// eventually. // eventually.
(Vec::new(), vec![UNCONNECTED_PEERSET_COST]) (Vec::new(), vec![UNCONNECTED_PEERSET_COST])
}; };
for report in reports { for report in reports {
network_service.report_peer(remote, report.into()); network_service.report_peer(remote, report.into());
@@ -738,14 +791,34 @@ fn update_our_view<Net, Context>(
} }
( (
shared.validation_peers.keys().cloned().collect::<Vec<_>>(), shared
shared.collation_peers.keys().cloned().collect::<Vec<_>>(), .validation_peers
.iter()
.map(|(peer_id, data)| (*peer_id, data.version))
.collect::<Vec<_>>(),
shared
.collation_peers
.iter()
.map(|(peer_id, data)| (*peer_id, data.version))
.collect::<Vec<_>>(),
) )
}; };
let filter_by_version = |peers: &[(PeerId, ProtocolVersion)], version| {
peers.iter().filter(|(_, v)| v == &version).map(|(p, _)| *p).collect::<Vec<_>>()
};
let v1_validation_peers = filter_by_version(&validation_peers, ValidationVersion::V1.into());
let v1_collation_peers = filter_by_version(&collation_peers, CollationVersion::V1.into());
let vstaging_validation_peers =
filter_by_version(&validation_peers, ValidationVersion::VStaging.into());
let vstaging_collation_peers =
filter_by_version(&collation_peers, ValidationVersion::VStaging.into());
send_validation_message_v1( send_validation_message_v1(
net, net,
validation_peers, v1_validation_peers,
peerset_protocol_names, peerset_protocol_names,
WireMessage::ViewUpdate(new_view.clone()), WireMessage::ViewUpdate(new_view.clone()),
metrics, metrics,
@@ -753,7 +826,23 @@ fn update_our_view<Net, Context>(
send_collation_message_v1( send_collation_message_v1(
net, net,
collation_peers, v1_collation_peers,
peerset_protocol_names,
WireMessage::ViewUpdate(new_view.clone()),
metrics,
);
send_validation_message_vstaging(
net,
vstaging_validation_peers,
peerset_protocol_names,
WireMessage::ViewUpdate(new_view.clone()),
metrics,
);
send_collation_message_vstaging(
net,
vstaging_collation_peers,
peerset_protocol_names, peerset_protocol_names,
WireMessage::ViewUpdate(new_view), WireMessage::ViewUpdate(new_view),
metrics, metrics,
@@ -777,7 +866,7 @@ fn update_our_view<Net, Context>(
// Handle messages on a specific v1 peer-set. The peer is expected to be connected on that // Handle messages on a specific v1 peer-set. The peer is expected to be connected on that
// peer-set. // peer-set.
fn handle_v1_peer_messages<RawMessage: Decode, OutMessage: From<RawMessage>>( fn handle_peer_messages<RawMessage: Decode, OutMessage: From<RawMessage>>(
peer: PeerId, peer: PeerId,
peer_set: PeerSet, peer_set: PeerSet,
peers: &mut HashMap<PeerId, PeerData>, peers: &mut HashMap<PeerId, PeerData>,
@@ -864,6 +953,42 @@ fn send_collation_message_v1(
); );
} }
fn send_validation_message_vstaging(
net: &mut impl Network,
peers: Vec<PeerId>,
protocol_names: &PeerSetProtocolNames,
message: WireMessage<protocol_vstaging::ValidationProtocol>,
metrics: &Metrics,
) {
send_message(
net,
peers,
PeerSet::Validation,
ValidationVersion::VStaging.into(),
protocol_names,
message,
metrics,
);
}
fn send_collation_message_vstaging(
net: &mut impl Network,
peers: Vec<PeerId>,
protocol_names: &PeerSetProtocolNames,
message: WireMessage<protocol_vstaging::CollationProtocol>,
metrics: &Metrics,
) {
send_message(
net,
peers,
PeerSet::Collation,
CollationVersion::VStaging.into(),
protocol_names,
message,
metrics,
);
}
async fn dispatch_validation_event_to_all( async fn dispatch_validation_event_to_all(
event: NetworkBridgeEvent<net_protocol::VersionedValidationProtocol>, event: NetworkBridgeEvent<net_protocol::VersionedValidationProtocol>,
ctx: &mut impl overseer::NetworkBridgeRxSenderTrait, ctx: &mut impl overseer::NetworkBridgeRxSenderTrait,
+208 -25
View File
@@ -25,6 +25,7 @@ use parking_lot::Mutex;
use std::{ use std::{
collections::HashSet, collections::HashSet,
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
task::Poll,
}; };
use sc_network::{Event as NetworkEvent, IfDisconnected, ProtocolName, ReputationChange}; use sc_network::{Event as NetworkEvent, IfDisconnected, ProtocolName, ReputationChange};
@@ -46,7 +47,7 @@ use polkadot_node_subsystem_test_helpers::{
SingleItemSink, SingleItemStream, TestSubsystemContextHandle, SingleItemSink, SingleItemStream, TestSubsystemContextHandle,
}; };
use polkadot_node_subsystem_util::metered; use polkadot_node_subsystem_util::metered;
use polkadot_primitives::{AuthorityDiscoveryId, Hash}; use polkadot_primitives::{AuthorityDiscoveryId, CandidateHash, Hash};
use sc_network::Multiaddr; use sc_network::Multiaddr;
use sp_keyring::Sr25519Keyring; use sp_keyring::Sr25519Keyring;
@@ -142,8 +143,7 @@ impl Network for TestNetwork {
} }
fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) {
let (peer_set, version) = self.protocol_names.try_get_protocol(&protocol).unwrap(); let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap();
assert_eq!(version, peer_set.get_main_version());
self.action_tx self.action_tx
.lock() .lock()
@@ -152,8 +152,7 @@ impl Network for TestNetwork {
} }
fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec<u8>) { fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec<u8>) {
let (peer_set, version) = self.protocol_names.try_get_protocol(&protocol).unwrap(); let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap();
assert_eq!(version, peer_set.get_main_version());
self.action_tx self.action_tx
.lock() .lock()
@@ -195,10 +194,17 @@ impl TestNetworkHandle {
v v
} }
async fn connect_peer(&mut self, peer: PeerId, peer_set: PeerSet, role: ObservedRole) { async fn connect_peer(
&mut self,
peer: PeerId,
protocol_version: ValidationVersion,
peer_set: PeerSet,
role: ObservedRole,
) {
let protocol_version = ProtocolVersion::from(protocol_version);
self.send_network_event(NetworkEvent::NotificationStreamOpened { self.send_network_event(NetworkEvent::NotificationStreamOpened {
remote: peer, remote: peer,
protocol: self.protocol_names.get_main_name(peer_set), protocol: self.protocol_names.get_name(peer_set, protocol_version),
negotiated_fallback: None, negotiated_fallback: None,
role: role.into(), role: role.into(),
received_handshake: vec![], received_handshake: vec![],
@@ -432,8 +438,12 @@ fn send_our_view_upon_connection() {
handle.await_mode_switch().await; handle.await_mode_switch().await;
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await; network_handle
network_handle.connect_peer(peer, PeerSet::Collation, ObservedRole::Full).await; .connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await; await_peer_connections(&shared, 1, 1).await;
@@ -478,10 +488,10 @@ fn sends_view_updates_to_peers() {
handle.await_mode_switch().await; handle.await_mode_switch().await;
network_handle network_handle
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await; .await;
network_handle network_handle
.connect_peer(peer_b, PeerSet::Collation, ObservedRole::Full) .connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await; .await;
await_peer_connections(&shared, 1, 1).await; await_peer_connections(&shared, 1, 1).await;
@@ -541,10 +551,10 @@ fn do_not_send_view_update_until_synced() {
assert_ne!(peer_a, peer_b); assert_ne!(peer_a, peer_b);
network_handle network_handle
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await; .await;
network_handle network_handle
.connect_peer(peer_b, PeerSet::Collation, ObservedRole::Full) .connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await; .await;
await_peer_connections(&shared, 1, 1).await; await_peer_connections(&shared, 1, 1).await;
@@ -636,10 +646,10 @@ fn do_not_send_view_update_when_only_finalized_block_changed() {
let peer_b = PeerId::random(); let peer_b = PeerId::random();
network_handle network_handle
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await; .await;
network_handle network_handle
.connect_peer(peer_b, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer_b, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await; .await;
await_peer_connections(&shared, 2, 0).await; await_peer_connections(&shared, 2, 0).await;
@@ -696,7 +706,9 @@ fn peer_view_updates_sent_via_overseer() {
let peer = PeerId::random(); let peer = PeerId::random();
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await; network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 0).await; await_peer_connections(&shared, 1, 0).await;
@@ -746,7 +758,9 @@ fn peer_messages_sent_via_overseer() {
let peer = PeerId::random(); let peer = PeerId::random();
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await; network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 0).await; await_peer_connections(&shared, 1, 0).await;
@@ -819,8 +833,12 @@ fn peer_disconnect_from_just_one_peerset() {
let peer = PeerId::random(); let peer = PeerId::random();
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await; network_handle
network_handle.connect_peer(peer, PeerSet::Collation, ObservedRole::Full).await; .connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await; await_peer_connections(&shared, 1, 1).await;
@@ -907,10 +925,10 @@ fn relays_collation_protocol_messages() {
let peer_b = PeerId::random(); let peer_b = PeerId::random();
network_handle network_handle
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await; .await;
network_handle network_handle
.connect_peer(peer_b, PeerSet::Collation, ObservedRole::Full) .connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await; .await;
await_peer_connections(&shared, 1, 1).await; await_peer_connections(&shared, 1, 1).await;
@@ -1011,8 +1029,12 @@ fn different_views_on_different_peer_sets() {
let peer = PeerId::random(); let peer = PeerId::random();
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await; network_handle
network_handle.connect_peer(peer, PeerSet::Collation, ObservedRole::Full).await; .connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await;
network_handle
.connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.await;
await_peer_connections(&shared, 1, 1).await; await_peer_connections(&shared, 1, 1).await;
@@ -1097,7 +1119,7 @@ fn sent_views_include_finalized_number_update() {
let peer_a = PeerId::random(); let peer_a = PeerId::random();
network_handle network_handle
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await; .await;
await_peer_connections(&shared, 1, 0).await; await_peer_connections(&shared, 1, 0).await;
@@ -1140,7 +1162,7 @@ fn view_finalized_number_can_not_go_down() {
let peer_a = PeerId::random(); let peer_a = PeerId::random();
network_handle network_handle
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.await; .await;
await_peer_connections(&shared, 1, 0).await; await_peer_connections(&shared, 1, 0).await;
@@ -1225,3 +1247,164 @@ fn our_view_updates_decreasing_order_and_limited_to_max() {
virtual_overseer virtual_overseer
}); });
} }
#[test]
fn network_protocol_versioning_view_update() {
let (oracle, handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, .. } = test_harness;
let peer_ids: Vec<_> = (0..4).map(|_| PeerId::random()).collect();
let peers = [
(peer_ids[0], PeerSet::Validation, ValidationVersion::VStaging),
(peer_ids[1], PeerSet::Collation, ValidationVersion::V1),
(peer_ids[2], PeerSet::Validation, ValidationVersion::V1),
(peer_ids[3], PeerSet::Collation, ValidationVersion::VStaging),
];
let head = Hash::repeat_byte(1);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(ActivatedLeaf {
hash: head,
number: 1,
status: LeafStatus::Fresh,
span: Arc::new(jaeger::Span::Disabled),
}),
)))
.await;
handle.await_mode_switch().await;
for &(peer_id, peer_set, version) in &peers {
network_handle
.connect_peer(peer_id, version, peer_set, ObservedRole::Full)
.await;
}
let view = view![head];
let actions = network_handle.next_network_actions(4).await;
for &(peer_id, peer_set, version) in &peers {
let wire_msg = match version {
ValidationVersion::V1 =>
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(view.clone())
.encode(),
ValidationVersion::VStaging =>
WireMessage::<protocol_vstaging::ValidationProtocol>::ViewUpdate(view.clone())
.encode(),
};
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(peer_id, peer_set, wire_msg),
);
}
virtual_overseer
});
}
#[test]
fn network_protocol_versioning_subsystem_msg() {
let (oracle, _handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer, .. } = test_harness;
let peer = PeerId::random();
network_handle
.connect_peer(
peer,
ValidationVersion::VStaging,
PeerSet::Validation,
ObservedRole::Full,
)
.await;
// bridge will inform about all connected peers.
{
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::VStaging.into(),
None,
),
&mut virtual_overseer,
)
.await;
assert_sends_validation_event_to_all(
NetworkBridgeEvent::PeerViewChange(peer, View::default()),
&mut virtual_overseer,
)
.await;
}
let approval_distribution_message =
protocol_vstaging::ApprovalDistributionMessage::Approvals(Vec::new());
let msg = protocol_vstaging::ValidationProtocol::ApprovalDistribution(
approval_distribution_message.clone(),
);
network_handle
.peer_message(
peer,
PeerSet::Validation,
WireMessage::ProtocolMessage(msg.clone()).encode(),
)
.await;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ApprovalDistribution(
ApprovalDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::VStaging(m))
)
) => {
assert_eq!(p, peer);
assert_eq!(m, approval_distribution_message);
}
);
let metadata = protocol_v1::StatementMetadata {
relay_parent: Hash::zero(),
candidate_hash: CandidateHash::default(),
signed_by: ValidatorIndex(0),
signature: sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]),
};
let statement_distribution_message =
protocol_vstaging::StatementDistributionMessage::V1Compatibility(
protocol_v1::StatementDistributionMessage::LargeStatement(metadata),
);
let msg = protocol_vstaging::ValidationProtocol::StatementDistribution(
statement_distribution_message.clone(),
);
network_handle
.peer_message(
peer,
PeerSet::Validation,
WireMessage::ProtocolMessage(msg.clone()).encode(),
)
.await;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::StatementDistribution(
StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::VStaging(m))
)
) => {
assert_eq!(p, peer);
assert_eq!(m, statement_distribution_message);
}
);
// No more messages.
assert_matches!(futures::poll!(virtual_overseer.recv().boxed()), Poll::Pending);
virtual_overseer
});
}
+65 -1
View File
@@ -20,7 +20,7 @@ use super::*;
use polkadot_node_network_protocol::{ use polkadot_node_network_protocol::{
peer_set::{CollationVersion, PeerSet, PeerSetProtocolNames, ValidationVersion}, peer_set::{CollationVersion, PeerSet, PeerSetProtocolNames, ValidationVersion},
request_response::ReqProtocolNames, request_response::ReqProtocolNames,
v1 as protocol_v1, PeerId, Versioned, v1 as protocol_v1, vstaging as protocol_vstaging, PeerId, Versioned,
}; };
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
@@ -197,6 +197,13 @@ where
WireMessage::ProtocolMessage(msg), WireMessage::ProtocolMessage(msg),
&metrics, &metrics,
), ),
Versioned::VStaging(msg) => send_validation_message_vstaging(
&mut network_service,
peers,
peerset_protocol_names,
WireMessage::ProtocolMessage(msg),
&metrics,
),
} }
}, },
NetworkBridgeTxMessage::SendValidationMessages(msgs) => { NetworkBridgeTxMessage::SendValidationMessages(msgs) => {
@@ -215,6 +222,13 @@ where
WireMessage::ProtocolMessage(msg), WireMessage::ProtocolMessage(msg),
&metrics, &metrics,
), ),
Versioned::VStaging(msg) => send_validation_message_vstaging(
&mut network_service,
peers,
peerset_protocol_names,
WireMessage::ProtocolMessage(msg),
&metrics,
),
} }
} }
}, },
@@ -233,6 +247,13 @@ where
WireMessage::ProtocolMessage(msg), WireMessage::ProtocolMessage(msg),
&metrics, &metrics,
), ),
Versioned::VStaging(msg) => send_collation_message_vstaging(
&mut network_service,
peers,
peerset_protocol_names,
WireMessage::ProtocolMessage(msg),
&metrics,
),
} }
}, },
NetworkBridgeTxMessage::SendCollationMessages(msgs) => { NetworkBridgeTxMessage::SendCollationMessages(msgs) => {
@@ -251,6 +272,13 @@ where
WireMessage::ProtocolMessage(msg), WireMessage::ProtocolMessage(msg),
&metrics, &metrics,
), ),
Versioned::VStaging(msg) => send_collation_message_vstaging(
&mut network_service,
peers,
peerset_protocol_names,
WireMessage::ProtocolMessage(msg),
&metrics,
),
} }
} }
}, },
@@ -381,3 +409,39 @@ fn send_collation_message_v1(
metrics, metrics,
); );
} }
fn send_validation_message_vstaging(
net: &mut impl Network,
peers: Vec<PeerId>,
protocol_names: &PeerSetProtocolNames,
message: WireMessage<protocol_vstaging::ValidationProtocol>,
metrics: &Metrics,
) {
send_message(
net,
peers,
PeerSet::Validation,
ValidationVersion::VStaging.into(),
protocol_names,
message,
metrics,
);
}
fn send_collation_message_vstaging(
net: &mut impl Network,
peers: Vec<PeerId>,
protocol_names: &PeerSetProtocolNames,
message: WireMessage<protocol_vstaging::CollationProtocol>,
metrics: &Metrics,
) {
send_message(
net,
peers,
PeerSet::Collation,
CollationVersion::VStaging.into(),
protocol_names,
message,
metrics,
);
}
+117 -8
View File
@@ -130,8 +130,7 @@ impl Network for TestNetwork {
} }
fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) {
let (peer_set, version) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap(); let (peer_set, _) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap();
assert_eq!(version, peer_set.get_main_version());
self.action_tx self.action_tx
.lock() .lock()
@@ -140,8 +139,7 @@ impl Network for TestNetwork {
} }
fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec<u8>) { fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec<u8>) {
let (peer_set, version) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap(); let (peer_set, _) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap();
assert_eq!(version, peer_set.get_main_version());
self.action_tx self.action_tx
.lock() .lock()
@@ -173,10 +171,17 @@ impl TestNetworkHandle {
self.action_rx.next().await.expect("subsystem concluded early") self.action_rx.next().await.expect("subsystem concluded early")
} }
async fn connect_peer(&mut self, peer: PeerId, peer_set: PeerSet, role: ObservedRole) { async fn connect_peer(
&mut self,
peer: PeerId,
protocol_version: ValidationVersion,
peer_set: PeerSet,
role: ObservedRole,
) {
let protocol_version = ProtocolVersion::from(protocol_version);
self.send_network_event(NetworkEvent::NotificationStreamOpened { self.send_network_event(NetworkEvent::NotificationStreamOpened {
remote: peer, remote: peer,
protocol: self.peerset_protocol_names.get_main_name(peer_set), protocol: self.peerset_protocol_names.get_name(peer_set, protocol_version),
negotiated_fallback: None, negotiated_fallback: None,
role: role.into(), role: role.into(),
received_handshake: vec![], received_handshake: vec![],
@@ -242,7 +247,7 @@ fn send_messages_to_peers() {
let peer = PeerId::random(); let peer = PeerId::random();
network_handle network_handle
.connect_peer(peer, PeerSet::Validation, ObservedRole::Full) .connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
.timeout(TIMEOUT) .timeout(TIMEOUT)
.await .await
.expect("Timeout does not occur"); .expect("Timeout does not occur");
@@ -251,7 +256,7 @@ fn send_messages_to_peers() {
// so the single item sink has to be free explicitly // so the single item sink has to be free explicitly
network_handle network_handle
.connect_peer(peer, PeerSet::Collation, ObservedRole::Full) .connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
.timeout(TIMEOUT) .timeout(TIMEOUT)
.await .await
.expect("Timeout does not occur"); .expect("Timeout does not occur");
@@ -328,3 +333,107 @@ fn send_messages_to_peers() {
virtual_overseer virtual_overseer
}); });
} }
#[test]
fn network_protocol_versioning_send() {
test_harness(|test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer } = test_harness;
let peer_ids: Vec<_> = (0..4).map(|_| PeerId::random()).collect();
let peers = [
(peer_ids[0], PeerSet::Validation, ValidationVersion::VStaging),
(peer_ids[1], PeerSet::Collation, ValidationVersion::V1),
(peer_ids[2], PeerSet::Validation, ValidationVersion::V1),
(peer_ids[3], PeerSet::Collation, ValidationVersion::VStaging),
];
for &(peer_id, peer_set, version) in &peers {
network_handle
.connect_peer(peer_id, version, peer_set, ObservedRole::Full)
.timeout(TIMEOUT)
.await
.expect("Timeout does not occur");
}
// send a validation protocol message.
{
let approval_distribution_message =
protocol_vstaging::ApprovalDistributionMessage::Approvals(Vec::new());
let msg = protocol_vstaging::ValidationProtocol::ApprovalDistribution(
approval_distribution_message.clone(),
);
// Note that bridge doesn't ensure neither peer's protocol version
// or peer set match the message.
let receivers = vec![peer_ids[0], peer_ids[3]];
virtual_overseer
.send(FromOrchestra::Communication {
msg: NetworkBridgeTxMessage::SendValidationMessage(
receivers.clone(),
Versioned::VStaging(msg.clone()),
),
})
.timeout(TIMEOUT)
.await
.expect("Timeout does not occur");
for peer in &receivers {
assert_eq!(
network_handle
.next_network_action()
.timeout(TIMEOUT)
.await
.expect("Timeout does not occur"),
NetworkAction::WriteNotification(
*peer,
PeerSet::Validation,
WireMessage::ProtocolMessage(msg.clone()).encode(),
)
);
}
}
// send a collation protocol message.
{
let collator_protocol_message = protocol_vstaging::CollatorProtocolMessage::Declare(
Sr25519Keyring::Alice.public().into(),
0_u32.into(),
dummy_collator_signature(),
);
let msg = protocol_vstaging::CollationProtocol::CollatorProtocol(
collator_protocol_message.clone(),
);
let receivers = vec![peer_ids[1], peer_ids[2]];
virtual_overseer
.send(FromOrchestra::Communication {
msg: NetworkBridgeTxMessage::SendCollationMessages(vec![(
receivers.clone(),
Versioned::VStaging(msg.clone()),
)]),
})
.await;
for peer in &receivers {
assert_eq!(
network_handle
.next_network_action()
.timeout(TIMEOUT)
.await
.expect("Timeout does not occur"),
NetworkAction::WriteNotification(
*peer,
PeerSet::Collation,
WireMessage::ProtocolMessage(msg.clone()).encode(),
)
);
}
}
virtual_overseer
});
}
@@ -6,7 +6,6 @@ edition.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
always-assert = "0.1.2"
bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] }
futures = "0.3.21" futures = "0.3.21"
futures-timer = "3" futures-timer = "3"
@@ -23,6 +22,7 @@ polkadot-node-subsystem-util = { path = "../../subsystem-util" }
polkadot-node-subsystem = {path = "../../subsystem" } polkadot-node-subsystem = {path = "../../subsystem" }
fatality = "0.0.6" fatality = "0.0.6"
thiserror = "1.0.31" thiserror = "1.0.31"
tokio-util = "0.7.1"
[dev-dependencies] [dev-dependencies]
log = "0.4.17" log = "0.4.17"
@@ -31,6 +31,7 @@ assert_matches = "1.4.0"
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", features = ["std"] } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", features = ["std"] }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
parity-scale-codec = { version = "3.6.1", features = ["std"] } parity-scale-codec = { version = "3.6.1", features = ["std"] }
@@ -0,0 +1,162 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Primitives for tracking collations-related data.
use std::collections::{HashSet, VecDeque};
use futures::{future::BoxFuture, stream::FuturesUnordered};
use polkadot_node_network_protocol::{
request_response::{
incoming::OutgoingResponse, v1 as protocol_v1, vstaging as protocol_vstaging,
IncomingRequest,
},
PeerId,
};
use polkadot_node_primitives::PoV;
use polkadot_primitives::{CandidateHash, CandidateReceipt, Hash, Id as ParaId};
/// The status of a collation as seen from the collator.
pub enum CollationStatus {
/// The collation was created, but we did not advertise it to any validator.
Created,
/// The collation was advertised to at least one validator.
Advertised,
/// The collation was requested by at least one validator.
Requested,
}
impl CollationStatus {
/// Advance to the [`Self::Advertised`] status.
///
/// This ensures that `self` isn't already [`Self::Requested`].
pub fn advance_to_advertised(&mut self) {
if !matches!(self, Self::Requested) {
*self = Self::Advertised;
}
}
/// Advance to the [`Self::Requested`] status.
pub fn advance_to_requested(&mut self) {
*self = Self::Requested;
}
}
/// A collation built by the collator.
pub struct Collation {
/// Candidate receipt.
pub receipt: CandidateReceipt,
/// Parent head-data hash.
pub parent_head_data_hash: Hash,
/// Proof to verify the state transition of the parachain.
pub pov: PoV,
/// Collation status.
pub status: CollationStatus,
}
/// Stores the state for waiting collation fetches per relay parent.
#[derive(Default)]
pub struct WaitingCollationFetches {
/// A flag indicating that we have an ongoing request.
/// This limits the number of collations being sent at any moment
/// of time to 1 for each relay parent.
///
/// If set to `true`, any new request will be queued.
pub collation_fetch_active: bool,
/// The collation fetches waiting to be fulfilled.
pub req_queue: VecDeque<VersionedCollationRequest>,
/// All peers that are waiting or actively uploading.
///
/// We will not accept multiple requests from the same peer, otherwise our DoS protection of
/// moving on to the next peer after `MAX_UNSHARED_UPLOAD_TIME` would be pointless.
pub waiting_peers: HashSet<(PeerId, CandidateHash)>,
}
/// Backwards-compatible wrapper for incoming collations requests.
pub enum VersionedCollationRequest {
V1(IncomingRequest<protocol_v1::CollationFetchingRequest>),
VStaging(IncomingRequest<protocol_vstaging::CollationFetchingRequest>),
}
impl From<IncomingRequest<protocol_v1::CollationFetchingRequest>> for VersionedCollationRequest {
fn from(req: IncomingRequest<protocol_v1::CollationFetchingRequest>) -> Self {
Self::V1(req)
}
}
impl From<IncomingRequest<protocol_vstaging::CollationFetchingRequest>>
for VersionedCollationRequest
{
fn from(req: IncomingRequest<protocol_vstaging::CollationFetchingRequest>) -> Self {
Self::VStaging(req)
}
}
impl VersionedCollationRequest {
/// Returns parachain id from the request payload.
pub fn para_id(&self) -> ParaId {
match self {
VersionedCollationRequest::V1(req) => req.payload.para_id,
VersionedCollationRequest::VStaging(req) => req.payload.para_id,
}
}
/// Returns relay parent from the request payload.
pub fn relay_parent(&self) -> Hash {
match self {
VersionedCollationRequest::V1(req) => req.payload.relay_parent,
VersionedCollationRequest::VStaging(req) => req.payload.relay_parent,
}
}
/// Returns id of the peer the request was received from.
pub fn peer_id(&self) -> PeerId {
match self {
VersionedCollationRequest::V1(req) => req.peer,
VersionedCollationRequest::VStaging(req) => req.peer,
}
}
/// Sends the response back to requester.
pub fn send_outgoing_response(
self,
response: OutgoingResponse<protocol_v1::CollationFetchingResponse>,
) -> Result<(), ()> {
match self {
VersionedCollationRequest::V1(req) => req.send_outgoing_response(response),
VersionedCollationRequest::VStaging(req) => req.send_outgoing_response(response),
}
}
}
/// Result of the finished background send-collation task.
///
/// Note that if the timeout was hit the request doesn't get
/// aborted, it only indicates that we should start processing
/// the next one from the queue.
pub struct CollationSendResult {
/// Candidate's relay parent.
pub relay_parent: Hash,
/// Candidate hash.
pub candidate_hash: CandidateHash,
/// Peer id.
pub peer_id: PeerId,
/// Whether the max unshared timeout was hit.
pub timed_out: bool,
}
pub type ActiveCollationFetches = FuturesUnordered<BoxFuture<'static, CollationSendResult>>;
@@ -20,7 +20,7 @@ use polkadot_node_subsystem_util::metrics::{self, prometheus};
pub struct Metrics(Option<MetricsInner>); pub struct Metrics(Option<MetricsInner>);
impl Metrics { impl Metrics {
pub fn on_advertisment_made(&self) { pub fn on_advertisement_made(&self) {
if let Some(metrics) = &self.0 { if let Some(metrics) = &self.0 {
metrics.advertisements_made.inc(); metrics.advertisements_made.inc();
} }
File diff suppressed because it is too large Load Diff
@@ -37,6 +37,7 @@ use polkadot_node_network_protocol::{
}; };
use polkadot_node_primitives::BlockData; use polkadot_node_primitives::BlockData;
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
errors::RuntimeApiError,
jaeger, jaeger,
messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest}, messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest},
ActivatedLeaf, ActiveLeavesUpdate, LeafStatus, ActivatedLeaf, ActiveLeavesUpdate, LeafStatus,
@@ -49,8 +50,13 @@ use polkadot_primitives::{
}; };
use polkadot_primitives_test_helpers::TestCandidateBuilder; use polkadot_primitives_test_helpers::TestCandidateBuilder;
mod prospective_parachains;
const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10); const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10);
const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError =
RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" };
#[derive(Clone)] #[derive(Clone)]
struct TestState { struct TestState {
para_id: ParaId, para_id: ParaId,
@@ -186,6 +192,17 @@ impl TestState {
)), )),
) )
.await; .await;
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
relay_parent,
RuntimeApiRequest::StagingAsyncBackingParams(tx)
)) => {
assert_eq!(relay_parent, self.relay_parent);
tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap();
}
);
} }
} }
@@ -193,7 +210,8 @@ type VirtualOverseer = test_helpers::TestSubsystemContextHandle<CollatorProtocol
struct TestHarness { struct TestHarness {
virtual_overseer: VirtualOverseer, virtual_overseer: VirtualOverseer,
req_cfg: sc_network::config::RequestResponseConfig, req_v1_cfg: sc_network::config::RequestResponseConfig,
req_vstaging_cfg: sc_network::config::RequestResponseConfig,
} }
fn test_harness<T: Future<Output = TestHarness>>( fn test_harness<T: Future<Output = TestHarness>>(
@@ -215,7 +233,9 @@ fn test_harness<T: Future<Output = TestHarness>>(
let genesis_hash = Hash::repeat_byte(0xff); let genesis_hash = Hash::repeat_byte(0xff);
let req_protocol_names = ReqProtocolNames::new(&genesis_hash, None); let req_protocol_names = ReqProtocolNames::new(&genesis_hash, None);
let (collation_req_receiver, req_cfg) = let (collation_req_receiver, req_v1_cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names);
let (collation_req_vstaging_receiver, req_vstaging_cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names); IncomingRequest::get_config_receiver(&req_protocol_names);
let subsystem = async { let subsystem = async {
run_inner( run_inner(
@@ -223,6 +243,7 @@ fn test_harness<T: Future<Output = TestHarness>>(
local_peer_id, local_peer_id,
collator_pair, collator_pair,
collation_req_receiver, collation_req_receiver,
collation_req_vstaging_receiver,
Default::default(), Default::default(),
reputation, reputation,
REPUTATION_CHANGE_TEST_INTERVAL, REPUTATION_CHANGE_TEST_INTERVAL,
@@ -231,7 +252,7 @@ fn test_harness<T: Future<Output = TestHarness>>(
.unwrap(); .unwrap();
}; };
let test_fut = test(TestHarness { virtual_overseer, req_cfg }); let test_fut = test(TestHarness { virtual_overseer, req_v1_cfg, req_vstaging_cfg });
futures::pin_mut!(test_fut); futures::pin_mut!(test_fut);
futures::pin_mut!(subsystem); futures::pin_mut!(subsystem);
@@ -305,6 +326,17 @@ async fn setup_system(virtual_overseer: &mut VirtualOverseer, test_state: &TestS
])), ])),
) )
.await; .await;
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
relay_parent,
RuntimeApiRequest::StagingAsyncBackingParams(tx)
)) => {
assert_eq!(relay_parent, test_state.relay_parent);
tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap();
}
);
} }
/// Result of [`distribute_collation`] /// Result of [`distribute_collation`]
@@ -313,29 +345,23 @@ struct DistributeCollation {
pov_block: PoV, pov_block: PoV,
} }
/// Create some PoV and distribute it. async fn distribute_collation_with_receipt(
async fn distribute_collation(
virtual_overseer: &mut VirtualOverseer, virtual_overseer: &mut VirtualOverseer,
test_state: &TestState, test_state: &TestState,
// whether or not we expect a connection request or not. relay_parent: Hash,
should_connect: bool, should_connect: bool,
candidate: CandidateReceipt,
pov: PoV,
parent_head_data_hash: Hash,
) -> DistributeCollation { ) -> DistributeCollation {
// Now we want to distribute a `PoVBlock`
let pov_block = PoV { block_data: BlockData(vec![42, 43, 44]) };
let pov_hash = pov_block.hash();
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent: test_state.relay_parent,
pov_hash,
..Default::default()
}
.build();
overseer_send( overseer_send(
virtual_overseer, virtual_overseer,
CollatorProtocolMessage::DistributeCollation(candidate.clone(), pov_block.clone(), None), CollatorProtocolMessage::DistributeCollation(
candidate.clone(),
parent_head_data_hash,
pov.clone(),
None,
),
) )
.await; .await;
@@ -343,10 +369,10 @@ async fn distribute_collation(
assert_matches!( assert_matches!(
overseer_recv(virtual_overseer).await, overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request( AllMessages::RuntimeApi(RuntimeApiMessage::Request(
relay_parent, _relay_parent,
RuntimeApiRequest::AvailabilityCores(tx) RuntimeApiRequest::AvailabilityCores(tx)
)) => { )) => {
assert_eq!(relay_parent, test_state.relay_parent); assert_eq!(relay_parent, _relay_parent);
tx.send(Ok(test_state.availability_cores.clone())).unwrap(); tx.send(Ok(test_state.availability_cores.clone())).unwrap();
} }
); );
@@ -358,7 +384,7 @@ async fn distribute_collation(
relay_parent, relay_parent,
RuntimeApiRequest::SessionIndexForChild(tx), RuntimeApiRequest::SessionIndexForChild(tx),
)) => { )) => {
assert_eq!(relay_parent, test_state.relay_parent); assert_eq!(relay_parent, relay_parent);
tx.send(Ok(test_state.current_session_index())).unwrap(); tx.send(Ok(test_state.current_session_index())).unwrap();
}, },
@@ -366,17 +392,17 @@ async fn distribute_collation(
relay_parent, relay_parent,
RuntimeApiRequest::SessionInfo(index, tx), RuntimeApiRequest::SessionInfo(index, tx),
)) => { )) => {
assert_eq!(relay_parent, test_state.relay_parent); assert_eq!(relay_parent, relay_parent);
assert_eq!(index, test_state.current_session_index()); assert_eq!(index, test_state.current_session_index());
tx.send(Ok(Some(test_state.session_info.clone()))).unwrap(); tx.send(Ok(Some(test_state.session_info.clone()))).unwrap();
}, },
AllMessages::RuntimeApi(RuntimeApiMessage::Request( AllMessages::RuntimeApi(RuntimeApiMessage::Request(
relay_parent, _relay_parent,
RuntimeApiRequest::ValidatorGroups(tx), RuntimeApiRequest::ValidatorGroups(tx),
)) => { )) => {
assert_eq!(relay_parent, test_state.relay_parent); assert_eq!(_relay_parent, relay_parent);
tx.send(Ok(( tx.send(Ok((
test_state.session_info.validator_groups.to_vec(), test_state.session_info.validator_groups.to_vec(),
test_state.group_rotation_info.clone(), test_state.group_rotation_info.clone(),
@@ -400,13 +426,48 @@ async fn distribute_collation(
); );
} }
DistributeCollation { candidate, pov_block } DistributeCollation { candidate, pov_block: pov }
}
/// Create some PoV and distribute it.
async fn distribute_collation(
virtual_overseer: &mut VirtualOverseer,
test_state: &TestState,
relay_parent: Hash,
// whether or not we expect a connection request or not.
should_connect: bool,
) -> DistributeCollation {
// Now we want to distribute a `PoVBlock`
let pov_block = PoV { block_data: BlockData(vec![42, 43, 44]) };
let pov_hash = pov_block.hash();
let parent_head_data_hash = Hash::zero();
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent,
pov_hash,
..Default::default()
}
.build();
distribute_collation_with_receipt(
virtual_overseer,
test_state,
relay_parent,
should_connect,
candidate,
pov_block,
parent_head_data_hash,
)
.await
} }
/// Connect a peer /// Connect a peer
async fn connect_peer( async fn connect_peer(
virtual_overseer: &mut VirtualOverseer, virtual_overseer: &mut VirtualOverseer,
peer: PeerId, peer: PeerId,
version: CollationVersion,
authority_id: Option<AuthorityDiscoveryId>, authority_id: Option<AuthorityDiscoveryId>,
) { ) {
overseer_send( overseer_send(
@@ -414,7 +475,7 @@ async fn connect_peer(
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected(
peer, peer,
polkadot_node_network_protocol::ObservedRole::Authority, polkadot_node_network_protocol::ObservedRole::Authority,
CollationVersion::V1.into(), version.into(),
authority_id.map(|v| HashSet::from([v])), authority_id.map(|v| HashSet::from([v])),
)), )),
) )
@@ -474,30 +535,65 @@ async fn expect_declare_msg(
} }
/// Check that the next received message is a collation advertisement message. /// Check that the next received message is a collation advertisement message.
///
/// Expects vstaging message if `expected_candidate_hashes` is `Some`, v1 otherwise.
async fn expect_advertise_collation_msg( async fn expect_advertise_collation_msg(
virtual_overseer: &mut VirtualOverseer, virtual_overseer: &mut VirtualOverseer,
peer: &PeerId, peer: &PeerId,
expected_relay_parent: Hash, expected_relay_parent: Hash,
expected_candidate_hashes: Option<Vec<CandidateHash>>,
) { ) {
assert_matches!( let mut candidate_hashes: Option<HashSet<_>> =
overseer_recv(virtual_overseer).await, expected_candidate_hashes.map(|hashes| hashes.into_iter().collect());
AllMessages::NetworkBridgeTx( let iter_num = candidate_hashes.as_ref().map(|hashes| hashes.len()).unwrap_or(1);
NetworkBridgeTxMessage::SendCollationMessage(
to, for _ in 0..iter_num {
Versioned::V1(protocol_v1::CollationProtocol::CollatorProtocol(wire_message)), assert_matches!(
) overseer_recv(virtual_overseer).await,
) => { AllMessages::NetworkBridgeTx(
assert_eq!(to[0], *peer); NetworkBridgeTxMessage::SendCollationMessage(
assert_matches!( to,
wire_message, wire_message,
protocol_v1::CollatorProtocolMessage::AdvertiseCollation( )
relay_parent, ) => {
) => { assert_eq!(to[0], *peer);
assert_eq!(relay_parent, expected_relay_parent); match (candidate_hashes.as_mut(), wire_message) {
(None, Versioned::V1(protocol_v1::CollationProtocol::CollatorProtocol(wire_message))) => {
assert_matches!(
wire_message,
protocol_v1::CollatorProtocolMessage::AdvertiseCollation(
relay_parent,
) => {
assert_eq!(relay_parent, expected_relay_parent);
}
);
},
(
Some(candidate_hashes),
Versioned::VStaging(protocol_vstaging::CollationProtocol::CollatorProtocol(
wire_message,
)),
) => {
assert_matches!(
wire_message,
protocol_vstaging::CollatorProtocolMessage::AdvertiseCollation {
relay_parent,
candidate_hash,
..
} => {
assert_eq!(relay_parent, expected_relay_parent);
assert!(candidate_hashes.contains(&candidate_hash));
// Drop the hash we've already seen.
candidate_hashes.remove(&candidate_hash);
}
);
},
_ => panic!("Invalid advertisement"),
} }
); }
} );
); }
} }
/// Send a message that the given peer's view changed. /// Send a message that the given peer's view changed.
@@ -528,19 +624,26 @@ fn advertise_and_send_collation() {
ReputationAggregator::new(|_| true), ReputationAggregator::new(|_| true),
|test_harness| async move { |test_harness| async move {
let mut virtual_overseer = test_harness.virtual_overseer; let mut virtual_overseer = test_harness.virtual_overseer;
let mut req_cfg = test_harness.req_cfg; let mut req_v1_cfg = test_harness.req_v1_cfg;
let req_vstaging_cfg = test_harness.req_vstaging_cfg;
setup_system(&mut virtual_overseer, &test_state).await; setup_system(&mut virtual_overseer, &test_state).await;
let DistributeCollation { candidate, pov_block } = let DistributeCollation { candidate, pov_block } = distribute_collation(
distribute_collation(&mut virtual_overseer, &test_state, true).await; &mut virtual_overseer,
&test_state,
test_state.relay_parent,
true,
)
.await;
for (val, peer) in test_state for (val, peer) in test_state
.current_group_validator_authority_ids() .current_group_validator_authority_ids()
.into_iter() .into_iter()
.zip(test_state.current_group_validator_peer_ids()) .zip(test_state.current_group_validator_peer_ids())
{ {
connect_peer(&mut virtual_overseer, peer, Some(val.clone())).await; connect_peer(&mut virtual_overseer, peer, CollationVersion::V1, Some(val.clone()))
.await;
} }
// We declare to the connected validators that we are a collator. // We declare to the connected validators that we are a collator.
@@ -558,18 +661,23 @@ fn advertise_and_send_collation() {
// The peer is interested in a leaf that we have a collation for; // The peer is interested in a leaf that we have a collation for;
// advertise it. // advertise it.
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent) expect_advertise_collation_msg(
.await; &mut virtual_overseer,
&peer,
test_state.relay_parent,
None,
)
.await;
// Request a collation. // Request a collation.
let (pending_response, rx) = oneshot::channel(); let (pending_response, rx) = oneshot::channel();
req_cfg req_v1_cfg
.inbound_queue .inbound_queue
.as_mut() .as_mut()
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer, peer,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: test_state.relay_parent, relay_parent: test_state.relay_parent,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -582,13 +690,13 @@ fn advertise_and_send_collation() {
{ {
let (pending_response, rx) = oneshot::channel(); let (pending_response, rx) = oneshot::channel();
req_cfg req_v1_cfg
.inbound_queue .inbound_queue
.as_mut() .as_mut()
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer, peer,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: test_state.relay_parent, relay_parent: test_state.relay_parent,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -613,8 +721,8 @@ fn advertise_and_send_collation() {
assert_matches!( assert_matches!(
rx.await, rx.await,
Ok(full_response) => { Ok(full_response) => {
let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse
= CollationFetchingResponse::decode( = request_v1::CollationFetchingResponse::decode(
&mut full_response.result &mut full_response.result
.expect("We should have a proper answer").as_ref() .expect("We should have a proper answer").as_ref()
) )
@@ -632,13 +740,13 @@ fn advertise_and_send_collation() {
// Re-request a collation. // Re-request a collation.
let (pending_response, rx) = oneshot::channel(); let (pending_response, rx) = oneshot::channel();
req_cfg req_v1_cfg
.inbound_queue .inbound_queue
.as_mut() .as_mut()
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer, peer,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: old_relay_parent, relay_parent: old_relay_parent,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -652,7 +760,8 @@ fn advertise_and_send_collation() {
assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none()); assert!(overseer_recv_with_timeout(&mut virtual_overseer, TIMEOUT).await.is_none());
distribute_collation(&mut virtual_overseer, &test_state, true).await; distribute_collation(&mut virtual_overseer, &test_state, test_state.relay_parent, true)
.await;
// Send info about peer's view. // Send info about peer's view.
overseer_send( overseer_send(
@@ -664,9 +773,14 @@ fn advertise_and_send_collation() {
) )
.await; .await;
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent) expect_advertise_collation_msg(
.await; &mut virtual_overseer,
TestHarness { virtual_overseer, req_cfg } &peer,
test_state.relay_parent,
None,
)
.await;
TestHarness { virtual_overseer, req_v1_cfg, req_vstaging_cfg }
}, },
); );
} }
@@ -683,18 +797,26 @@ fn delay_reputation_change() {
ReputationAggregator::new(|_| false), ReputationAggregator::new(|_| false),
|test_harness| async move { |test_harness| async move {
let mut virtual_overseer = test_harness.virtual_overseer; let mut virtual_overseer = test_harness.virtual_overseer;
let mut req_cfg = test_harness.req_cfg; let mut req_v1_cfg = test_harness.req_v1_cfg;
let req_vstaging_cfg = test_harness.req_vstaging_cfg;
setup_system(&mut virtual_overseer, &test_state).await; setup_system(&mut virtual_overseer, &test_state).await;
let _ = distribute_collation(&mut virtual_overseer, &test_state, true).await; let _ = distribute_collation(
&mut virtual_overseer,
&test_state,
test_state.relay_parent,
true,
)
.await;
for (val, peer) in test_state for (val, peer) in test_state
.current_group_validator_authority_ids() .current_group_validator_authority_ids()
.into_iter() .into_iter()
.zip(test_state.current_group_validator_peer_ids()) .zip(test_state.current_group_validator_peer_ids())
{ {
connect_peer(&mut virtual_overseer, peer, Some(val.clone())).await; connect_peer(&mut virtual_overseer, peer, CollationVersion::V1, Some(val.clone()))
.await;
} }
// We declare to the connected validators that we are a collator. // We declare to the connected validators that we are a collator.
@@ -712,18 +834,23 @@ fn delay_reputation_change() {
// The peer is interested in a leaf that we have a collation for; // The peer is interested in a leaf that we have a collation for;
// advertise it. // advertise it.
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent) expect_advertise_collation_msg(
.await; &mut virtual_overseer,
&peer,
test_state.relay_parent,
None,
)
.await;
// Request a collation. // Request a collation.
let (pending_response, _rx) = oneshot::channel(); let (pending_response, _rx) = oneshot::channel();
req_cfg req_v1_cfg
.inbound_queue .inbound_queue
.as_mut() .as_mut()
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer, peer,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: test_state.relay_parent, relay_parent: test_state.relay_parent,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -736,13 +863,13 @@ fn delay_reputation_change() {
{ {
let (pending_response, _rx) = oneshot::channel(); let (pending_response, _rx) = oneshot::channel();
req_cfg req_v1_cfg
.inbound_queue .inbound_queue
.as_mut() .as_mut()
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer, peer,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: test_state.relay_parent, relay_parent: test_state.relay_parent,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -767,7 +894,90 @@ fn delay_reputation_change() {
); );
} }
TestHarness { virtual_overseer, req_cfg } TestHarness { virtual_overseer, req_v1_cfg, req_vstaging_cfg }
},
);
}
/// Tests that collator side works with vstaging network protocol
/// before async backing is enabled.
#[test]
fn advertise_collation_vstaging_protocol() {
let test_state = TestState::default();
let local_peer_id = test_state.local_peer_id;
let collator_pair = test_state.collator_pair.clone();
test_harness(
local_peer_id,
collator_pair,
ReputationAggregator::new(|_| true),
|mut test_harness| async move {
let virtual_overseer = &mut test_harness.virtual_overseer;
setup_system(virtual_overseer, &test_state).await;
let DistributeCollation { candidate, .. } =
distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true)
.await;
let validators = test_state.current_group_validator_authority_ids();
assert!(validators.len() >= 2);
let peer_ids = test_state.current_group_validator_peer_ids();
// Connect first peer with v1.
connect_peer(
virtual_overseer,
peer_ids[0],
CollationVersion::V1,
Some(validators[0].clone()),
)
.await;
// The rest with vstaging.
for (val, peer) in validators.iter().zip(peer_ids.iter()).skip(1) {
connect_peer(
virtual_overseer,
*peer,
CollationVersion::VStaging,
Some(val.clone()),
)
.await;
}
// Declare messages.
expect_declare_msg(virtual_overseer, &test_state, &peer_ids[0]).await;
for peer_id in peer_ids.iter().skip(1) {
prospective_parachains::expect_declare_msg_vstaging(
virtual_overseer,
&test_state,
&peer_id,
)
.await;
}
// Send info about peers view.
for peer in peer_ids.iter() {
send_peer_view_change(virtual_overseer, peer, vec![test_state.relay_parent]).await;
}
// Versioned advertisements work.
expect_advertise_collation_msg(
virtual_overseer,
&peer_ids[0],
test_state.relay_parent,
None,
)
.await;
for peer_id in peer_ids.iter().skip(1) {
expect_advertise_collation_msg(
virtual_overseer,
peer_id,
test_state.relay_parent,
Some(vec![candidate.hash()]), // This is `Some`, advertisement is vstaging.
)
.await;
}
test_harness
}, },
); );
} }
@@ -814,7 +1024,13 @@ fn collators_declare_to_connected_peers() {
setup_system(&mut test_harness.virtual_overseer, &test_state).await; setup_system(&mut test_harness.virtual_overseer, &test_state).await;
// A validator connected to us // A validator connected to us
connect_peer(&mut test_harness.virtual_overseer, peer, Some(validator_id)).await; connect_peer(
&mut test_harness.virtual_overseer,
peer,
CollationVersion::V1,
Some(validator_id),
)
.await;
expect_declare_msg(&mut test_harness.virtual_overseer, &test_state, &peer).await; expect_declare_msg(&mut test_harness.virtual_overseer, &test_state, &peer).await;
test_harness test_harness
}, },
@@ -843,10 +1059,10 @@ fn collations_are_only_advertised_to_validators_with_correct_view() {
setup_system(virtual_overseer, &test_state).await; setup_system(virtual_overseer, &test_state).await;
// A validator connected to us // A validator connected to us
connect_peer(virtual_overseer, peer, Some(validator_id)).await; connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await;
// Connect the second validator // Connect the second validator
connect_peer(virtual_overseer, peer2, Some(validator_id2)).await; connect_peer(virtual_overseer, peer2, CollationVersion::V1, Some(validator_id2)).await;
expect_declare_msg(virtual_overseer, &test_state, &peer).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await;
expect_declare_msg(virtual_overseer, &test_state, &peer2).await; expect_declare_msg(virtual_overseer, &test_state, &peer2).await;
@@ -854,15 +1070,18 @@ fn collations_are_only_advertised_to_validators_with_correct_view() {
// And let it tell us that it is has the same view. // And let it tell us that it is has the same view.
send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await; send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
distribute_collation(virtual_overseer, &test_state, true).await; distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true)
.await;
expect_advertise_collation_msg(virtual_overseer, &peer2, test_state.relay_parent).await; expect_advertise_collation_msg(virtual_overseer, &peer2, test_state.relay_parent, None)
.await;
// The other validator announces that it changed its view. // The other validator announces that it changed its view.
send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await; send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await;
// After changing the view we should receive the advertisement // After changing the view we should receive the advertisement
expect_advertise_collation_msg(virtual_overseer, &peer, test_state.relay_parent).await; expect_advertise_collation_msg(virtual_overseer, &peer, test_state.relay_parent, None)
.await;
test_harness test_harness
}, },
) )
@@ -890,15 +1109,16 @@ fn collate_on_two_different_relay_chain_blocks() {
setup_system(virtual_overseer, &test_state).await; setup_system(virtual_overseer, &test_state).await;
// A validator connected to us // A validator connected to us
connect_peer(virtual_overseer, peer, Some(validator_id)).await; connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await;
// Connect the second validator // Connect the second validator
connect_peer(virtual_overseer, peer2, Some(validator_id2)).await; connect_peer(virtual_overseer, peer2, CollationVersion::V1, Some(validator_id2)).await;
expect_declare_msg(virtual_overseer, &test_state, &peer).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await;
expect_declare_msg(virtual_overseer, &test_state, &peer2).await; expect_declare_msg(virtual_overseer, &test_state, &peer2).await;
distribute_collation(virtual_overseer, &test_state, true).await; distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true)
.await;
let old_relay_parent = test_state.relay_parent; let old_relay_parent = test_state.relay_parent;
@@ -906,14 +1126,16 @@ fn collate_on_two_different_relay_chain_blocks() {
// parent are active. // parent are active.
test_state.advance_to_new_round(virtual_overseer, true).await; test_state.advance_to_new_round(virtual_overseer, true).await;
distribute_collation(virtual_overseer, &test_state, true).await; distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true)
.await;
send_peer_view_change(virtual_overseer, &peer, vec![old_relay_parent]).await; send_peer_view_change(virtual_overseer, &peer, vec![old_relay_parent]).await;
expect_advertise_collation_msg(virtual_overseer, &peer, old_relay_parent).await; expect_advertise_collation_msg(virtual_overseer, &peer, old_relay_parent, None).await;
send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await; send_peer_view_change(virtual_overseer, &peer2, vec![test_state.relay_parent]).await;
expect_advertise_collation_msg(virtual_overseer, &peer2, test_state.relay_parent).await; expect_advertise_collation_msg(virtual_overseer, &peer2, test_state.relay_parent, None)
.await;
test_harness test_harness
}, },
) )
@@ -938,17 +1160,20 @@ fn validator_reconnect_does_not_advertise_a_second_time() {
setup_system(virtual_overseer, &test_state).await; setup_system(virtual_overseer, &test_state).await;
// A validator connected to us // A validator connected to us
connect_peer(virtual_overseer, peer, Some(validator_id.clone())).await; connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id.clone()))
.await;
expect_declare_msg(virtual_overseer, &test_state, &peer).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await;
distribute_collation(virtual_overseer, &test_state, true).await; distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true)
.await;
send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await; send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await;
expect_advertise_collation_msg(virtual_overseer, &peer, test_state.relay_parent).await; expect_advertise_collation_msg(virtual_overseer, &peer, test_state.relay_parent, None)
.await;
// Disconnect and reconnect directly // Disconnect and reconnect directly
disconnect_peer(virtual_overseer, peer).await; disconnect_peer(virtual_overseer, peer).await;
connect_peer(virtual_overseer, peer, Some(validator_id)).await; connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await;
expect_declare_msg(virtual_overseer, &test_state, &peer).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await;
send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await; send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await;
@@ -979,7 +1204,7 @@ fn collators_reject_declare_messages() {
setup_system(virtual_overseer, &test_state).await; setup_system(virtual_overseer, &test_state).await;
// A validator connected to us // A validator connected to us
connect_peer(virtual_overseer, peer, Some(validator_id)).await; connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(validator_id)).await;
expect_declare_msg(virtual_overseer, &test_state, &peer).await; expect_declare_msg(virtual_overseer, &test_state, &peer).await;
overseer_send( overseer_send(
@@ -1031,19 +1256,20 @@ where
ReputationAggregator::new(|_| true), ReputationAggregator::new(|_| true),
|mut test_harness| async move { |mut test_harness| async move {
let virtual_overseer = &mut test_harness.virtual_overseer; let virtual_overseer = &mut test_harness.virtual_overseer;
let req_cfg = &mut test_harness.req_cfg; let req_cfg = &mut test_harness.req_v1_cfg;
setup_system(virtual_overseer, &test_state).await; setup_system(virtual_overseer, &test_state).await;
let DistributeCollation { candidate, pov_block } = let DistributeCollation { candidate, pov_block } =
distribute_collation(virtual_overseer, &test_state, true).await; distribute_collation(virtual_overseer, &test_state, test_state.relay_parent, true)
.await;
for (val, peer) in test_state for (val, peer) in test_state
.current_group_validator_authority_ids() .current_group_validator_authority_ids()
.into_iter() .into_iter()
.zip(test_state.current_group_validator_peer_ids()) .zip(test_state.current_group_validator_peer_ids())
{ {
connect_peer(virtual_overseer, peer, Some(val.clone())).await; connect_peer(virtual_overseer, peer, CollationVersion::V1, Some(val.clone())).await;
} }
// We declare to the connected validators that we are a collator. // We declare to the connected validators that we are a collator.
@@ -1064,10 +1290,20 @@ where
// The peer is interested in a leaf that we have a collation for; // The peer is interested in a leaf that we have a collation for;
// advertise it. // advertise it.
expect_advertise_collation_msg(virtual_overseer, &validator_0, test_state.relay_parent) expect_advertise_collation_msg(
.await; virtual_overseer,
expect_advertise_collation_msg(virtual_overseer, &validator_1, test_state.relay_parent) &validator_0,
.await; test_state.relay_parent,
None,
)
.await;
expect_advertise_collation_msg(
virtual_overseer,
&validator_1,
test_state.relay_parent,
None,
)
.await;
// Request a collation. // Request a collation.
let (pending_response, rx) = oneshot::channel(); let (pending_response, rx) = oneshot::channel();
@@ -1077,7 +1313,7 @@ where
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer: validator_0, peer: validator_0,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: test_state.relay_parent, relay_parent: test_state.relay_parent,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -1092,8 +1328,8 @@ where
let feedback_tx = assert_matches!( let feedback_tx = assert_matches!(
rx.await, rx.await,
Ok(full_response) => { Ok(full_response) => {
let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse
= CollationFetchingResponse::decode( = request_v1::CollationFetchingResponse::decode(
&mut full_response.result &mut full_response.result
.expect("We should have a proper answer").as_ref() .expect("We should have a proper answer").as_ref()
) )
@@ -1113,7 +1349,7 @@ where
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer: validator_1, peer: validator_1,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: test_state.relay_parent, relay_parent: test_state.relay_parent,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -1129,8 +1365,8 @@ where
assert_matches!( assert_matches!(
rx.await, rx.await,
Ok(full_response) => { Ok(full_response) => {
let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse
= CollationFetchingResponse::decode( = request_v1::CollationFetchingResponse::decode(
&mut full_response.result &mut full_response.result
.expect("We should have a proper answer").as_ref() .expect("We should have a proper answer").as_ref()
) )
@@ -1159,7 +1395,8 @@ fn connect_to_buffered_groups() {
ReputationAggregator::new(|_| true), ReputationAggregator::new(|_| true),
|test_harness| async move { |test_harness| async move {
let mut virtual_overseer = test_harness.virtual_overseer; let mut virtual_overseer = test_harness.virtual_overseer;
let mut req_cfg = test_harness.req_cfg; let mut req_cfg = test_harness.req_v1_cfg;
let req_vstaging_cfg = test_harness.req_vstaging_cfg;
setup_system(&mut virtual_overseer, &test_state).await; setup_system(&mut virtual_overseer, &test_state).await;
@@ -1167,7 +1404,13 @@ fn connect_to_buffered_groups() {
let peers_a = test_state.current_group_validator_peer_ids(); let peers_a = test_state.current_group_validator_peer_ids();
assert!(group_a.len() > 1); assert!(group_a.len() > 1);
distribute_collation(&mut virtual_overseer, &test_state, false).await; distribute_collation(
&mut virtual_overseer,
&test_state,
test_state.relay_parent,
false,
)
.await;
assert_matches!( assert_matches!(
overseer_recv(&mut virtual_overseer).await, overseer_recv(&mut virtual_overseer).await,
@@ -1181,7 +1424,8 @@ fn connect_to_buffered_groups() {
let head_a = test_state.relay_parent; let head_a = test_state.relay_parent;
for (val, peer) in group_a.iter().zip(&peers_a) { for (val, peer) in group_a.iter().zip(&peers_a) {
connect_peer(&mut virtual_overseer, *peer, Some(val.clone())).await; connect_peer(&mut virtual_overseer, *peer, CollationVersion::V1, Some(val.clone()))
.await;
} }
for peer_id in &peers_a { for peer_id in &peers_a {
@@ -1191,7 +1435,7 @@ fn connect_to_buffered_groups() {
// Update views. // Update views.
for peed_id in &peers_a { for peed_id in &peers_a {
send_peer_view_change(&mut virtual_overseer, peed_id, vec![head_a]).await; send_peer_view_change(&mut virtual_overseer, peed_id, vec![head_a]).await;
expect_advertise_collation_msg(&mut virtual_overseer, peed_id, head_a).await; expect_advertise_collation_msg(&mut virtual_overseer, peed_id, head_a, None).await;
} }
let peer = peers_a[0]; let peer = peers_a[0];
@@ -1203,7 +1447,7 @@ fn connect_to_buffered_groups() {
.unwrap() .unwrap()
.send(RawIncomingRequest { .send(RawIncomingRequest {
peer, peer,
payload: CollationFetchingRequest { payload: request_v1::CollationFetchingRequest {
relay_parent: head_a, relay_parent: head_a,
para_id: test_state.para_id, para_id: test_state.para_id,
} }
@@ -1215,14 +1459,17 @@ fn connect_to_buffered_groups() {
assert_matches!( assert_matches!(
rx.await, rx.await,
Ok(full_response) => { Ok(full_response) => {
let CollationFetchingResponse::Collation(..): CollationFetchingResponse = let request_v1::CollationFetchingResponse::Collation(..) =
CollationFetchingResponse::decode( request_v1::CollationFetchingResponse::decode(
&mut full_response.result.expect("We should have a proper answer").as_ref(), &mut full_response.result.expect("We should have a proper answer").as_ref(),
) )
.expect("Decoding should work"); .expect("Decoding should work");
} }
); );
// Let the subsystem process process the collation event.
test_helpers::Yield::new().await;
test_state.advance_to_new_round(&mut virtual_overseer, true).await; test_state.advance_to_new_round(&mut virtual_overseer, true).await;
test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation();
@@ -1231,7 +1478,13 @@ fn connect_to_buffered_groups() {
assert_ne!(head_a, head_b); assert_ne!(head_a, head_b);
assert_ne!(group_a, group_b); assert_ne!(group_a, group_b);
distribute_collation(&mut virtual_overseer, &test_state, false).await; distribute_collation(
&mut virtual_overseer,
&test_state,
test_state.relay_parent,
false,
)
.await;
// Should be connected to both groups except for the validator that fetched advertised // Should be connected to both groups except for the validator that fetched advertised
// collation. // collation.
@@ -1248,7 +1501,7 @@ fn connect_to_buffered_groups() {
} }
); );
TestHarness { virtual_overseer, req_cfg } TestHarness { virtual_overseer, req_v1_cfg: req_cfg, req_vstaging_cfg }
}, },
); );
} }
@@ -0,0 +1,575 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Tests for the collator side with enabled prospective parachains.
use super::*;
use polkadot_node_subsystem::messages::{ChainApiMessage, ProspectiveParachainsMessage};
use polkadot_primitives::{vstaging as vstaging_primitives, Header, OccupiedCore};
const ASYNC_BACKING_PARAMETERS: vstaging_primitives::AsyncBackingParams =
vstaging_primitives::AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 };
fn get_parent_hash(hash: Hash) -> Hash {
Hash::from_low_u64_be(hash.to_low_u64_be() + 1)
}
/// Handle a view update.
async fn update_view(
virtual_overseer: &mut VirtualOverseer,
test_state: &TestState,
new_view: Vec<(Hash, u32)>, // Hash and block number.
activated: u8, // How many new heads does this update contain?
) {
let new_view: HashMap<Hash, u32> = HashMap::from_iter(new_view);
let our_view =
OurView::new(new_view.keys().map(|hash| (*hash, Arc::new(jaeger::Span::Disabled))), 0);
overseer_send(
virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(our_view)),
)
.await;
let mut next_overseer_message = None;
for _ in 0..activated {
let (leaf_hash, leaf_number) = assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::StagingAsyncBackingParams(tx),
)) => {
tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap();
(parent, new_view.get(&parent).copied().expect("Unknown parent requested"))
}
);
let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetMinimumRelayParents(parent, tx),
) if parent == leaf_hash => {
tx.send(vec![(test_state.para_id, min_number)]).unwrap();
}
);
let ancestry_len = leaf_number + 1 - min_number;
let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h)))
.take(ancestry_len as usize);
let ancestry_numbers = (min_number..=leaf_number).rev();
let mut ancestry_iter = ancestry_hashes.clone().zip(ancestry_numbers).peekable();
while let Some((hash, number)) = ancestry_iter.next() {
// May be `None` for the last element.
let parent_hash =
ancestry_iter.peek().map(|(h, _)| *h).unwrap_or_else(|| get_parent_hash(hash));
let msg = match next_overseer_message.take() {
Some(msg) => Some(msg),
None =>
overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(50)).await,
};
let msg = match msg {
Some(msg) => msg,
None => {
// We're done.
return
},
};
if !matches!(
&msg,
AllMessages::ChainApi(ChainApiMessage::BlockHeader(_hash, ..))
if *_hash == hash
) {
// Ancestry has already been cached for this leaf.
next_overseer_message.replace(msg);
break
}
assert_matches!(
msg,
AllMessages::ChainApi(ChainApiMessage::BlockHeader(.., tx)) => {
let header = Header {
parent_hash,
number,
state_root: Hash::zero(),
extrinsics_root: Hash::zero(),
digest: Default::default(),
};
tx.send(Ok(Some(header))).unwrap();
}
);
}
}
}
/// Check that the next received message is a `Declare` message.
pub(super) async fn expect_declare_msg_vstaging(
virtual_overseer: &mut VirtualOverseer,
test_state: &TestState,
peer: &PeerId,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendCollationMessage(
to,
Versioned::VStaging(protocol_vstaging::CollationProtocol::CollatorProtocol(
wire_message,
)),
)) => {
assert_eq!(to[0], *peer);
assert_matches!(
wire_message,
protocol_vstaging::CollatorProtocolMessage::Declare(
collator_id,
para_id,
signature,
) => {
assert!(signature.verify(
&*protocol_vstaging::declare_signature_payload(&test_state.local_peer_id),
&collator_id),
);
assert_eq!(collator_id, test_state.collator_pair.public());
assert_eq!(para_id, test_state.para_id);
}
);
}
);
}
/// Test that a collator distributes a collation from the allowed ancestry
/// to correct validators group.
#[test]
fn distribute_collation_from_implicit_view() {
let head_a = Hash::from_low_u64_be(126);
let head_a_num: u32 = 66;
// Grandparent of head `a`.
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 64;
// Grandparent of head `b`.
let head_c = Hash::from_low_u64_be(130);
let head_c_num = 62;
let group_rotation_info = GroupRotationInfo {
session_start_block: head_c_num - 2,
group_rotation_frequency: 3,
now: head_c_num,
};
let mut test_state = TestState::default();
test_state.group_rotation_info = group_rotation_info;
let local_peer_id = test_state.local_peer_id;
let collator_pair = test_state.collator_pair.clone();
test_harness(
local_peer_id,
collator_pair,
ReputationAggregator::new(|_| true),
|mut test_harness| async move {
let virtual_overseer = &mut test_harness.virtual_overseer;
// Set collating para id.
overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id))
.await;
// Activated leaf is `b`, but the collation will be based on `c`.
update_view(virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let validator_peer_ids = test_state.current_group_validator_peer_ids();
for (val, peer) in test_state
.current_group_validator_authority_ids()
.into_iter()
.zip(validator_peer_ids.clone())
{
connect_peer(virtual_overseer, peer, CollationVersion::VStaging, Some(val.clone()))
.await;
}
// Collator declared itself to each peer.
for peer_id in &validator_peer_ids {
expect_declare_msg_vstaging(virtual_overseer, &test_state, peer_id).await;
}
let pov = PoV { block_data: BlockData(vec![1, 2, 3]) };
let parent_head_data_hash = Hash::repeat_byte(0xAA);
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent: head_c,
pov_hash: pov.hash(),
..Default::default()
}
.build();
let DistributeCollation { candidate, pov_block: _ } =
distribute_collation_with_receipt(
virtual_overseer,
&test_state,
head_c,
false, // Check the group manually.
candidate,
pov,
parent_head_data_hash,
)
.await;
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::ConnectToValidators { validator_ids, .. }
) => {
let expected_validators = test_state.current_group_validator_authority_ids();
assert_eq!(expected_validators, validator_ids);
}
);
let candidate_hash = candidate.hash();
// Update peer views.
for peed_id in &validator_peer_ids {
send_peer_view_change(virtual_overseer, peed_id, vec![head_b]).await;
expect_advertise_collation_msg(
virtual_overseer,
peed_id,
head_c,
Some(vec![candidate_hash]),
)
.await;
}
// Head `c` goes out of view.
// Build a different candidate for this relay parent and attempt to distribute it.
update_view(virtual_overseer, &test_state, vec![(head_a, head_a_num)], 1).await;
let pov = PoV { block_data: BlockData(vec![4, 5, 6]) };
let parent_head_data_hash = Hash::repeat_byte(0xBB);
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent: head_c,
pov_hash: pov.hash(),
..Default::default()
}
.build();
overseer_send(
virtual_overseer,
CollatorProtocolMessage::DistributeCollation(
candidate.clone(),
parent_head_data_hash,
pov.clone(),
None,
),
)
.await;
// Parent out of view, nothing happens.
assert!(overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(100))
.await
.is_none());
test_harness
},
)
}
/// Tests that collator can distribute up to `MAX_CANDIDATE_DEPTH + 1` candidates
/// per relay parent.
#[test]
fn distribute_collation_up_to_limit() {
let test_state = TestState::default();
let local_peer_id = test_state.local_peer_id;
let collator_pair = test_state.collator_pair.clone();
test_harness(
local_peer_id,
collator_pair,
ReputationAggregator::new(|_| true),
|mut test_harness| async move {
let virtual_overseer = &mut test_harness.virtual_overseer;
let head_a = Hash::from_low_u64_be(128);
let head_a_num: u32 = 64;
// Grandparent of head `a`.
let head_b = Hash::from_low_u64_be(130);
// Set collating para id.
overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id))
.await;
// Activated leaf is `a`, but the collation will be based on `b`.
update_view(virtual_overseer, &test_state, vec![(head_a, head_a_num)], 1).await;
for i in 0..(ASYNC_BACKING_PARAMETERS.max_candidate_depth + 1) {
let pov = PoV { block_data: BlockData(vec![i as u8]) };
let parent_head_data_hash = Hash::repeat_byte(0xAA);
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent: head_b,
pov_hash: pov.hash(),
..Default::default()
}
.build();
distribute_collation_with_receipt(
virtual_overseer,
&test_state,
head_b,
true,
candidate,
pov,
parent_head_data_hash,
)
.await;
}
let pov = PoV { block_data: BlockData(vec![10, 12, 6]) };
let parent_head_data_hash = Hash::repeat_byte(0xBB);
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent: head_b,
pov_hash: pov.hash(),
..Default::default()
}
.build();
overseer_send(
virtual_overseer,
CollatorProtocolMessage::DistributeCollation(
candidate.clone(),
parent_head_data_hash,
pov.clone(),
None,
),
)
.await;
// Limit has been reached.
assert!(overseer_recv_with_timeout(virtual_overseer, Duration::from_millis(100))
.await
.is_none());
test_harness
},
)
}
/// Tests that collator correctly handles peer V2 requests.
#[test]
fn advertise_and_send_collation_by_hash() {
let test_state = TestState::default();
let local_peer_id = test_state.local_peer_id;
let collator_pair = test_state.collator_pair.clone();
test_harness(
local_peer_id,
collator_pair,
ReputationAggregator::new(|_| true),
|test_harness| async move {
let mut virtual_overseer = test_harness.virtual_overseer;
let req_v1_cfg = test_harness.req_v1_cfg;
let mut req_vstaging_cfg = test_harness.req_vstaging_cfg;
let head_a = Hash::from_low_u64_be(128);
let head_a_num: u32 = 64;
// Parent of head `a`.
let head_b = Hash::from_low_u64_be(129);
let head_b_num: u32 = 63;
// Set collating para id.
overseer_send(
&mut virtual_overseer,
CollatorProtocolMessage::CollateOn(test_state.para_id),
)
.await;
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
update_view(&mut virtual_overseer, &test_state, vec![(head_a, head_a_num)], 1).await;
let candidates: Vec<_> = (0..2)
.map(|i| {
let pov = PoV { block_data: BlockData(vec![i as u8]) };
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent: head_b,
pov_hash: pov.hash(),
..Default::default()
}
.build();
(candidate, pov)
})
.collect();
for (candidate, pov) in &candidates {
distribute_collation_with_receipt(
&mut virtual_overseer,
&test_state,
head_b,
true,
candidate.clone(),
pov.clone(),
Hash::zero(),
)
.await;
}
let peer = test_state.validator_peer_id[0];
let validator_id = test_state.current_group_validator_authority_ids()[0].clone();
connect_peer(
&mut virtual_overseer,
peer,
CollationVersion::VStaging,
Some(validator_id.clone()),
)
.await;
expect_declare_msg_vstaging(&mut virtual_overseer, &test_state, &peer).await;
// Head `b` is not a leaf, but both advertisements are still relevant.
send_peer_view_change(&mut virtual_overseer, &peer, vec![head_b]).await;
let hashes: Vec<_> = candidates.iter().map(|(candidate, _)| candidate.hash()).collect();
expect_advertise_collation_msg(&mut virtual_overseer, &peer, head_b, Some(hashes))
.await;
for (candidate, pov_block) in candidates {
let (pending_response, rx) = oneshot::channel();
req_vstaging_cfg
.inbound_queue
.as_mut()
.unwrap()
.send(RawIncomingRequest {
peer,
payload: request_vstaging::CollationFetchingRequest {
relay_parent: head_b,
para_id: test_state.para_id,
candidate_hash: candidate.hash(),
}
.encode(),
pending_response,
})
.await
.unwrap();
assert_matches!(
rx.await,
Ok(full_response) => {
// Response is the same for vstaging.
let request_v1::CollationFetchingResponse::Collation(receipt, pov): request_v1::CollationFetchingResponse
= request_v1::CollationFetchingResponse::decode(
&mut full_response.result
.expect("We should have a proper answer").as_ref()
)
.expect("Decoding should work");
assert_eq!(receipt, candidate);
assert_eq!(pov, pov_block);
}
);
}
TestHarness { virtual_overseer, req_v1_cfg, req_vstaging_cfg }
},
)
}
/// Tests that collator distributes collation built on top of occupied core.
#[test]
fn advertise_core_occupied() {
let mut test_state = TestState::default();
let candidate =
TestCandidateBuilder { para_id: test_state.para_id, ..Default::default() }.build();
test_state.availability_cores[0] = CoreState::Occupied(OccupiedCore {
next_up_on_available: None,
occupied_since: 0,
time_out_at: 0,
next_up_on_time_out: None,
availability: BitVec::default(),
group_responsible: GroupIndex(0),
candidate_hash: candidate.hash(),
candidate_descriptor: candidate.descriptor,
});
let local_peer_id = test_state.local_peer_id;
let collator_pair = test_state.collator_pair.clone();
test_harness(
local_peer_id,
collator_pair,
ReputationAggregator::new(|_| true),
|mut test_harness| async move {
let virtual_overseer = &mut test_harness.virtual_overseer;
let head_a = Hash::from_low_u64_be(128);
let head_a_num: u32 = 64;
// Grandparent of head `a`.
let head_b = Hash::from_low_u64_be(130);
// Set collating para id.
overseer_send(virtual_overseer, CollatorProtocolMessage::CollateOn(test_state.para_id))
.await;
// Activated leaf is `a`, but the collation will be based on `b`.
update_view(virtual_overseer, &test_state, vec![(head_a, head_a_num)], 1).await;
let pov = PoV { block_data: BlockData(vec![1, 2, 3]) };
let candidate = TestCandidateBuilder {
para_id: test_state.para_id,
relay_parent: head_b,
pov_hash: pov.hash(),
..Default::default()
}
.build();
let candidate_hash = candidate.hash();
distribute_collation_with_receipt(
virtual_overseer,
&test_state,
head_b,
true,
candidate,
pov,
Hash::zero(),
)
.await;
let validators = test_state.current_group_validator_authority_ids();
let peer_ids = test_state.current_group_validator_peer_ids();
connect_peer(
virtual_overseer,
peer_ids[0],
CollationVersion::VStaging,
Some(validators[0].clone()),
)
.await;
expect_declare_msg_vstaging(virtual_overseer, &test_state, &peer_ids[0]).await;
// Peer is aware of the leaf.
send_peer_view_change(virtual_overseer, &peer_ids[0], vec![head_a]).await;
// Collation is advertised.
expect_advertise_collation_msg(
virtual_overseer,
&peer_ids[0],
head_b,
Some(vec![candidate_hash]),
)
.await;
test_harness
},
)
}
@@ -31,13 +31,19 @@
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
future::Future,
num::NonZeroUsize, num::NonZeroUsize,
ops::Range, ops::Range,
pin::Pin,
task::{Context, Poll},
time::Duration,
}; };
use bitvec::{bitvec, vec::BitVec}; use bitvec::{bitvec, vec::BitVec};
use futures::FutureExt;
use polkadot_primitives::{AuthorityDiscoveryId, GroupIndex, Hash, SessionIndex}; use polkadot_node_network_protocol::PeerId;
use polkadot_primitives::{AuthorityDiscoveryId, CandidateHash, GroupIndex, SessionIndex};
/// The ring buffer stores at most this many unique validator groups. /// The ring buffer stores at most this many unique validator groups.
/// ///
@@ -66,9 +72,9 @@ pub struct ValidatorGroupsBuffer {
group_infos: VecDeque<ValidatorsGroupInfo>, group_infos: VecDeque<ValidatorsGroupInfo>,
/// Continuous buffer of validators discovery keys. /// Continuous buffer of validators discovery keys.
validators: VecDeque<AuthorityDiscoveryId>, validators: VecDeque<AuthorityDiscoveryId>,
/// Mapping from relay-parent to bit-vectors with bits for all `validators`. /// Mapping from candidate hashes to bit-vectors with bits for all `validators`.
/// Invariants kept: All bit-vectors are guaranteed to have the same size. /// Invariants kept: All bit-vectors are guaranteed to have the same size.
should_be_connected: HashMap<Hash, BitVec>, should_be_connected: HashMap<CandidateHash, BitVec>,
/// Buffer capacity, limits the number of **groups** tracked. /// Buffer capacity, limits the number of **groups** tracked.
cap: NonZeroUsize, cap: NonZeroUsize,
} }
@@ -107,7 +113,7 @@ impl ValidatorGroupsBuffer {
/// of the buffer. /// of the buffer.
pub fn note_collation_advertised( pub fn note_collation_advertised(
&mut self, &mut self,
relay_parent: Hash, candidate_hash: CandidateHash,
session_index: SessionIndex, session_index: SessionIndex,
group_index: GroupIndex, group_index: GroupIndex,
validators: &[AuthorityDiscoveryId], validators: &[AuthorityDiscoveryId],
@@ -121,19 +127,19 @@ impl ValidatorGroupsBuffer {
}) { }) {
Some((idx, group)) => { Some((idx, group)) => {
let group_start_idx = self.group_lengths_iter().take(idx).sum(); let group_start_idx = self.group_lengths_iter().take(idx).sum();
self.set_bits(relay_parent, group_start_idx..(group_start_idx + group.len)); self.set_bits(candidate_hash, group_start_idx..(group_start_idx + group.len));
}, },
None => self.push(relay_parent, session_index, group_index, validators), None => self.push(candidate_hash, session_index, group_index, validators),
} }
} }
/// Note that a validator is no longer interested in a given relay parent. /// Note that a validator is no longer interested in a given relay parent.
pub fn reset_validator_interest( pub fn reset_validator_interest(
&mut self, &mut self,
relay_parent: Hash, candidate_hash: CandidateHash,
authority_id: &AuthorityDiscoveryId, authority_id: &AuthorityDiscoveryId,
) { ) {
let bits = match self.should_be_connected.get_mut(&relay_parent) { let bits = match self.should_be_connected.get_mut(&candidate_hash) {
Some(bits) => bits, Some(bits) => bits,
None => return, None => return,
}; };
@@ -145,17 +151,12 @@ impl ValidatorGroupsBuffer {
} }
} }
/// Remove relay parent from the buffer. /// Remove advertised candidate from the buffer.
/// ///
/// The buffer will no longer track which validators are interested in a corresponding /// The buffer will no longer track which validators are interested in a corresponding
/// advertisement. /// advertisement.
pub fn remove_relay_parent(&mut self, relay_parent: &Hash) { pub fn remove_candidate(&mut self, candidate_hash: &CandidateHash) {
self.should_be_connected.remove(relay_parent); self.should_be_connected.remove(candidate_hash);
}
/// Removes all advertisements from the buffer.
pub fn clear_advertisements(&mut self) {
self.should_be_connected.clear();
} }
/// Pushes a new group to the buffer along with advertisement, setting all validators /// Pushes a new group to the buffer along with advertisement, setting all validators
@@ -164,7 +165,7 @@ impl ValidatorGroupsBuffer {
/// If the buffer is full, drops group from the tail. /// If the buffer is full, drops group from the tail.
fn push( fn push(
&mut self, &mut self,
relay_parent: Hash, candidate_hash: CandidateHash,
session_index: SessionIndex, session_index: SessionIndex,
group_index: GroupIndex, group_index: GroupIndex,
validators: &[AuthorityDiscoveryId], validators: &[AuthorityDiscoveryId],
@@ -193,17 +194,17 @@ impl ValidatorGroupsBuffer {
self.should_be_connected self.should_be_connected
.values_mut() .values_mut()
.for_each(|bits| bits.resize(new_len, false)); .for_each(|bits| bits.resize(new_len, false));
self.set_bits(relay_parent, group_start_idx..(group_start_idx + validators.len())); self.set_bits(candidate_hash, group_start_idx..(group_start_idx + validators.len()));
} }
/// Sets advertisement bits to 1 in a given range (usually corresponding to some group). /// Sets advertisement bits to 1 in a given range (usually corresponding to some group).
/// If the relay parent is unknown, inserts 0-initialized bitvec first. /// If the relay parent is unknown, inserts 0-initialized bitvec first.
/// ///
/// The range must be ensured to be within bounds. /// The range must be ensured to be within bounds.
fn set_bits(&mut self, relay_parent: Hash, range: Range<usize>) { fn set_bits(&mut self, candidate_hash: CandidateHash, range: Range<usize>) {
let bits = self let bits = self
.should_be_connected .should_be_connected
.entry(relay_parent) .entry(candidate_hash)
.or_insert_with(|| bitvec![0; self.validators.len()]); .or_insert_with(|| bitvec![0; self.validators.len()]);
bits[range].fill(true); bits[range].fill(true);
@@ -217,9 +218,40 @@ impl ValidatorGroupsBuffer {
} }
} }
/// A timeout for resetting validators' interests in collations.
pub const RESET_INTEREST_TIMEOUT: Duration = Duration::from_secs(6);
/// A future that returns a candidate hash along with validator discovery
/// keys once a timeout hit.
///
/// If a validator doesn't manage to fetch a collation within this timeout
/// we should reset its interest in this advertisement in a buffer. For example,
/// when the PoV was already requested from another peer.
pub struct ResetInterestTimeout {
fut: futures_timer::Delay,
candidate_hash: CandidateHash,
peer_id: PeerId,
}
impl ResetInterestTimeout {
/// Returns new `ResetInterestTimeout` that resolves after given timeout.
pub fn new(candidate_hash: CandidateHash, peer_id: PeerId, delay: Duration) -> Self {
Self { fut: futures_timer::Delay::new(delay), candidate_hash, peer_id }
}
}
impl Future for ResetInterestTimeout {
type Output = (CandidateHash, PeerId);
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.fut.poll_unpin(cx).map(|_| (self.candidate_hash, self.peer_id))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use polkadot_primitives::Hash;
use sp_keyring::Sr25519Keyring; use sp_keyring::Sr25519Keyring;
#[test] #[test]
@@ -227,8 +259,8 @@ mod tests {
let cap = NonZeroUsize::new(1).unwrap(); let cap = NonZeroUsize::new(1).unwrap();
let mut buf = ValidatorGroupsBuffer::with_capacity(cap); let mut buf = ValidatorGroupsBuffer::with_capacity(cap);
let hash_a = Hash::repeat_byte(0x1); let hash_a = CandidateHash(Hash::repeat_byte(0x1));
let hash_b = Hash::repeat_byte(0x2); let hash_b = CandidateHash(Hash::repeat_byte(0x2));
let validators: Vec<_> = [ let validators: Vec<_> = [
Sr25519Keyring::Alice, Sr25519Keyring::Alice,
@@ -263,7 +295,7 @@ mod tests {
let cap = NonZeroUsize::new(3).unwrap(); let cap = NonZeroUsize::new(3).unwrap();
let mut buf = ValidatorGroupsBuffer::with_capacity(cap); let mut buf = ValidatorGroupsBuffer::with_capacity(cap);
let hashes: Vec<_> = (0..5).map(Hash::repeat_byte).collect(); let hashes: Vec<_> = (0..5).map(|i| CandidateHash(Hash::repeat_byte(i))).collect();
let validators: Vec<_> = [ let validators: Vec<_> = [
Sr25519Keyring::Alice, Sr25519Keyring::Alice,
@@ -17,10 +17,12 @@
//! Error handling related code and Error/Result definitions. //! Error handling related code and Error/Result definitions.
use futures::channel::oneshot;
use polkadot_node_network_protocol::request_response::incoming; use polkadot_node_network_protocol::request_response::incoming;
use polkadot_node_primitives::UncheckedSignedFullStatement; use polkadot_node_primitives::UncheckedSignedFullStatement;
use polkadot_node_subsystem::errors::SubsystemError; use polkadot_node_subsystem::{errors::SubsystemError, RuntimeApiError};
use polkadot_node_subsystem_util::runtime; use polkadot_node_subsystem_util::{backing_implicit_view, runtime};
use crate::LOG_TARGET; use crate::LOG_TARGET;
@@ -44,10 +46,78 @@ pub enum Error {
#[error("Error while accessing runtime information")] #[error("Error while accessing runtime information")]
Runtime(#[from] runtime::Error), Runtime(#[from] runtime::Error),
#[error("Error while accessing Runtime API")]
RuntimeApi(#[from] RuntimeApiError),
#[error(transparent)]
ImplicitViewFetchError(backing_implicit_view::FetchError),
#[error("Response receiver for active validators request cancelled")]
CancelledActiveValidators(oneshot::Canceled),
#[error("Response receiver for validator groups request cancelled")]
CancelledValidatorGroups(oneshot::Canceled),
#[error("Response receiver for availability cores request cancelled")]
CancelledAvailabilityCores(oneshot::Canceled),
#[error("CollationSeconded contained statement with invalid signature")] #[error("CollationSeconded contained statement with invalid signature")]
InvalidStatementSignature(UncheckedSignedFullStatement), InvalidStatementSignature(UncheckedSignedFullStatement),
} }
/// An error happened on the validator side of the protocol when attempting
/// to start seconding a candidate.
#[derive(Debug, thiserror::Error)]
pub enum SecondingError {
#[error("Error while accessing Runtime API")]
RuntimeApi(#[from] RuntimeApiError),
#[error("Response receiver for persisted validation data request cancelled")]
CancelledRuntimePersistedValidationData(oneshot::Canceled),
#[error("Response receiver for prospective validation data request cancelled")]
CancelledProspectiveValidationData(oneshot::Canceled),
#[error("Persisted validation data is not available")]
PersistedValidationDataNotFound,
#[error("Persisted validation data hash doesn't match one in the candidate receipt.")]
PersistedValidationDataMismatch,
#[error("Candidate hash doesn't match the advertisement")]
CandidateHashMismatch,
#[error("Received duplicate collation from the peer")]
Duplicate,
}
impl SecondingError {
/// Returns true if an error indicates that a peer is malicious.
pub fn is_malicious(&self) -> bool {
use SecondingError::*;
matches!(self, PersistedValidationDataMismatch | CandidateHashMismatch | Duplicate)
}
}
/// A validator failed to request a collation due to an error.
#[derive(Debug, thiserror::Error)]
pub enum FetchError {
#[error("Collation was not previously advertised")]
NotAdvertised,
#[error("Peer is unknown")]
UnknownPeer,
#[error("Collation was already requested")]
AlreadyRequested,
#[error("Relay parent went out of view")]
RelayParentOutOfView,
#[error("Peer's protocol doesn't match the advertisement")]
ProtocolMismatch,
}
/// Utility for eating top level errors and log them. /// Utility for eating top level errors and log them.
/// ///
/// We basically always want to try and continue on error. This utility function is meant to /// We basically always want to try and continue on error. This utility function is meant to
@@ -32,7 +32,7 @@ use polkadot_node_subsystem_util::reputation::ReputationAggregator;
use sp_keystore::KeystorePtr; use sp_keystore::KeystorePtr;
use polkadot_node_network_protocol::{ use polkadot_node_network_protocol::{
request_response::{v1 as request_v1, IncomingRequestReceiver}, request_response::{v1 as request_v1, vstaging as protocol_vstaging, IncomingRequestReceiver},
PeerId, UnifiedReputationChange as Rep, PeerId, UnifiedReputationChange as Rep,
}; };
use polkadot_primitives::CollatorPair; use polkadot_primitives::CollatorPair;
@@ -76,12 +76,19 @@ pub enum ProtocolSide {
metrics: validator_side::Metrics, metrics: validator_side::Metrics,
}, },
/// Collators operate on a parachain. /// Collators operate on a parachain.
Collator( Collator {
PeerId, /// Local peer id.
CollatorPair, peer_id: PeerId,
IncomingRequestReceiver<request_v1::CollationFetchingRequest>, /// Parachain collator pair.
collator_side::Metrics, collator_pair: CollatorPair,
), /// Receiver for v1 collation fetching requests.
request_receiver_v1: IncomingRequestReceiver<request_v1::CollationFetchingRequest>,
/// Receiver for vstaging collation fetching requests.
request_receiver_vstaging:
IncomingRequestReceiver<protocol_vstaging::CollationFetchingRequest>,
/// Metrics.
metrics: collator_side::Metrics,
},
/// No protocol side, just disable it. /// No protocol side, just disable it.
None, None,
} }
@@ -110,10 +117,22 @@ impl<Context> CollatorProtocolSubsystem {
validator_side::run(ctx, keystore, eviction_policy, metrics) validator_side::run(ctx, keystore, eviction_policy, metrics)
.map_err(|e| SubsystemError::with_origin("collator-protocol", e)) .map_err(|e| SubsystemError::with_origin("collator-protocol", e))
.boxed(), .boxed(),
ProtocolSide::Collator(local_peer_id, collator_pair, req_receiver, metrics) => ProtocolSide::Collator {
collator_side::run(ctx, local_peer_id, collator_pair, req_receiver, metrics) peer_id,
.map_err(|e| SubsystemError::with_origin("collator-protocol", e)) collator_pair,
.boxed(), request_receiver_v1,
request_receiver_vstaging,
metrics,
} => collator_side::run(
ctx,
peer_id,
collator_pair,
request_receiver_v1,
request_receiver_vstaging,
metrics,
)
.map_err(|e| SubsystemError::with_origin("collator-protocol", e))
.boxed(),
ProtocolSide::None => return DummySubsystem.start(ctx), ProtocolSide::None => return DummySubsystem.start(ctx),
}; };
@@ -0,0 +1,366 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Primitives for tracking collations-related data.
//!
//! Usually a path of collations is as follows:
//! 1. First, collation must be advertised by collator.
//! 2. If the advertisement was accepted, it's queued for fetch (per relay parent).
//! 3. Once it's requested, the collation is said to be Pending.
//! 4. Pending collation becomes Fetched once received, we send it to backing for validation.
//! 5. If it turns to be invalid or async backing allows seconding another candidate, carry on
//! with the next advertisement, otherwise we're done with this relay parent.
//!
//! ┌──────────────────────────────────────────┐
//! └─▶Advertised ─▶ Pending ─▶ Fetched ─▶ Validated
use std::{collections::VecDeque, future::Future, pin::Pin, task::Poll};
use futures::{future::BoxFuture, FutureExt};
use polkadot_node_network_protocol::{
request_response::{outgoing::RequestError, v1 as request_v1, OutgoingResult},
PeerId,
};
use polkadot_node_primitives::PoV;
use polkadot_node_subsystem::jaeger;
use polkadot_node_subsystem_util::{
metrics::prometheus::prometheus::HistogramTimer, runtime::ProspectiveParachainsMode,
};
use polkadot_primitives::{
CandidateHash, CandidateReceipt, CollatorId, Hash, Id as ParaId, PersistedValidationData,
};
use tokio_util::sync::CancellationToken;
use crate::{error::SecondingError, LOG_TARGET};
/// Candidate supplied with a para head it's built on top of.
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct ProspectiveCandidate {
/// Candidate hash.
pub candidate_hash: CandidateHash,
/// Parent head-data hash as supplied in advertisement.
pub parent_head_data_hash: Hash,
}
impl ProspectiveCandidate {
pub fn candidate_hash(&self) -> CandidateHash {
self.candidate_hash
}
}
/// Identifier of a fetched collation.
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct FetchedCollation {
/// Candidate's relay parent.
pub relay_parent: Hash,
/// Parachain id.
pub para_id: ParaId,
/// Candidate hash.
pub candidate_hash: CandidateHash,
/// Id of the collator the collation was fetched from.
pub collator_id: CollatorId,
}
impl From<&CandidateReceipt<Hash>> for FetchedCollation {
fn from(receipt: &CandidateReceipt<Hash>) -> Self {
let descriptor = receipt.descriptor();
Self {
relay_parent: descriptor.relay_parent,
para_id: descriptor.para_id,
candidate_hash: receipt.hash(),
collator_id: descriptor.collator.clone(),
}
}
}
/// Identifier of a collation being requested.
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct PendingCollation {
/// Candidate's relay parent.
pub relay_parent: Hash,
/// Parachain id.
pub para_id: ParaId,
/// Peer that advertised this collation.
pub peer_id: PeerId,
/// Optional candidate hash and parent head-data hash if were
/// supplied in advertisement.
pub prospective_candidate: Option<ProspectiveCandidate>,
/// Hash of the candidate's commitments.
pub commitments_hash: Option<Hash>,
}
impl PendingCollation {
pub fn new(
relay_parent: Hash,
para_id: ParaId,
peer_id: &PeerId,
prospective_candidate: Option<ProspectiveCandidate>,
) -> Self {
Self {
relay_parent,
para_id,
peer_id: *peer_id,
prospective_candidate,
commitments_hash: None,
}
}
}
/// vstaging advertisement that was rejected by the backing
/// subsystem. Validator may fetch it later if its fragment
/// membership gets recognized before relay parent goes out of view.
#[derive(Debug, Clone)]
pub struct BlockedAdvertisement {
/// Peer that advertised the collation.
pub peer_id: PeerId,
/// Collator id.
pub collator_id: CollatorId,
/// The relay-parent of the candidate.
pub candidate_relay_parent: Hash,
/// Hash of the candidate.
pub candidate_hash: CandidateHash,
}
/// Performs a sanity check between advertised and fetched collations.
///
/// Since the persisted validation data is constructed using the advertised
/// parent head data hash, the latter doesn't require an additional check.
pub fn fetched_collation_sanity_check(
advertised: &PendingCollation,
fetched: &CandidateReceipt,
persisted_validation_data: &PersistedValidationData,
) -> Result<(), SecondingError> {
if persisted_validation_data.hash() != fetched.descriptor().persisted_validation_data_hash {
Err(SecondingError::PersistedValidationDataMismatch)
} else if advertised
.prospective_candidate
.map_or(false, |pc| pc.candidate_hash() != fetched.hash())
{
Err(SecondingError::CandidateHashMismatch)
} else {
Ok(())
}
}
/// Identifier for a requested collation and the respective collator that advertised it.
#[derive(Debug, Clone)]
pub struct CollationEvent {
/// Collator id.
pub collator_id: CollatorId,
/// The requested collation data.
pub pending_collation: PendingCollation,
}
/// Fetched collation data.
#[derive(Debug, Clone)]
pub struct PendingCollationFetch {
/// Collation identifier.
pub collation_event: CollationEvent,
/// Candidate receipt.
pub candidate_receipt: CandidateReceipt,
/// Proof of validity.
pub pov: PoV,
}
/// The status of the collations in [`CollationsPerRelayParent`].
#[derive(Debug, Clone, Copy)]
pub enum CollationStatus {
/// We are waiting for a collation to be advertised to us.
Waiting,
/// We are currently fetching a collation.
Fetching,
/// We are waiting that a collation is being validated.
WaitingOnValidation,
/// We have seconded a collation.
Seconded,
}
impl Default for CollationStatus {
fn default() -> Self {
Self::Waiting
}
}
impl CollationStatus {
/// Downgrades to `Waiting`, but only if `self != Seconded`.
fn back_to_waiting(&mut self, relay_parent_mode: ProspectiveParachainsMode) {
match self {
Self::Seconded =>
if relay_parent_mode.is_enabled() {
// With async backing enabled it's allowed to
// second more candidates.
*self = Self::Waiting
},
_ => *self = Self::Waiting,
}
}
}
/// Information about collations per relay parent.
#[derive(Default)]
pub struct Collations {
/// What is the current status in regards to a collation for this relay parent?
pub status: CollationStatus,
/// Collator we're fetching from, optionally which candidate was requested.
///
/// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME`
/// yet.
pub fetching_from: Option<(CollatorId, Option<CandidateHash>)>,
/// Collation that were advertised to us, but we did not yet fetch.
pub waiting_queue: VecDeque<(PendingCollation, CollatorId)>,
/// How many collations have been seconded.
pub seconded_count: usize,
}
impl Collations {
/// Note a seconded collation for a given para.
pub(super) fn note_seconded(&mut self) {
self.seconded_count += 1
}
/// Returns the next collation to fetch from the `waiting_queue`.
///
/// This will reset the status back to `Waiting` using [`CollationStatus::back_to_waiting`].
///
/// Returns `Some(_)` if there is any collation to fetch, the `status` is not `Seconded` and
/// the passed in `finished_one` is the currently `waiting_collation`.
pub(super) fn get_next_collation_to_fetch(
&mut self,
finished_one: &(CollatorId, Option<CandidateHash>),
relay_parent_mode: ProspectiveParachainsMode,
) -> Option<(PendingCollation, CollatorId)> {
// If finished one does not match waiting_collation, then we already dequeued another fetch
// to replace it.
if let Some((collator_id, maybe_candidate_hash)) = self.fetching_from.as_ref() {
// If a candidate hash was saved previously, `finished_one` must include this too.
if collator_id != &finished_one.0 &&
maybe_candidate_hash.map_or(true, |hash| Some(&hash) != finished_one.1.as_ref())
{
gum::trace!(
target: LOG_TARGET,
waiting_collation = ?self.fetching_from,
?finished_one,
"Not proceeding to the next collation - has already been done."
);
return None
}
}
self.status.back_to_waiting(relay_parent_mode);
match self.status {
// We don't need to fetch any other collation when we already have seconded one.
CollationStatus::Seconded => None,
CollationStatus::Waiting =>
if !self.is_seconded_limit_reached(relay_parent_mode) {
None
} else {
self.waiting_queue.pop_front()
},
CollationStatus::WaitingOnValidation | CollationStatus::Fetching =>
unreachable!("We have reset the status above!"),
}
}
/// Checks the limit of seconded candidates for a given para.
pub(super) fn is_seconded_limit_reached(
&self,
relay_parent_mode: ProspectiveParachainsMode,
) -> bool {
let seconded_limit =
if let ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } =
relay_parent_mode
{
max_candidate_depth + 1
} else {
1
};
self.seconded_count < seconded_limit
}
}
// Any error that can occur when awaiting a collation fetch response.
#[derive(Debug, thiserror::Error)]
pub(super) enum CollationFetchError {
#[error("Future was cancelled.")]
Cancelled,
#[error("{0}")]
Request(#[from] RequestError),
}
/// Future that concludes when the collator has responded to our collation fetch request
/// or the request was cancelled by the validator.
pub(super) struct CollationFetchRequest {
/// Info about the requested collation.
pub pending_collation: PendingCollation,
/// Collator id.
pub collator_id: CollatorId,
/// Responses from collator.
pub from_collator: BoxFuture<'static, OutgoingResult<request_v1::CollationFetchingResponse>>,
/// Handle used for checking if this request was cancelled.
pub cancellation_token: CancellationToken,
/// A jaeger span corresponding to the lifetime of the request.
pub span: Option<jaeger::Span>,
/// A metric histogram for the lifetime of the request
pub _lifetime_timer: Option<HistogramTimer>,
}
impl Future for CollationFetchRequest {
type Output = (
CollationEvent,
std::result::Result<request_v1::CollationFetchingResponse, CollationFetchError>,
);
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
// First check if this fetch request was cancelled.
let cancelled = match std::pin::pin!(self.cancellation_token.cancelled()).poll(cx) {
Poll::Ready(()) => true,
Poll::Pending => false,
};
if cancelled {
self.span.as_mut().map(|s| s.add_string_tag("success", "false"));
return Poll::Ready((
CollationEvent {
collator_id: self.collator_id.clone(),
pending_collation: self.pending_collation,
},
Err(CollationFetchError::Cancelled),
))
}
let res = self.from_collator.poll_unpin(cx).map(|res| {
(
CollationEvent {
collator_id: self.collator_id.clone(),
pending_collation: self.pending_collation,
},
res.map_err(CollationFetchError::Request),
)
});
match &res {
Poll::Ready((_, Ok(request_v1::CollationFetchingResponse::Collation(..)))) => {
self.span.as_mut().map(|s| s.add_string_tag("success", "true"));
},
Poll::Ready((_, Err(_))) => {
self.span.as_mut().map(|s| s.add_string_tag("success", "false"));
},
_ => {},
};
res
}
}
@@ -0,0 +1,142 @@
// Copyright 2017-2023 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use polkadot_node_subsystem_util::metrics::{self, prometheus};
#[derive(Clone, Default)]
pub struct Metrics(Option<MetricsInner>);
impl Metrics {
pub fn on_request(&self, succeeded: std::result::Result<(), ()>) {
if let Some(metrics) = &self.0 {
match succeeded {
Ok(()) => metrics.collation_requests.with_label_values(&["succeeded"]).inc(),
Err(()) => metrics.collation_requests.with_label_values(&["failed"]).inc(),
}
}
}
/// Provide a timer for `process_msg` which observes on drop.
pub fn time_process_msg(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| metrics.process_msg.start_timer())
}
/// Provide a timer for `handle_collation_request_result` which observes on drop.
pub fn time_handle_collation_request_result(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0
.as_ref()
.map(|metrics| metrics.handle_collation_request_result.start_timer())
}
/// Note the current number of collator peers.
pub fn note_collator_peer_count(&self, collator_peers: usize) {
self.0
.as_ref()
.map(|metrics| metrics.collator_peer_count.set(collator_peers as u64));
}
/// Provide a timer for `CollationFetchRequest` structure which observes on drop.
pub fn time_collation_request_duration(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| metrics.collation_request_duration.start_timer())
}
/// Provide a timer for `request_unblocked_collations` which observes on drop.
pub fn time_request_unblocked_collations(
&self,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0
.as_ref()
.map(|metrics| metrics.request_unblocked_collations.start_timer())
}
}
#[derive(Clone)]
struct MetricsInner {
collation_requests: prometheus::CounterVec<prometheus::U64>,
process_msg: prometheus::Histogram,
handle_collation_request_result: prometheus::Histogram,
collator_peer_count: prometheus::Gauge<prometheus::U64>,
collation_request_duration: prometheus::Histogram,
request_unblocked_collations: prometheus::Histogram,
}
impl metrics::Metrics for Metrics {
fn try_register(
registry: &prometheus::Registry,
) -> std::result::Result<Self, prometheus::PrometheusError> {
let metrics = MetricsInner {
collation_requests: prometheus::register(
prometheus::CounterVec::new(
prometheus::Opts::new(
"polkadot_parachain_collation_requests_total",
"Number of collations requested from Collators.",
),
&["success"],
)?,
registry,
)?,
process_msg: prometheus::register(
prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"polkadot_parachain_collator_protocol_validator_process_msg",
"Time spent within `collator_protocol_validator::process_msg`",
)
)?,
registry,
)?,
handle_collation_request_result: prometheus::register(
prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"polkadot_parachain_collator_protocol_validator_handle_collation_request_result",
"Time spent within `collator_protocol_validator::handle_collation_request_result`",
)
)?,
registry,
)?,
collator_peer_count: prometheus::register(
prometheus::Gauge::new(
"polkadot_parachain_collator_peer_count",
"Amount of collator peers connected",
)?,
registry,
)?,
collation_request_duration: prometheus::register(
prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"polkadot_parachain_collator_protocol_validator_collation_request_duration",
"Lifetime of the `CollationFetchRequest` structure",
).buckets(vec![0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.75, 0.9, 1.0, 1.2, 1.5, 1.75]),
)?,
registry,
)?,
request_unblocked_collations: prometheus::register(
prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"polkadot_parachain_collator_protocol_validator_request_unblocked_collations",
"Time spent within `collator_protocol_validator::request_unblocked_collations`",
)
)?,
registry,
)?,
};
Ok(Metrics(Some(metrics)))
}
}
File diff suppressed because it is too large Load Diff
@@ -19,8 +19,8 @@ use assert_matches::assert_matches;
use futures::{executor, future, Future}; use futures::{executor, future, Future};
use sp_core::{crypto::Pair, Encode}; use sp_core::{crypto::Pair, Encode};
use sp_keyring::Sr25519Keyring; use sp_keyring::Sr25519Keyring;
use sp_keystore::{testing::MemoryKeystore, Keystore}; use sp_keystore::Keystore;
use std::{iter, sync::Arc, task::Poll, time::Duration}; use std::{iter, sync::Arc, time::Duration};
use polkadot_node_network_protocol::{ use polkadot_node_network_protocol::{
our_view, our_view,
@@ -28,24 +28,39 @@ use polkadot_node_network_protocol::{
request_response::{Requests, ResponseSender}, request_response::{Requests, ResponseSender},
ObservedRole, ObservedRole,
}; };
use polkadot_node_primitives::BlockData; use polkadot_node_primitives::{BlockData, PoV};
use polkadot_node_subsystem::messages::{ use polkadot_node_subsystem::{
AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest, errors::RuntimeApiError,
messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest},
}; };
use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt};
use polkadot_primitives::{ use polkadot_primitives::{
CollatorPair, CoreState, GroupIndex, GroupRotationInfo, OccupiedCore, ScheduledCore, CandidateReceipt, CollatorPair, CoreState, GroupIndex, GroupRotationInfo, HeadData,
ValidatorId, ValidatorIndex, OccupiedCore, PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex,
}; };
use polkadot_primitives_test_helpers::{ use polkadot_primitives_test_helpers::{
dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash,
}; };
mod prospective_parachains;
const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500); const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500);
const DECLARE_TIMEOUT: Duration = Duration::from_millis(25); const DECLARE_TIMEOUT: Duration = Duration::from_millis(25);
const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10); const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10);
const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError =
RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" };
fn dummy_pvd() -> PersistedValidationData {
PersistedValidationData {
parent_head: HeadData(vec![7, 8, 9]),
relay_parent_number: 5,
max_pov_size: 1024,
relay_parent_storage_root: Default::default(),
}
}
#[derive(Clone)] #[derive(Clone)]
struct TestState { struct TestState {
chain_ids: Vec<ParaId>, chain_ids: Vec<ParaId>,
@@ -120,6 +135,7 @@ type VirtualOverseer = test_helpers::TestSubsystemContextHandle<CollatorProtocol
struct TestHarness { struct TestHarness {
virtual_overseer: VirtualOverseer, virtual_overseer: VirtualOverseer,
keystore: KeystorePtr,
} }
fn test_harness<T: Future<Output = VirtualOverseer>>( fn test_harness<T: Future<Output = VirtualOverseer>>(
@@ -136,17 +152,17 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone()); let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
let keystore = MemoryKeystore::new(); let keystore = Arc::new(sc_keystore::LocalKeystore::in_memory());
keystore Keystore::sr25519_generate_new(
.sr25519_generate_new( &*keystore,
polkadot_primitives::PARACHAIN_KEY_TYPE_ID, polkadot_primitives::PARACHAIN_KEY_TYPE_ID,
Some(&Sr25519Keyring::Alice.to_seed()), Some(&Sr25519Keyring::Alice.to_seed()),
) )
.unwrap(); .expect("Insert key into keystore");
let subsystem = run_inner( let subsystem = run_inner(
context, context,
Arc::new(keystore), keystore.clone(),
crate::CollatorEvictionPolicy { crate::CollatorEvictionPolicy {
inactive_collator: ACTIVITY_TIMEOUT, inactive_collator: ACTIVITY_TIMEOUT,
undeclared: DECLARE_TIMEOUT, undeclared: DECLARE_TIMEOUT,
@@ -156,7 +172,7 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
REPUTATION_CHANGE_TEST_INTERVAL, REPUTATION_CHANGE_TEST_INTERVAL,
); );
let test_fut = test(TestHarness { virtual_overseer }); let test_fut = test(TestHarness { virtual_overseer, keystore });
futures::pin_mut!(test_fut); futures::pin_mut!(test_fut);
futures::pin_mut!(subsystem); futures::pin_mut!(subsystem);
@@ -253,16 +269,53 @@ async fn assert_candidate_backing_second(
expected_relay_parent: Hash, expected_relay_parent: Hash,
expected_para_id: ParaId, expected_para_id: ParaId,
expected_pov: &PoV, expected_pov: &PoV,
mode: ProspectiveParachainsMode,
) -> CandidateReceipt { ) -> CandidateReceipt {
let pvd = dummy_pvd();
// Depending on relay parent mode pvd will be either requested
// from the Runtime API or Prospective Parachains.
let msg = overseer_recv(virtual_overseer).await;
match mode {
ProspectiveParachainsMode::Disabled => assert_matches!(
msg,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
hash,
RuntimeApiRequest::PersistedValidationData(para_id, assumption, tx),
)) => {
assert_eq!(expected_relay_parent, hash);
assert_eq!(expected_para_id, para_id);
assert_eq!(OccupiedCoreAssumption::Free, assumption);
tx.send(Ok(Some(pvd.clone()))).unwrap();
}
),
ProspectiveParachainsMode::Enabled { .. } => assert_matches!(
msg,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetProspectiveValidationData(request, tx),
) => {
assert_eq!(expected_relay_parent, request.candidate_relay_parent);
assert_eq!(expected_para_id, request.para_id);
tx.send(Some(pvd.clone())).unwrap();
}
),
}
assert_matches!( assert_matches!(
overseer_recv(virtual_overseer).await, overseer_recv(virtual_overseer).await,
AllMessages::CandidateBacking(CandidateBackingMessage::Second(relay_parent, candidate_receipt, incoming_pov) AllMessages::CandidateBacking(CandidateBackingMessage::Second(
) => { relay_parent,
assert_eq!(expected_relay_parent, relay_parent); candidate_receipt,
assert_eq!(expected_para_id, candidate_receipt.descriptor.para_id); received_pvd,
assert_eq!(*expected_pov, incoming_pov); incoming_pov,
candidate_receipt )) => {
}) assert_eq!(expected_relay_parent, relay_parent);
assert_eq!(expected_para_id, candidate_receipt.descriptor.para_id);
assert_eq!(*expected_pov, incoming_pov);
assert_eq!(pvd, received_pvd);
candidate_receipt
}
)
} }
/// Assert that a collator got disconnected. /// Assert that a collator got disconnected.
@@ -284,6 +337,7 @@ async fn assert_fetch_collation_request(
virtual_overseer: &mut VirtualOverseer, virtual_overseer: &mut VirtualOverseer,
relay_parent: Hash, relay_parent: Hash,
para_id: ParaId, para_id: ParaId,
candidate_hash: Option<CandidateHash>,
) -> ResponseSender { ) -> ResponseSender {
assert_matches!( assert_matches!(
overseer_recv(virtual_overseer).await, overseer_recv(virtual_overseer).await,
@@ -291,14 +345,26 @@ async fn assert_fetch_collation_request(
) => { ) => {
let req = reqs.into_iter().next() let req = reqs.into_iter().next()
.expect("There should be exactly one request"); .expect("There should be exactly one request");
match req { match candidate_hash {
Requests::CollationFetchingV1(req) => { None => assert_matches!(
let payload = req.payload; req,
assert_eq!(payload.relay_parent, relay_parent); Requests::CollationFetchingV1(req) => {
assert_eq!(payload.para_id, para_id); let payload = req.payload;
req.pending_response assert_eq!(payload.relay_parent, relay_parent);
} assert_eq!(payload.para_id, para_id);
_ => panic!("Unexpected request"), req.pending_response
}
),
Some(candidate_hash) => assert_matches!(
req,
Requests::CollationFetchingVStaging(req) => {
let payload = req.payload;
assert_eq!(payload.relay_parent, relay_parent);
assert_eq!(payload.para_id, para_id);
assert_eq!(payload.candidate_hash, candidate_hash);
req.pending_response
}
),
} }
}) })
} }
@@ -309,27 +375,38 @@ async fn connect_and_declare_collator(
peer: PeerId, peer: PeerId,
collator: CollatorPair, collator: CollatorPair,
para_id: ParaId, para_id: ParaId,
version: CollationVersion,
) { ) {
overseer_send( overseer_send(
virtual_overseer, virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected(
peer, peer,
ObservedRole::Full, ObservedRole::Full,
CollationVersion::V1.into(), version.into(),
None, None,
)), )),
) )
.await; .await;
overseer_send( let wire_message = match version {
virtual_overseer, CollationVersion::V1 => Versioned::V1(protocol_v1::CollatorProtocolMessage::Declare(
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage( collator.public(),
peer, para_id,
Versioned::V1(protocol_v1::CollatorProtocolMessage::Declare( collator.sign(&protocol_v1::declare_signature_payload(&peer)),
)),
CollationVersion::VStaging =>
Versioned::VStaging(protocol_vstaging::CollatorProtocolMessage::Declare(
collator.public(), collator.public(),
para_id, para_id,
collator.sign(&protocol_v1::declare_signature_payload(&peer)), collator.sign(&protocol_v1::declare_signature_payload(&peer)),
)), )),
};
overseer_send(
virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(
peer,
wire_message,
)), )),
) )
.await; .await;
@@ -340,24 +417,48 @@ async fn advertise_collation(
virtual_overseer: &mut VirtualOverseer, virtual_overseer: &mut VirtualOverseer,
peer: PeerId, peer: PeerId,
relay_parent: Hash, relay_parent: Hash,
candidate: Option<(CandidateHash, Hash)>, // Candidate hash + parent head data hash.
) { ) {
let wire_message = match candidate {
Some((candidate_hash, parent_head_data_hash)) =>
Versioned::VStaging(protocol_vstaging::CollatorProtocolMessage::AdvertiseCollation {
relay_parent,
candidate_hash,
parent_head_data_hash,
}),
None =>
Versioned::V1(protocol_v1::CollatorProtocolMessage::AdvertiseCollation(relay_parent)),
};
overseer_send( overseer_send(
virtual_overseer, virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage( CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(
peer, peer,
Versioned::V1(protocol_v1::CollatorProtocolMessage::AdvertiseCollation(relay_parent)), wire_message,
)), )),
) )
.await; .await;
} }
async fn assert_async_backing_params_request(virtual_overseer: &mut VirtualOverseer, hash: Hash) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
relay_parent,
RuntimeApiRequest::StagingAsyncBackingParams(tx)
)) => {
assert_eq!(relay_parent, hash);
tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap();
}
);
}
// As we receive a relevant advertisement act on it and issue a collation request. // As we receive a relevant advertisement act on it and issue a collation request.
#[test] #[test]
fn act_on_advertisement() { fn act_on_advertisement() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0; let pair = CollatorPair::generate().0;
gum::trace!("activating"); gum::trace!("activating");
@@ -370,6 +471,7 @@ fn act_on_advertisement() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -379,15 +481,74 @@ fn act_on_advertisement() {
peer_b, peer_b,
pair.clone(), pair.clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent).await; advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await;
assert_fetch_collation_request( assert_fetch_collation_request(
&mut virtual_overseer, &mut virtual_overseer,
test_state.relay_parent, test_state.relay_parent,
test_state.chain_ids[0], test_state.chain_ids[0],
None,
)
.await;
virtual_overseer
});
}
/// Tests that validator side works with vstaging network protocol
/// before async backing is enabled.
#[test]
fn act_on_advertisement_vstaging() {
let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0;
gum::trace!("activating");
overseer_send(
&mut virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
our_view![test_state.relay_parent],
)),
)
.await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random();
connect_and_declare_collator(
&mut virtual_overseer,
peer_b,
pair.clone(),
test_state.chain_ids[0],
CollationVersion::VStaging,
)
.await;
let candidate_hash = CandidateHash::default();
let parent_head_data_hash = Hash::zero();
// vstaging advertisement.
advertise_collation(
&mut virtual_overseer,
peer_b,
test_state.relay_parent,
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_fetch_collation_request(
&mut virtual_overseer,
test_state.relay_parent,
test_state.chain_ids[0],
Some(candidate_hash),
) )
.await; .await;
@@ -401,7 +562,7 @@ fn collator_reporting_works() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
overseer_send( overseer_send(
&mut virtual_overseer, &mut virtual_overseer,
@@ -411,6 +572,8 @@ fn collator_reporting_works() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -421,6 +584,7 @@ fn collator_reporting_works() {
peer_b, peer_b,
test_state.collators[0].clone(), test_state.collators[0].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
@@ -429,6 +593,7 @@ fn collator_reporting_works() {
peer_c, peer_c,
test_state.collators[1].clone(), test_state.collators[1].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
@@ -458,7 +623,7 @@ fn collator_authentication_verification_works() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -509,20 +674,24 @@ fn fetch_one_collation_at_a_time() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let second = Hash::random(); let second = Hash::random();
let our_view = our_view![test_state.relay_parent, second];
overseer_send( overseer_send(
&mut virtual_overseer, &mut virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
our_view![test_state.relay_parent, second], our_view.clone(),
)), )),
) )
.await; .await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; // Iter over view since the order may change due to sorted invariant.
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; for hash in our_view.iter() {
assert_async_backing_params_request(&mut virtual_overseer, *hash).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
}
let peer_b = PeerId::random(); let peer_b = PeerId::random();
let peer_c = PeerId::random(); let peer_c = PeerId::random();
@@ -532,6 +701,7 @@ fn fetch_one_collation_at_a_time() {
peer_b, peer_b,
test_state.collators[0].clone(), test_state.collators[0].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
@@ -540,16 +710,18 @@ fn fetch_one_collation_at_a_time() {
peer_c, peer_c,
test_state.collators[1].clone(), test_state.collators[1].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent).await; advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await;
advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent).await; advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await;
let response_channel = assert_fetch_collation_request( let response_channel = assert_fetch_collation_request(
&mut virtual_overseer, &mut virtual_overseer,
test_state.relay_parent, test_state.relay_parent,
test_state.chain_ids[0], test_state.chain_ids[0],
None,
) )
.await; .await;
@@ -563,10 +735,13 @@ fn fetch_one_collation_at_a_time() {
dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default()));
candidate_a.descriptor.para_id = test_state.chain_ids[0]; candidate_a.descriptor.para_id = test_state.chain_ids[0];
candidate_a.descriptor.relay_parent = test_state.relay_parent; candidate_a.descriptor.relay_parent = test_state.relay_parent;
candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
response_channel response_channel
.send(Ok( .send(Ok(request_v1::CollationFetchingResponse::Collation(
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode() candidate_a.clone(),
)) pov.clone(),
)
.encode()))
.expect("Sending response should succeed"); .expect("Sending response should succeed");
assert_candidate_backing_second( assert_candidate_backing_second(
@@ -574,6 +749,7 @@ fn fetch_one_collation_at_a_time() {
test_state.relay_parent, test_state.relay_parent,
test_state.chain_ids[0], test_state.chain_ids[0],
&pov, &pov,
ProspectiveParachainsMode::Disabled,
) )
.await; .await;
@@ -581,7 +757,7 @@ fn fetch_one_collation_at_a_time() {
test_helpers::Yield::new().await; test_helpers::Yield::new().await;
// Second collation is not requested since there's already seconded one. // Second collation is not requested since there's already seconded one.
assert_matches!(futures::poll!(virtual_overseer.recv().boxed()), Poll::Pending); assert_matches!(virtual_overseer.recv().now_or_never(), None);
virtual_overseer virtual_overseer
}) })
@@ -594,20 +770,24 @@ fn fetches_next_collation() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let second = Hash::random(); let second = Hash::random();
let our_view = our_view![test_state.relay_parent, second];
overseer_send( overseer_send(
&mut virtual_overseer, &mut virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
our_view![test_state.relay_parent, second], our_view.clone(),
)), )),
) )
.await; .await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; for hash in our_view.iter() {
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; assert_async_backing_params_request(&mut virtual_overseer, *hash).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
}
let peer_b = PeerId::random(); let peer_b = PeerId::random();
let peer_c = PeerId::random(); let peer_c = PeerId::random();
@@ -618,6 +798,7 @@ fn fetches_next_collation() {
peer_b, peer_b,
test_state.collators[2].clone(), test_state.collators[2].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
@@ -626,6 +807,7 @@ fn fetches_next_collation() {
peer_c, peer_c,
test_state.collators[3].clone(), test_state.collators[3].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
@@ -634,45 +816,64 @@ fn fetches_next_collation() {
peer_d, peer_d,
test_state.collators[4].clone(), test_state.collators[4].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
advertise_collation(&mut virtual_overseer, peer_b, second).await; advertise_collation(&mut virtual_overseer, peer_b, second, None).await;
advertise_collation(&mut virtual_overseer, peer_c, second).await; advertise_collation(&mut virtual_overseer, peer_c, second, None).await;
advertise_collation(&mut virtual_overseer, peer_d, second).await; advertise_collation(&mut virtual_overseer, peer_d, second, None).await;
// Dropping the response channel should lead to fetching the second collation. // Dropping the response channel should lead to fetching the second collation.
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0]) assert_fetch_collation_request(
.await; &mut virtual_overseer,
second,
test_state.chain_ids[0],
None,
)
.await;
let response_channel_non_exclusive = let response_channel_non_exclusive = assert_fetch_collation_request(
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0]) &mut virtual_overseer,
.await; second,
test_state.chain_ids[0],
None,
)
.await;
// Third collator should receive response after that timeout: // Third collator should receive response after that timeout:
Delay::new(MAX_UNSHARED_DOWNLOAD_TIME + Duration::from_millis(50)).await; Delay::new(MAX_UNSHARED_DOWNLOAD_TIME + Duration::from_millis(50)).await;
let response_channel = let response_channel = assert_fetch_collation_request(
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0]) &mut virtual_overseer,
.await; second,
test_state.chain_ids[0],
None,
)
.await;
let pov = PoV { block_data: BlockData(vec![1]) }; let pov = PoV { block_data: BlockData(vec![1]) };
let mut candidate_a = let mut candidate_a =
dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default()));
candidate_a.descriptor.para_id = test_state.chain_ids[0]; candidate_a.descriptor.para_id = test_state.chain_ids[0];
candidate_a.descriptor.relay_parent = second; candidate_a.descriptor.relay_parent = second;
candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
// First request finishes now: // First request finishes now:
response_channel_non_exclusive response_channel_non_exclusive
.send(Ok( .send(Ok(request_v1::CollationFetchingResponse::Collation(
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode() candidate_a.clone(),
)) pov.clone(),
)
.encode()))
.expect("Sending response should succeed"); .expect("Sending response should succeed");
response_channel response_channel
.send(Ok( .send(Ok(request_v1::CollationFetchingResponse::Collation(
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode() candidate_a.clone(),
)) pov.clone(),
)
.encode()))
.expect("Sending response should succeed"); .expect("Sending response should succeed");
assert_candidate_backing_second( assert_candidate_backing_second(
@@ -680,6 +881,7 @@ fn fetches_next_collation() {
second, second,
test_state.chain_ids[0], test_state.chain_ids[0],
&pov, &pov,
ProspectiveParachainsMode::Disabled,
) )
.await; .await;
@@ -692,7 +894,7 @@ fn reject_connection_to_next_group() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
overseer_send( overseer_send(
&mut virtual_overseer, &mut virtual_overseer,
@@ -702,6 +904,7 @@ fn reject_connection_to_next_group() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -711,6 +914,7 @@ fn reject_connection_to_next_group() {
peer_b, peer_b,
test_state.collators[0].clone(), test_state.collators[0].clone(),
test_state.chain_ids[1], // next, not current `para_id` test_state.chain_ids[1], // next, not current `para_id`
CollationVersion::V1,
) )
.await; .await;
@@ -737,20 +941,24 @@ fn fetch_next_collation_on_invalid_collation() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let second = Hash::random(); let second = Hash::random();
let our_view = our_view![test_state.relay_parent, second];
overseer_send( overseer_send(
&mut virtual_overseer, &mut virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
our_view![test_state.relay_parent, second], our_view.clone(),
)), )),
) )
.await; .await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; for hash in our_view.iter() {
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; assert_async_backing_params_request(&mut virtual_overseer, *hash).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
}
let peer_b = PeerId::random(); let peer_b = PeerId::random();
let peer_c = PeerId::random(); let peer_c = PeerId::random();
@@ -760,6 +968,7 @@ fn fetch_next_collation_on_invalid_collation() {
peer_b, peer_b,
test_state.collators[0].clone(), test_state.collators[0].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
@@ -768,16 +977,18 @@ fn fetch_next_collation_on_invalid_collation() {
peer_c, peer_c,
test_state.collators[1].clone(), test_state.collators[1].clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent).await; advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await;
advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent).await; advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await;
let response_channel = assert_fetch_collation_request( let response_channel = assert_fetch_collation_request(
&mut virtual_overseer, &mut virtual_overseer,
test_state.relay_parent, test_state.relay_parent,
test_state.chain_ids[0], test_state.chain_ids[0],
None,
) )
.await; .await;
@@ -786,10 +997,13 @@ fn fetch_next_collation_on_invalid_collation() {
dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default()));
candidate_a.descriptor.para_id = test_state.chain_ids[0]; candidate_a.descriptor.para_id = test_state.chain_ids[0];
candidate_a.descriptor.relay_parent = test_state.relay_parent; candidate_a.descriptor.relay_parent = test_state.relay_parent;
candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
response_channel response_channel
.send(Ok( .send(Ok(request_v1::CollationFetchingResponse::Collation(
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode() candidate_a.clone(),
)) pov.clone(),
)
.encode()))
.expect("Sending response should succeed"); .expect("Sending response should succeed");
let receipt = assert_candidate_backing_second( let receipt = assert_candidate_backing_second(
@@ -797,6 +1011,7 @@ fn fetch_next_collation_on_invalid_collation() {
test_state.relay_parent, test_state.relay_parent,
test_state.chain_ids[0], test_state.chain_ids[0],
&pov, &pov,
ProspectiveParachainsMode::Disabled,
) )
.await; .await;
@@ -822,6 +1037,7 @@ fn fetch_next_collation_on_invalid_collation() {
&mut virtual_overseer, &mut virtual_overseer,
test_state.relay_parent, test_state.relay_parent,
test_state.chain_ids[0], test_state.chain_ids[0],
None,
) )
.await; .await;
@@ -834,7 +1050,7 @@ fn inactive_disconnected() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0; let pair = CollatorPair::generate().0;
@@ -848,6 +1064,7 @@ fn inactive_disconnected() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, hash_a).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -857,14 +1074,16 @@ fn inactive_disconnected() {
peer_b, peer_b,
pair.clone(), pair.clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent).await; advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await;
assert_fetch_collation_request( assert_fetch_collation_request(
&mut virtual_overseer, &mut virtual_overseer,
test_state.relay_parent, test_state.relay_parent,
test_state.chain_ids[0], test_state.chain_ids[0],
None,
) )
.await; .await;
@@ -880,7 +1099,7 @@ fn activity_extends_life() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0; let pair = CollatorPair::generate().0;
@@ -888,18 +1107,20 @@ fn activity_extends_life() {
let hash_b = Hash::repeat_byte(1); let hash_b = Hash::repeat_byte(1);
let hash_c = Hash::repeat_byte(2); let hash_c = Hash::repeat_byte(2);
let our_view = our_view![hash_a, hash_b, hash_c];
overseer_send( overseer_send(
&mut virtual_overseer, &mut virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
our_view![hash_a, hash_b, hash_c], our_view.clone(),
)), )),
) )
.await; .await;
// 3 heads, 3 times. for hash in our_view.iter() {
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; assert_async_backing_params_request(&mut virtual_overseer, *hash).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; }
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -908,29 +1129,45 @@ fn activity_extends_life() {
peer_b, peer_b,
pair.clone(), pair.clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await;
advertise_collation(&mut virtual_overseer, peer_b, hash_a).await; advertise_collation(&mut virtual_overseer, peer_b, hash_a, None).await;
assert_fetch_collation_request(&mut virtual_overseer, hash_a, test_state.chain_ids[0]) assert_fetch_collation_request(
.await; &mut virtual_overseer,
hash_a,
test_state.chain_ids[0],
None,
)
.await;
Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await;
advertise_collation(&mut virtual_overseer, peer_b, hash_b).await; advertise_collation(&mut virtual_overseer, peer_b, hash_b, None).await;
assert_fetch_collation_request(&mut virtual_overseer, hash_b, test_state.chain_ids[0]) assert_fetch_collation_request(
.await; &mut virtual_overseer,
hash_b,
test_state.chain_ids[0],
None,
)
.await;
Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await; Delay::new(ACTIVITY_TIMEOUT * 2 / 3).await;
advertise_collation(&mut virtual_overseer, peer_b, hash_c).await; advertise_collation(&mut virtual_overseer, peer_b, hash_c, None).await;
assert_fetch_collation_request(&mut virtual_overseer, hash_c, test_state.chain_ids[0]) assert_fetch_collation_request(
.await; &mut virtual_overseer,
hash_c,
test_state.chain_ids[0],
None,
)
.await;
Delay::new(ACTIVITY_TIMEOUT * 3 / 2).await; Delay::new(ACTIVITY_TIMEOUT * 3 / 2).await;
@@ -945,7 +1182,7 @@ fn disconnect_if_no_declare() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
overseer_send( overseer_send(
&mut virtual_overseer, &mut virtual_overseer,
@@ -955,6 +1192,7 @@ fn disconnect_if_no_declare() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -981,7 +1219,7 @@ fn disconnect_if_wrong_declare() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0; let pair = CollatorPair::generate().0;
@@ -993,6 +1231,7 @@ fn disconnect_if_wrong_declare() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -1042,7 +1281,7 @@ fn delay_reputation_change() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| false), |test_harness| async move { test_harness(ReputationAggregator::new(|_| false), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0; let pair = CollatorPair::generate().0;
@@ -1054,6 +1293,7 @@ fn delay_reputation_change() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -1127,7 +1367,7 @@ fn view_change_clears_old_collators() {
let mut test_state = TestState::default(); let mut test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0; let pair = CollatorPair::generate().0;
@@ -1139,6 +1379,7 @@ fn view_change_clears_old_collators() {
) )
.await; .await;
assert_async_backing_params_request(&mut virtual_overseer, test_state.relay_parent).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
let peer_b = PeerId::random(); let peer_b = PeerId::random();
@@ -1148,6 +1389,7 @@ fn view_change_clears_old_collators() {
peer_b, peer_b,
pair.clone(), pair.clone(),
test_state.chain_ids[0], test_state.chain_ids[0],
CollationVersion::V1,
) )
.await; .await;
@@ -1162,6 +1404,7 @@ fn view_change_clears_old_collators() {
.await; .await;
test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation();
assert_async_backing_params_request(&mut virtual_overseer, hash_b).await;
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await; respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
assert_collator_disconnect(&mut virtual_overseer, peer_b).await; assert_collator_disconnect(&mut virtual_overseer, peer_b).await;
@@ -0,0 +1,988 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Tests for the validator side with enabled prospective parachains.
use super::*;
use polkadot_node_subsystem::messages::ChainApiMessage;
use polkadot_primitives::{
vstaging as vstaging_primitives, BlockNumber, CandidateCommitments, CommittedCandidateReceipt,
Header, SigningContext, ValidatorId,
};
const ASYNC_BACKING_PARAMETERS: vstaging_primitives::AsyncBackingParams =
vstaging_primitives::AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 };
fn get_parent_hash(hash: Hash) -> Hash {
Hash::from_low_u64_be(hash.to_low_u64_be() + 1)
}
async fn assert_assign_incoming(
virtual_overseer: &mut VirtualOverseer,
test_state: &TestState,
hash: Hash,
number: BlockNumber,
next_msg: &mut Option<AllMessages>,
) {
let msg = match next_msg.take() {
Some(msg) => msg,
None => overseer_recv(virtual_overseer).await,
};
assert_matches!(
msg,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx))
) if parent == hash => {
tx.send(Ok(test_state.validator_public.clone())).unwrap();
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx))
) if parent == hash => {
let validator_groups = test_state.validator_groups.clone();
let mut group_rotation_info = test_state.group_rotation_info.clone();
group_rotation_info.now = number;
tx.send(Ok((validator_groups, group_rotation_info))).unwrap();
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx))
) if parent == hash => {
tx.send(Ok(test_state.cores.clone())).unwrap();
}
);
}
/// Handle a view update.
async fn update_view(
virtual_overseer: &mut VirtualOverseer,
test_state: &TestState,
new_view: Vec<(Hash, u32)>, // Hash and block number.
activated: u8, // How many new heads does this update contain?
) -> Option<AllMessages> {
let new_view: HashMap<Hash, u32> = HashMap::from_iter(new_view);
let our_view =
OurView::new(new_view.keys().map(|hash| (*hash, Arc::new(jaeger::Span::Disabled))), 0);
overseer_send(
virtual_overseer,
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(our_view)),
)
.await;
let mut next_overseer_message = None;
for _ in 0..activated {
let (leaf_hash, leaf_number) = assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
parent,
RuntimeApiRequest::StagingAsyncBackingParams(tx),
)) => {
tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap();
(parent, new_view.get(&parent).copied().expect("Unknown parent requested"))
}
);
assert_assign_incoming(
virtual_overseer,
test_state,
leaf_hash,
leaf_number,
&mut next_overseer_message,
)
.await;
let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetMinimumRelayParents(parent, tx),
) if parent == leaf_hash => {
tx.send(test_state.chain_ids.iter().map(|para_id| (*para_id, min_number)).collect()).unwrap();
}
);
let ancestry_len = leaf_number + 1 - min_number;
let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h)))
.take(ancestry_len as usize);
let ancestry_numbers = (min_number..=leaf_number).rev();
let ancestry_iter = ancestry_hashes.clone().zip(ancestry_numbers).peekable();
// How many blocks were actually requested.
let mut requested_len: usize = 0;
{
let mut ancestry_iter = ancestry_iter.clone();
while let Some((hash, number)) = ancestry_iter.next() {
// May be `None` for the last element.
let parent_hash =
ancestry_iter.peek().map(|(h, _)| *h).unwrap_or_else(|| get_parent_hash(hash));
let msg = match next_overseer_message.take() {
Some(msg) => msg,
None => overseer_recv(virtual_overseer).await,
};
if !matches!(&msg, AllMessages::ChainApi(ChainApiMessage::BlockHeader(..))) {
// Ancestry has already been cached for this leaf.
next_overseer_message.replace(msg);
break
}
assert_matches!(
msg,
AllMessages::ChainApi(ChainApiMessage::BlockHeader(.., tx)) => {
let header = Header {
parent_hash,
number,
state_root: Hash::zero(),
extrinsics_root: Hash::zero(),
digest: Default::default(),
};
tx.send(Ok(Some(header))).unwrap();
}
);
requested_len += 1;
}
}
// Skip the leaf.
for (hash, number) in ancestry_iter.skip(1).take(requested_len.saturating_sub(1)) {
assert_assign_incoming(
virtual_overseer,
test_state,
hash,
number,
&mut next_overseer_message,
)
.await;
}
}
next_overseer_message
}
async fn send_seconded_statement(
virtual_overseer: &mut VirtualOverseer,
keystore: KeystorePtr,
candidate: &CommittedCandidateReceipt,
) {
let signing_context = SigningContext { session_index: 0, parent_hash: Hash::zero() };
let stmt = SignedFullStatement::sign(
&keystore,
Statement::Seconded(candidate.clone()),
&signing_context,
ValidatorIndex(0),
&ValidatorId::from(Sr25519Keyring::Alice.public()),
)
.ok()
.flatten()
.expect("should be signed");
overseer_send(
virtual_overseer,
CollatorProtocolMessage::Seconded(candidate.descriptor.relay_parent, stmt),
)
.await;
}
async fn assert_collation_seconded(
virtual_overseer: &mut VirtualOverseer,
relay_parent: Hash,
peer_id: PeerId,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(
ReportPeerMessage::Single(peer, rep)
)) => {
assert_eq!(peer_id, peer);
assert_eq!(rep.value, BENEFIT_NOTIFY_GOOD.cost_or_benefit());
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendCollationMessage(
peers,
Versioned::VStaging(protocol_vstaging::CollationProtocol::CollatorProtocol(
protocol_vstaging::CollatorProtocolMessage::CollationSeconded(
_relay_parent,
..,
),
)),
)) => {
assert_eq!(peers, vec![peer_id]);
assert_eq!(relay_parent, _relay_parent);
}
);
}
#[test]
fn v1_advertisement_rejected() {
let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair_a = CollatorPair::generate().0;
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 0;
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let peer_a = PeerId::random();
// Accept both collators from the implicit view.
connect_and_declare_collator(
&mut virtual_overseer,
peer_a,
pair_a.clone(),
test_state.chain_ids[0],
CollationVersion::V1,
)
.await;
advertise_collation(&mut virtual_overseer, peer_a, head_b, None).await;
// Not reported.
test_helpers::Yield::new().await;
assert_matches!(virtual_overseer.recv().now_or_never(), None);
virtual_overseer
});
}
#[test]
fn accept_advertisements_from_implicit_view() {
let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair_a = CollatorPair::generate().0;
let pair_b = CollatorPair::generate().0;
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 2;
let head_c = get_parent_hash(head_b);
// Grandparent of head `b`.
// Group rotation frequency is 1 by default, at `d` we're assigned
// to the first para.
let head_d = get_parent_hash(head_c);
// Activated leaf is `b`, but the collation will be based on `c`.
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
// Accept both collators from the implicit view.
connect_and_declare_collator(
&mut virtual_overseer,
peer_a,
pair_a.clone(),
test_state.chain_ids[0],
CollationVersion::VStaging,
)
.await;
connect_and_declare_collator(
&mut virtual_overseer,
peer_b,
pair_b.clone(),
test_state.chain_ids[1],
CollationVersion::VStaging,
)
.await;
let candidate_hash = CandidateHash::default();
let parent_head_data_hash = Hash::zero();
advertise_collation(
&mut virtual_overseer,
peer_b,
head_c,
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[1]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(true).expect("receiving side should be alive");
}
);
assert_fetch_collation_request(
&mut virtual_overseer,
head_c,
test_state.chain_ids[1],
Some(candidate_hash),
)
.await;
// Advertise with different para.
advertise_collation(
&mut virtual_overseer,
peer_a,
head_d, // Note different relay parent.
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(true).expect("receiving side should be alive");
}
);
assert_fetch_collation_request(
&mut virtual_overseer,
head_d,
test_state.chain_ids[0],
Some(candidate_hash),
)
.await;
virtual_overseer
});
}
#[test]
fn second_multiple_candidates_per_relay_parent() {
let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, keystore } = test_harness;
let pair = CollatorPair::generate().0;
// Grandparent of head `a`.
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 2;
// Grandparent of head `b`.
// Group rotation frequency is 1 by default, at `c` we're assigned
// to the first para.
let head_c = Hash::from_low_u64_be(130);
// Activated leaf is `b`, but the collation will be based on `c`.
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let peer_a = PeerId::random();
connect_and_declare_collator(
&mut virtual_overseer,
peer_a,
pair.clone(),
test_state.chain_ids[0],
CollationVersion::VStaging,
)
.await;
for i in 0..(ASYNC_BACKING_PARAMETERS.max_candidate_depth + 1) {
let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default()));
candidate.descriptor.para_id = test_state.chain_ids[0];
candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
let commitments = CandidateCommitments {
head_data: HeadData(vec![i as u8]),
horizontal_messages: Default::default(),
upward_messages: Default::default(),
new_validation_code: None,
processed_downward_messages: 0,
hrmp_watermark: 0,
};
candidate.commitments_hash = commitments.hash();
let candidate_hash = candidate.hash();
let parent_head_data_hash = Hash::zero();
advertise_collation(
&mut virtual_overseer,
peer_a,
head_c,
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(true).expect("receiving side should be alive");
}
);
let response_channel = assert_fetch_collation_request(
&mut virtual_overseer,
head_c,
test_state.chain_ids[0],
Some(candidate_hash),
)
.await;
let pov = PoV { block_data: BlockData(vec![1]) };
response_channel
.send(Ok(request_vstaging::CollationFetchingResponse::Collation(
candidate.clone(),
pov.clone(),
)
.encode()))
.expect("Sending response should succeed");
assert_candidate_backing_second(
&mut virtual_overseer,
head_c,
test_state.chain_ids[0],
&pov,
ProspectiveParachainsMode::Enabled {
max_candidate_depth: ASYNC_BACKING_PARAMETERS.max_candidate_depth as _,
allowed_ancestry_len: ASYNC_BACKING_PARAMETERS.allowed_ancestry_len as _,
},
)
.await;
let candidate =
CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments };
send_seconded_statement(&mut virtual_overseer, keystore.clone(), &candidate).await;
assert_collation_seconded(&mut virtual_overseer, head_c, peer_a).await;
}
// No more advertisements can be made for this relay parent.
let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA));
advertise_collation(
&mut virtual_overseer,
peer_a,
head_c,
Some((candidate_hash, Hash::zero())),
)
.await;
// Reported because reached the limit of advertisements per relay parent.
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, rep)),
) => {
assert_eq!(peer_a, peer_id);
assert_eq!(rep.value, COST_UNEXPECTED_MESSAGE.cost_or_benefit());
}
);
// By different peer too (not reported).
let pair_b = CollatorPair::generate().0;
let peer_b = PeerId::random();
connect_and_declare_collator(
&mut virtual_overseer,
peer_b,
pair_b.clone(),
test_state.chain_ids[0],
CollationVersion::VStaging,
)
.await;
let candidate_hash = CandidateHash(Hash::repeat_byte(0xFF));
advertise_collation(
&mut virtual_overseer,
peer_b,
head_c,
Some((candidate_hash, Hash::zero())),
)
.await;
test_helpers::Yield::new().await;
assert_matches!(virtual_overseer.recv().now_or_never(), None);
virtual_overseer
});
}
#[test]
fn fetched_collation_sanity_check() {
let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair = CollatorPair::generate().0;
// Grandparent of head `a`.
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 2;
// Grandparent of head `b`.
// Group rotation frequency is 1 by default, at `c` we're assigned
// to the first para.
let head_c = Hash::from_low_u64_be(130);
// Activated leaf is `b`, but the collation will be based on `c`.
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let peer_a = PeerId::random();
connect_and_declare_collator(
&mut virtual_overseer,
peer_a,
pair.clone(),
test_state.chain_ids[0],
CollationVersion::VStaging,
)
.await;
let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default()));
candidate.descriptor.para_id = test_state.chain_ids[0];
let commitments = CandidateCommitments {
head_data: HeadData(vec![1, 2, 3]),
horizontal_messages: Default::default(),
upward_messages: Default::default(),
new_validation_code: None,
processed_downward_messages: 0,
hrmp_watermark: 0,
};
candidate.commitments_hash = commitments.hash();
let candidate_hash = CandidateHash(Hash::zero());
let parent_head_data_hash = Hash::zero();
advertise_collation(
&mut virtual_overseer,
peer_a,
head_c,
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(true).expect("receiving side should be alive");
}
);
let response_channel = assert_fetch_collation_request(
&mut virtual_overseer,
head_c,
test_state.chain_ids[0],
Some(candidate_hash),
)
.await;
let pov = PoV { block_data: BlockData(vec![1]) };
response_channel
.send(Ok(request_vstaging::CollationFetchingResponse::Collation(
candidate.clone(),
pov.clone(),
)
.encode()))
.expect("Sending response should succeed");
// PVD request.
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetProspectiveValidationData(request, tx),
) => {
assert_eq!(head_c, request.candidate_relay_parent);
assert_eq!(test_state.chain_ids[0], request.para_id);
tx.send(Some(dummy_pvd())).unwrap();
}
);
// Reported malicious.
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, rep)),
) => {
assert_eq!(peer_a, peer_id);
assert_eq!(rep.value, COST_REPORT_BAD.cost_or_benefit());
}
);
virtual_overseer
});
}
#[test]
fn advertisement_spam_protection() {
let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair_a = CollatorPair::generate().0;
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 2;
let head_c = get_parent_hash(head_b);
// Activated leaf is `b`, but the collation will be based on `c`.
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let peer_a = PeerId::random();
connect_and_declare_collator(
&mut virtual_overseer,
peer_a,
pair_a.clone(),
test_state.chain_ids[1],
CollationVersion::VStaging,
)
.await;
let candidate_hash = CandidateHash::default();
let parent_head_data_hash = Hash::zero();
advertise_collation(
&mut virtual_overseer,
peer_a,
head_c,
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[1]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
// Reject it.
tx.send(false).expect("receiving side should be alive");
}
);
// Send the same advertisement again.
advertise_collation(
&mut virtual_overseer,
peer_a,
head_c,
Some((candidate_hash, parent_head_data_hash)),
)
.await;
// Reported.
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, rep)),
) => {
assert_eq!(peer_a, peer_id);
assert_eq!(rep.value, COST_UNEXPECTED_MESSAGE.cost_or_benefit());
}
);
virtual_overseer
});
}
#[test]
fn backed_candidate_unblocks_advertisements() {
let test_state = TestState::default();
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
let pair_a = CollatorPair::generate().0;
let pair_b = CollatorPair::generate().0;
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 2;
let head_c = get_parent_hash(head_b);
// Grandparent of head `b`.
// Group rotation frequency is 1 by default, at `d` we're assigned
// to the first para.
let head_d = get_parent_hash(head_c);
// Activated leaf is `b`, but the collation will be based on `c`.
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
// Accept both collators from the implicit view.
connect_and_declare_collator(
&mut virtual_overseer,
peer_a,
pair_a.clone(),
test_state.chain_ids[0],
CollationVersion::VStaging,
)
.await;
connect_and_declare_collator(
&mut virtual_overseer,
peer_b,
pair_b.clone(),
test_state.chain_ids[1],
CollationVersion::VStaging,
)
.await;
let candidate_hash = CandidateHash::default();
let parent_head_data_hash = Hash::zero();
advertise_collation(
&mut virtual_overseer,
peer_b,
head_c,
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[1]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
// Reject it.
tx.send(false).expect("receiving side should be alive");
}
);
// Advertise with different para.
advertise_collation(
&mut virtual_overseer,
peer_a,
head_d, // Note different relay parent.
Some((candidate_hash, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(false).expect("receiving side should be alive");
}
);
overseer_send(
&mut virtual_overseer,
CollatorProtocolMessage::Backed {
para_id: test_state.chain_ids[0],
para_head: parent_head_data_hash,
},
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidate_hash);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(true).expect("receiving side should be alive");
}
);
assert_fetch_collation_request(
&mut virtual_overseer,
head_d,
test_state.chain_ids[0],
Some(candidate_hash),
)
.await;
virtual_overseer
});
}
#[test]
fn active_leave_unblocks_advertisements() {
let mut test_state = TestState::default();
test_state.group_rotation_info.group_rotation_frequency = 100;
test_harness(ReputationAggregator::new(|_| true), |test_harness| async move {
let TestHarness { mut virtual_overseer, .. } = test_harness;
let head_b = Hash::from_low_u64_be(128);
let head_b_num: u32 = 0;
update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await;
let peers: Vec<CollatorPair> = (0..3).map(|_| CollatorPair::generate().0).collect();
let peer_ids: Vec<PeerId> = (0..3).map(|_| PeerId::random()).collect();
let candidates: Vec<CandidateHash> =
(0u8..3).map(|i| CandidateHash(Hash::repeat_byte(i))).collect();
for (peer, peer_id) in peers.iter().zip(&peer_ids) {
connect_and_declare_collator(
&mut virtual_overseer,
*peer_id,
peer.clone(),
test_state.chain_ids[0],
CollationVersion::VStaging,
)
.await;
}
let parent_head_data_hash = Hash::zero();
for (peer, candidate) in peer_ids.iter().zip(&candidates).take(2) {
advertise_collation(
&mut virtual_overseer,
*peer,
head_b,
Some((*candidate, parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, *candidate);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
// Send false.
tx.send(false).expect("receiving side should be alive");
}
);
}
let head_c = Hash::from_low_u64_be(127);
let head_c_num: u32 = 1;
let next_overseer_message =
update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)], 1)
.await
.expect("should've sent request to backing");
// Unblock first request.
assert_matches!(
next_overseer_message,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidates[0]);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(true).expect("receiving side should be alive");
}
);
assert_fetch_collation_request(
&mut virtual_overseer,
head_b,
test_state.chain_ids[0],
Some(candidates[0]),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidates[1]);
assert_eq!(request.candidate_para_id, test_state.chain_ids[0]);
assert_eq!(request.parent_head_data_hash, parent_head_data_hash);
tx.send(false).expect("receiving side should be alive");
}
);
// Collation request was discarded.
test_helpers::Yield::new().await;
assert_matches!(virtual_overseer.recv().now_or_never(), None);
advertise_collation(
&mut virtual_overseer,
peer_ids[2],
head_c,
Some((candidates[2], parent_head_data_hash)),
)
.await;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidates[2]);
tx.send(false).expect("receiving side should be alive");
}
);
let head_d = Hash::from_low_u64_be(126);
let head_d_num: u32 = 2;
let next_overseer_message =
update_view(&mut virtual_overseer, &test_state, vec![(head_d, head_d_num)], 1)
.await
.expect("should've sent request to backing");
// Reject 2, accept 3.
assert_matches!(
next_overseer_message,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidates[1]);
tx.send(false).expect("receiving side should be alive");
}
);
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::CandidateBacking(
CandidateBackingMessage::CanSecond(request, tx),
) => {
assert_eq!(request.candidate_hash, candidates[2]);
tx.send(true).expect("receiving side should be alive");
}
);
assert_fetch_collation_request(
&mut virtual_overseer,
head_c,
test_state.chain_ids[0],
Some(candidates[2]),
)
.await;
virtual_overseer
});
}
@@ -448,8 +448,12 @@ where
NetworkBridgeEvent::OurViewChange(_) => {}, NetworkBridgeEvent::OurViewChange(_) => {},
NetworkBridgeEvent::PeerViewChange(_, _) => {}, NetworkBridgeEvent::PeerViewChange(_, _) => {},
NetworkBridgeEvent::NewGossipTopology { .. } => {}, NetworkBridgeEvent::NewGossipTopology { .. } => {},
NetworkBridgeEvent::PeerMessage(_, Versioned::V1(v)) => { NetworkBridgeEvent::PeerMessage(_, message) => {
match v {}; // match void -> LLVM unreachable
match message {
Versioned::V1(m) => match m {},
Versioned::VStaging(m) => match m {},
}
}, },
} }
} }
@@ -23,6 +23,10 @@ fatality = "0.0.6"
rand = "0.8" rand = "0.8"
derive_more = "0.99" derive_more = "0.99"
gum = { package = "tracing-gum", path = "../../gum" } gum = { package = "tracing-gum", path = "../../gum" }
bitvec = "1"
[dev-dependencies] [dev-dependencies]
rand_chacha = "0.3.1" rand_chacha = "0.3.1"
[features]
network-protocol-staging = []
+303 -15
View File
@@ -253,22 +253,26 @@ impl View {
/// A protocol-versioned type. /// A protocol-versioned type.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Versioned<V1> { pub enum Versioned<V1, VStaging> {
/// V1 type. /// V1 type.
V1(V1), V1(V1),
/// VStaging type.
VStaging(VStaging),
} }
impl<V1: Clone> Versioned<&'_ V1> { impl<V1: Clone, VStaging: Clone> Versioned<&'_ V1, &'_ VStaging> {
/// Convert to a fully-owned version of the message. /// Convert to a fully-owned version of the message.
pub fn clone_inner(&self) -> Versioned<V1> { pub fn clone_inner(&self) -> Versioned<V1, VStaging> {
match *self { match *self {
Versioned::V1(inner) => Versioned::V1(inner.clone()), Versioned::V1(inner) => Versioned::V1(inner.clone()),
Versioned::VStaging(inner) => Versioned::VStaging(inner.clone()),
} }
} }
} }
/// All supported versions of the validation protocol message. /// All supported versions of the validation protocol message.
pub type VersionedValidationProtocol = Versioned<v1::ValidationProtocol>; pub type VersionedValidationProtocol =
Versioned<v1::ValidationProtocol, vstaging::ValidationProtocol>;
impl From<v1::ValidationProtocol> for VersionedValidationProtocol { impl From<v1::ValidationProtocol> for VersionedValidationProtocol {
fn from(v1: v1::ValidationProtocol) -> Self { fn from(v1: v1::ValidationProtocol) -> Self {
@@ -276,8 +280,14 @@ impl From<v1::ValidationProtocol> for VersionedValidationProtocol {
} }
} }
impl From<vstaging::ValidationProtocol> for VersionedValidationProtocol {
fn from(vstaging: vstaging::ValidationProtocol) -> Self {
VersionedValidationProtocol::VStaging(vstaging)
}
}
/// All supported versions of the collation protocol message. /// All supported versions of the collation protocol message.
pub type VersionedCollationProtocol = Versioned<v1::CollationProtocol>; pub type VersionedCollationProtocol = Versioned<v1::CollationProtocol, vstaging::CollationProtocol>;
impl From<v1::CollationProtocol> for VersionedCollationProtocol { impl From<v1::CollationProtocol> for VersionedCollationProtocol {
fn from(v1: v1::CollationProtocol) -> Self { fn from(v1: v1::CollationProtocol) -> Self {
@@ -285,12 +295,19 @@ impl From<v1::CollationProtocol> for VersionedCollationProtocol {
} }
} }
impl From<vstaging::CollationProtocol> for VersionedCollationProtocol {
fn from(vstaging: vstaging::CollationProtocol) -> Self {
VersionedCollationProtocol::VStaging(vstaging)
}
}
macro_rules! impl_versioned_full_protocol_from { macro_rules! impl_versioned_full_protocol_from {
($from:ty, $out:ty, $variant:ident) => { ($from:ty, $out:ty, $variant:ident) => {
impl From<$from> for $out { impl From<$from> for $out {
fn from(versioned_from: $from) -> $out { fn from(versioned_from: $from) -> $out {
match versioned_from { match versioned_from {
Versioned::V1(x) => Versioned::V1(x.into()), Versioned::V1(x) => Versioned::V1(x.into()),
Versioned::VStaging(x) => Versioned::VStaging(x.into()),
} }
} }
} }
@@ -300,7 +317,12 @@ macro_rules! impl_versioned_full_protocol_from {
/// Implement `TryFrom` for one versioned enum variant into the inner type. /// Implement `TryFrom` for one versioned enum variant into the inner type.
/// `$m_ty::$variant(inner) -> Ok(inner)` /// `$m_ty::$variant(inner) -> Ok(inner)`
macro_rules! impl_versioned_try_from { macro_rules! impl_versioned_try_from {
($from:ty, $out:ty, $v1_pat:pat => $v1_out:expr) => { (
$from:ty,
$out:ty,
$v1_pat:pat => $v1_out:expr,
$vstaging_pat:pat => $vstaging_out:expr
) => {
impl TryFrom<$from> for $out { impl TryFrom<$from> for $out {
type Error = crate::WrongVariant; type Error = crate::WrongVariant;
@@ -308,6 +330,7 @@ macro_rules! impl_versioned_try_from {
#[allow(unreachable_patterns)] // when there is only one variant #[allow(unreachable_patterns)] // when there is only one variant
match x { match x {
Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out)), Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out)),
Versioned::VStaging($vstaging_pat) => Ok(Versioned::VStaging($vstaging_out)),
_ => Err(crate::WrongVariant), _ => Err(crate::WrongVariant),
} }
} }
@@ -320,6 +343,8 @@ macro_rules! impl_versioned_try_from {
#[allow(unreachable_patterns)] // when there is only one variant #[allow(unreachable_patterns)] // when there is only one variant
match x { match x {
Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out.clone())), Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out.clone())),
Versioned::VStaging($vstaging_pat) =>
Ok(Versioned::VStaging($vstaging_out.clone())),
_ => Err(crate::WrongVariant), _ => Err(crate::WrongVariant),
} }
} }
@@ -328,7 +353,8 @@ macro_rules! impl_versioned_try_from {
} }
/// Version-annotated messages used by the bitfield distribution subsystem. /// Version-annotated messages used by the bitfield distribution subsystem.
pub type BitfieldDistributionMessage = Versioned<v1::BitfieldDistributionMessage>; pub type BitfieldDistributionMessage =
Versioned<v1::BitfieldDistributionMessage, vstaging::BitfieldDistributionMessage>;
impl_versioned_full_protocol_from!( impl_versioned_full_protocol_from!(
BitfieldDistributionMessage, BitfieldDistributionMessage,
VersionedValidationProtocol, VersionedValidationProtocol,
@@ -337,11 +363,13 @@ impl_versioned_full_protocol_from!(
impl_versioned_try_from!( impl_versioned_try_from!(
VersionedValidationProtocol, VersionedValidationProtocol,
BitfieldDistributionMessage, BitfieldDistributionMessage,
v1::ValidationProtocol::BitfieldDistribution(x) => x v1::ValidationProtocol::BitfieldDistribution(x) => x,
vstaging::ValidationProtocol::BitfieldDistribution(x) => x
); );
/// Version-annotated messages used by the statement distribution subsystem. /// Version-annotated messages used by the statement distribution subsystem.
pub type StatementDistributionMessage = Versioned<v1::StatementDistributionMessage>; pub type StatementDistributionMessage =
Versioned<v1::StatementDistributionMessage, vstaging::StatementDistributionMessage>;
impl_versioned_full_protocol_from!( impl_versioned_full_protocol_from!(
StatementDistributionMessage, StatementDistributionMessage,
VersionedValidationProtocol, VersionedValidationProtocol,
@@ -350,11 +378,13 @@ impl_versioned_full_protocol_from!(
impl_versioned_try_from!( impl_versioned_try_from!(
VersionedValidationProtocol, VersionedValidationProtocol,
StatementDistributionMessage, StatementDistributionMessage,
v1::ValidationProtocol::StatementDistribution(x) => x v1::ValidationProtocol::StatementDistribution(x) => x,
vstaging::ValidationProtocol::StatementDistribution(x) => x
); );
/// Version-annotated messages used by the approval distribution subsystem. /// Version-annotated messages used by the approval distribution subsystem.
pub type ApprovalDistributionMessage = Versioned<v1::ApprovalDistributionMessage>; pub type ApprovalDistributionMessage =
Versioned<v1::ApprovalDistributionMessage, vstaging::ApprovalDistributionMessage>;
impl_versioned_full_protocol_from!( impl_versioned_full_protocol_from!(
ApprovalDistributionMessage, ApprovalDistributionMessage,
VersionedValidationProtocol, VersionedValidationProtocol,
@@ -363,11 +393,14 @@ impl_versioned_full_protocol_from!(
impl_versioned_try_from!( impl_versioned_try_from!(
VersionedValidationProtocol, VersionedValidationProtocol,
ApprovalDistributionMessage, ApprovalDistributionMessage,
v1::ValidationProtocol::ApprovalDistribution(x) => x v1::ValidationProtocol::ApprovalDistribution(x) => x,
vstaging::ValidationProtocol::ApprovalDistribution(x) => x
); );
/// Version-annotated messages used by the gossip-support subsystem (this is void). /// Version-annotated messages used by the gossip-support subsystem (this is void).
pub type GossipSupportNetworkMessage = Versioned<v1::GossipSupportNetworkMessage>; pub type GossipSupportNetworkMessage =
Versioned<v1::GossipSupportNetworkMessage, vstaging::GossipSupportNetworkMessage>;
// This is a void enum placeholder, so never gets sent over the wire. // This is a void enum placeholder, so never gets sent over the wire.
impl TryFrom<VersionedValidationProtocol> for GossipSupportNetworkMessage { impl TryFrom<VersionedValidationProtocol> for GossipSupportNetworkMessage {
type Error = WrongVariant; type Error = WrongVariant;
@@ -384,7 +417,8 @@ impl<'a> TryFrom<&'a VersionedValidationProtocol> for GossipSupportNetworkMessag
} }
/// Version-annotated messages used by the bitfield distribution subsystem. /// Version-annotated messages used by the bitfield distribution subsystem.
pub type CollatorProtocolMessage = Versioned<v1::CollatorProtocolMessage>; pub type CollatorProtocolMessage =
Versioned<v1::CollatorProtocolMessage, vstaging::CollatorProtocolMessage>;
impl_versioned_full_protocol_from!( impl_versioned_full_protocol_from!(
CollatorProtocolMessage, CollatorProtocolMessage,
VersionedCollationProtocol, VersionedCollationProtocol,
@@ -393,7 +427,8 @@ impl_versioned_full_protocol_from!(
impl_versioned_try_from!( impl_versioned_try_from!(
VersionedCollationProtocol, VersionedCollationProtocol,
CollatorProtocolMessage, CollatorProtocolMessage,
v1::CollationProtocol::CollatorProtocol(x) => x v1::CollationProtocol::CollatorProtocol(x) => x,
vstaging::CollationProtocol::CollatorProtocol(x) => x
); );
/// v1 notification protocol types. /// v1 notification protocol types.
@@ -553,3 +588,256 @@ pub mod v1 {
payload payload
} }
} }
/// vstaging network protocol types.
pub mod vstaging {
use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec};
use parity_scale_codec::{Decode, Encode};
use polkadot_primitives::vstaging::{
CandidateHash, CandidateIndex, CollatorId, CollatorSignature, GroupIndex, Hash,
Id as ParaId, UncheckedSignedAvailabilityBitfield, UncheckedSignedStatement,
};
use polkadot_node_primitives::{
approval::{IndirectAssignmentCert, IndirectSignedApprovalVote},
UncheckedSignedFullStatement,
};
/// Network messages used by the bitfield distribution subsystem.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub enum BitfieldDistributionMessage {
/// A signed availability bitfield for a given relay-parent hash.
#[codec(index = 0)]
Bitfield(Hash, UncheckedSignedAvailabilityBitfield),
}
/// Bitfields indicating the statements that are known or undesired
/// about a candidate.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub struct StatementFilter {
/// Seconded statements. '1' is known or undesired.
pub seconded_in_group: BitVec<u8, Lsb0>,
/// Valid statements. '1' is known or undesired.
pub validated_in_group: BitVec<u8, Lsb0>,
}
impl StatementFilter {
/// Create a new blank filter with the given group size.
pub fn blank(group_size: usize) -> Self {
StatementFilter {
seconded_in_group: BitVec::repeat(false, group_size),
validated_in_group: BitVec::repeat(false, group_size),
}
}
/// Create a new full filter with the given group size.
pub fn full(group_size: usize) -> Self {
StatementFilter {
seconded_in_group: BitVec::repeat(true, group_size),
validated_in_group: BitVec::repeat(true, group_size),
}
}
/// Whether the filter has a specific expected length, consistent across both
/// bitfields.
pub fn has_len(&self, len: usize) -> bool {
self.seconded_in_group.len() == len && self.validated_in_group.len() == len
}
/// Determine the number of backing validators in the statement filter.
pub fn backing_validators(&self) -> usize {
self.seconded_in_group
.iter()
.by_vals()
.zip(self.validated_in_group.iter().by_vals())
.filter(|&(s, v)| s || v) // no double-counting
.count()
}
/// Whether the statement filter has at least one seconded statement.
pub fn has_seconded(&self) -> bool {
self.seconded_in_group.iter().by_vals().any(|x| x)
}
/// Mask out `Seconded` statements in `self` according to the provided
/// bitvec. Bits appearing in `mask` will not appear in `self` afterwards.
pub fn mask_seconded(&mut self, mask: &BitSlice<u8, Lsb0>) {
for (mut x, mask) in self
.seconded_in_group
.iter_mut()
.zip(mask.iter().by_vals().chain(std::iter::repeat(false)))
{
// (x, mask) => x
// (true, true) => false
// (true, false) => true
// (false, true) => false
// (false, false) => false
*x = *x && !mask;
}
}
/// Mask out `Valid` statements in `self` according to the provided
/// bitvec. Bits appearing in `mask` will not appear in `self` afterwards.
pub fn mask_valid(&mut self, mask: &BitSlice<u8, Lsb0>) {
for (mut x, mask) in self
.validated_in_group
.iter_mut()
.zip(mask.iter().by_vals().chain(std::iter::repeat(false)))
{
// (x, mask) => x
// (true, true) => false
// (true, false) => true
// (false, true) => false
// (false, false) => false
*x = *x && !mask;
}
}
}
/// A manifest of a known backed candidate, along with a description
/// of the statements backing it.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub struct BackedCandidateManifest {
/// The relay-parent of the candidate.
pub relay_parent: Hash,
/// The hash of the candidate.
pub candidate_hash: CandidateHash,
/// The group index backing the candidate at the relay-parent.
pub group_index: GroupIndex,
/// The para ID of the candidate. It is illegal for this to
/// be a para ID which is not assigned to the group indicated
/// in this manifest.
pub para_id: ParaId,
/// The head-data corresponding to the candidate.
pub parent_head_data_hash: Hash,
/// A statement filter which indicates which validators in the
/// para's group at the relay-parent have validated this candidate
/// and issued statements about it, to the advertiser's knowledge.
///
/// This MUST have exactly the minimum amount of bytes
/// necessary to represent the number of validators in the assigned
/// backing group as-of the relay-parent.
pub statement_knowledge: StatementFilter,
}
/// An acknowledgement of a backed candidate being known.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub struct BackedCandidateAcknowledgement {
/// The hash of the candidate.
pub candidate_hash: CandidateHash,
/// A statement filter which indicates which validators in the
/// para's group at the relay-parent have validated this candidate
/// and issued statements about it, to the advertiser's knowledge.
///
/// This MUST have exactly the minimum amount of bytes
/// necessary to represent the number of validators in the assigned
/// backing group as-of the relay-parent.
pub statement_knowledge: StatementFilter,
}
/// Network messages used by the statement distribution subsystem.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub enum StatementDistributionMessage {
/// A notification of a signed statement in compact form, for a given relay parent.
#[codec(index = 0)]
Statement(Hash, UncheckedSignedStatement),
/// A notification of a backed candidate being known by the
/// sending node, for the purpose of being requested by the receiving node
/// if needed.
#[codec(index = 1)]
BackedCandidateManifest(BackedCandidateManifest),
/// A notification of a backed candidate being known by the sending node,
/// for the purpose of informing a receiving node which already has the candidate.
#[codec(index = 2)]
BackedCandidateKnown(BackedCandidateAcknowledgement),
/// All messages for V1 for compatibility with the statement distribution
/// protocol, for relay-parents that don't support asynchronous backing.
///
/// These are illegal to send to V1 peers, and illegal to send concerning relay-parents
/// which support asynchronous backing. This backwards compatibility should be
/// considered immediately deprecated and can be removed once the node software
/// is not required to support logic from before asynchronous backing anymore.
#[codec(index = 255)]
V1Compatibility(crate::v1::StatementDistributionMessage),
}
/// Network messages used by the approval distribution subsystem.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub enum ApprovalDistributionMessage {
/// Assignments for candidates in recent, unfinalized blocks.
///
/// Actually checking the assignment may yield a different result.
#[codec(index = 0)]
Assignments(Vec<(IndirectAssignmentCert, CandidateIndex)>),
/// Approvals for candidates in some recent, unfinalized block.
#[codec(index = 1)]
Approvals(Vec<IndirectSignedApprovalVote>),
}
/// Dummy network message type, so we will receive connect/disconnect events.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GossipSupportNetworkMessage {}
/// Network messages used by the collator protocol subsystem
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub enum CollatorProtocolMessage {
/// Declare the intent to advertise collations under a collator ID, attaching a
/// signature of the `PeerId` of the node using the given collator ID key.
#[codec(index = 0)]
Declare(CollatorId, ParaId, CollatorSignature),
/// Advertise a collation to a validator. Can only be sent once the peer has
/// declared that they are a collator with given ID.
#[codec(index = 1)]
AdvertiseCollation {
/// Hash of the relay parent advertised collation is based on.
relay_parent: Hash,
/// Candidate hash.
candidate_hash: CandidateHash,
/// Parachain head data hash before candidate execution.
parent_head_data_hash: Hash,
},
/// A collation sent to a validator was seconded.
#[codec(index = 4)]
CollationSeconded(Hash, UncheckedSignedFullStatement),
}
/// All network messages on the validation peer-set.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, derive_more::From)]
pub enum ValidationProtocol {
/// Bitfield distribution messages
#[codec(index = 1)]
#[from]
BitfieldDistribution(BitfieldDistributionMessage),
/// Statement distribution messages
#[codec(index = 3)]
#[from]
StatementDistribution(StatementDistributionMessage),
/// Approval distribution messages
#[codec(index = 4)]
#[from]
ApprovalDistribution(ApprovalDistributionMessage),
}
/// All network messages on the collation peer-set.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, derive_more::From)]
pub enum CollationProtocol {
/// Collator protocol messages
#[codec(index = 0)]
#[from]
CollatorProtocol(CollatorProtocolMessage),
}
/// Get the payload that should be signed and included in a `Declare` message.
///
/// The payload is the local peer id of the node, which serves to prove that it
/// controls the collator key it is declaring an intention to collate under.
pub fn declare_signature_payload(peer_id: &sc_network::PeerId) -> Vec<u8> {
let mut payload = peer_id.to_bytes();
payload.extend_from_slice(b"COLL");
payload
}
}
@@ -118,10 +118,17 @@ impl PeerSet {
/// Networking layer relies on `get_main_version()` being the version /// Networking layer relies on `get_main_version()` being the version
/// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`]. /// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`].
pub fn get_main_version(self) -> ProtocolVersion { pub fn get_main_version(self) -> ProtocolVersion {
#[cfg(not(feature = "network-protocol-staging"))]
match self { match self {
PeerSet::Validation => ValidationVersion::V1.into(), PeerSet::Validation => ValidationVersion::V1.into(),
PeerSet::Collation => CollationVersion::V1.into(), PeerSet::Collation => CollationVersion::V1.into(),
} }
#[cfg(feature = "network-protocol-staging")]
match self {
PeerSet::Validation => ValidationVersion::VStaging.into(),
PeerSet::Collation => CollationVersion::VStaging.into(),
}
} }
/// Get the max notification size for this peer set. /// Get the max notification size for this peer set.
@@ -145,12 +152,16 @@ impl PeerSet {
PeerSet::Validation => PeerSet::Validation =>
if version == ValidationVersion::V1.into() { if version == ValidationVersion::V1.into() {
Some("validation/1") Some("validation/1")
} else if version == ValidationVersion::VStaging.into() {
Some("validation/2")
} else { } else {
None None
}, },
PeerSet::Collation => PeerSet::Collation =>
if version == CollationVersion::V1.into() { if version == CollationVersion::V1.into() {
Some("collation/1") Some("collation/1")
} else if version == CollationVersion::VStaging.into() {
Some("collation/2")
} else { } else {
None None
}, },
@@ -212,6 +223,8 @@ impl From<ProtocolVersion> for u32 {
pub enum ValidationVersion { pub enum ValidationVersion {
/// The first version. /// The first version.
V1 = 1, V1 = 1,
/// The staging version.
VStaging = 2,
} }
/// Supported collation protocol versions. Only versions defined here must be used in the codebase. /// Supported collation protocol versions. Only versions defined here must be used in the codebase.
@@ -219,6 +232,40 @@ pub enum ValidationVersion {
pub enum CollationVersion { pub enum CollationVersion {
/// The first version. /// The first version.
V1 = 1, V1 = 1,
/// The staging version.
VStaging = 2,
}
/// Marker indicating the version is unknown.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnknownVersion;
impl TryFrom<ProtocolVersion> for ValidationVersion {
type Error = UnknownVersion;
fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
for v in Self::iter() {
if v as u32 == p.0 {
return Ok(v)
}
}
Err(UnknownVersion)
}
}
impl TryFrom<ProtocolVersion> for CollationVersion {
type Error = UnknownVersion;
fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
for v in Self::iter() {
if v as u32 == p.0 {
return Ok(v)
}
}
Err(UnknownVersion)
}
} }
impl From<ValidationVersion> for ProtocolVersion { impl From<ValidationVersion> for ProtocolVersion {
@@ -51,9 +51,12 @@ pub use outgoing::{OutgoingRequest, OutgoingResult, Recipient, Requests, Respons
///// Multiplexer for incoming requests. ///// Multiplexer for incoming requests.
// pub mod multiplexer; // pub mod multiplexer;
/// Actual versioned requests and responses, that are sent over the wire. /// Actual versioned requests and responses that are sent over the wire.
pub mod v1; pub mod v1;
/// Actual versioned requests and responses that are sent over the wire.
pub mod vstaging;
/// A protocol per subsystem seems to make the most sense, this way we don't need any dispatching /// A protocol per subsystem seems to make the most sense, this way we don't need any dispatching
/// within protocols. /// within protocols.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumIter)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumIter)]
@@ -62,6 +65,8 @@ pub enum Protocol {
ChunkFetchingV1, ChunkFetchingV1,
/// Protocol for fetching collations from collators. /// Protocol for fetching collations from collators.
CollationFetchingV1, CollationFetchingV1,
/// Protocol for fetching collations from collators when async backing is enabled.
CollationFetchingVStaging,
/// Protocol for fetching seconded PoVs from validators of the same group. /// Protocol for fetching seconded PoVs from validators of the same group.
PoVFetchingV1, PoVFetchingV1,
/// Protocol for fetching available data. /// Protocol for fetching available data.
@@ -70,6 +75,10 @@ pub enum Protocol {
StatementFetchingV1, StatementFetchingV1,
/// Sending of dispute statements with application level confirmations. /// Sending of dispute statements with application level confirmations.
DisputeSendingV1, DisputeSendingV1,
/// Protocol for requesting candidates with attestations in statement distribution
/// when async backing is enabled.
AttestedCandidateVStaging,
} }
/// Minimum bandwidth we expect for validators - 500Mbit/s is the recommendation, so approximately /// Minimum bandwidth we expect for validators - 500Mbit/s is the recommendation, so approximately
@@ -101,12 +110,30 @@ const POV_REQUEST_TIMEOUT_CONNECTED: Duration = Duration::from_millis(1200);
/// fit statement distribution within a block of 6 seconds.) /// fit statement distribution within a block of 6 seconds.)
const STATEMENTS_TIMEOUT: Duration = Duration::from_secs(1); const STATEMENTS_TIMEOUT: Duration = Duration::from_secs(1);
/// We want attested candidate requests to time out relatively fast,
/// because slow requests will bottleneck the backing system. Ideally, we'd have
/// an adaptive timeout based on the candidate size, because there will be a lot of variance
/// in candidate sizes: candidates with no code and no messages vs candidates with code
/// and messages.
///
/// We supply leniency because there are often large candidates and asynchronous
/// backing allows them to be included over a longer window of time. Exponential back-off
/// up to a maximum of 10 seconds would be ideal, but isn't supported by the
/// infrastructure here yet: see https://github.com/paritytech/polkadot/issues/6009
const ATTESTED_CANDIDATE_TIMEOUT: Duration = Duration::from_millis(2500);
/// We don't want a slow peer to slow down all the others, at the same time we want to get out the /// We don't want a slow peer to slow down all the others, at the same time we want to get out the
/// data quickly in full to at least some peers (as this will reduce load on us as they then can /// data quickly in full to at least some peers (as this will reduce load on us as they then can
/// start serving the data). So this value is a trade-off. 3 seems to be sensible. So we would need /// start serving the data). So this value is a trade-off. 3 seems to be sensible. So we would need
/// to have 3 slow nodes connected, to delay transfer for others by `STATEMENTS_TIMEOUT`. /// to have 3 slow nodes connected, to delay transfer for others by `STATEMENTS_TIMEOUT`.
pub const MAX_PARALLEL_STATEMENT_REQUESTS: u32 = 3; pub const MAX_PARALLEL_STATEMENT_REQUESTS: u32 = 3;
/// We don't want a slow peer to slow down all the others, at the same time we want to get out the
/// data quickly in full to at least some peers (as this will reduce load on us as they then can
/// start serving the data). So this value is a tradeoff. 5 seems to be sensible. So we would need
/// to have 5 slow nodes connected, to delay transfer for others by `ATTESTED_CANDIDATE_TIMEOUT`.
pub const MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS: u32 = 5;
/// Response size limit for responses of POV like data. /// Response size limit for responses of POV like data.
/// ///
/// This is larger than `MAX_POV_SIZE` to account for protocol overhead and for additional data in /// This is larger than `MAX_POV_SIZE` to account for protocol overhead and for additional data in
@@ -120,6 +147,12 @@ const POV_RESPONSE_SIZE: u64 = MAX_POV_SIZE as u64 + 10_000;
/// This is `MAX_CODE_SIZE` plus some additional space for protocol overhead. /// This is `MAX_CODE_SIZE` plus some additional space for protocol overhead.
const STATEMENT_RESPONSE_SIZE: u64 = MAX_CODE_SIZE as u64 + 10_000; const STATEMENT_RESPONSE_SIZE: u64 = MAX_CODE_SIZE as u64 + 10_000;
/// Maximum response sizes for `AttestedCandidateVStaging`.
///
/// This is `MAX_CODE_SIZE` plus some additional space for protocol overhead and
/// additional backing statements.
const ATTESTED_CANDIDATE_RESPONSE_SIZE: u64 = MAX_CODE_SIZE as u64 + 100_000;
/// We can have relative large timeouts here, there is no value of hitting a /// We can have relative large timeouts here, there is no value of hitting a
/// timeout as we want to get statements through to each node in any case. /// timeout as we want to get statements through to each node in any case.
pub const DISPUTE_REQUEST_TIMEOUT: Duration = Duration::from_secs(12); pub const DISPUTE_REQUEST_TIMEOUT: Duration = Duration::from_secs(12);
@@ -166,15 +199,16 @@ impl Protocol {
request_timeout: CHUNK_REQUEST_TIMEOUT, request_timeout: CHUNK_REQUEST_TIMEOUT,
inbound_queue: tx, inbound_queue: tx,
}, },
Protocol::CollationFetchingV1 => RequestResponseConfig { Protocol::CollationFetchingV1 | Protocol::CollationFetchingVStaging =>
name, RequestResponseConfig {
fallback_names, name,
max_request_size: 1_000, fallback_names,
max_response_size: POV_RESPONSE_SIZE, max_request_size: 1_000,
// Taken from initial implementation in collator protocol: max_response_size: POV_RESPONSE_SIZE,
request_timeout: POV_REQUEST_TIMEOUT_CONNECTED, // Taken from initial implementation in collator protocol:
inbound_queue: tx, request_timeout: POV_REQUEST_TIMEOUT_CONNECTED,
}, inbound_queue: tx,
},
Protocol::PoVFetchingV1 => RequestResponseConfig { Protocol::PoVFetchingV1 => RequestResponseConfig {
name, name,
fallback_names, fallback_names,
@@ -220,6 +254,14 @@ impl Protocol {
request_timeout: DISPUTE_REQUEST_TIMEOUT, request_timeout: DISPUTE_REQUEST_TIMEOUT,
inbound_queue: tx, inbound_queue: tx,
}, },
Protocol::AttestedCandidateVStaging => RequestResponseConfig {
name,
fallback_names,
max_request_size: 1_000,
max_response_size: ATTESTED_CANDIDATE_RESPONSE_SIZE,
request_timeout: ATTESTED_CANDIDATE_TIMEOUT,
inbound_queue: tx,
},
} }
} }
@@ -233,7 +275,7 @@ impl Protocol {
// as well. // as well.
Protocol::ChunkFetchingV1 => 100, Protocol::ChunkFetchingV1 => 100,
// 10 seems reasonable, considering group sizes of max 10 validators. // 10 seems reasonable, considering group sizes of max 10 validators.
Protocol::CollationFetchingV1 => 10, Protocol::CollationFetchingV1 | Protocol::CollationFetchingVStaging => 10,
// 10 seems reasonable, considering group sizes of max 10 validators. // 10 seems reasonable, considering group sizes of max 10 validators.
Protocol::PoVFetchingV1 => 10, Protocol::PoVFetchingV1 => 10,
// Validators are constantly self-selecting to request available data which may lead // Validators are constantly self-selecting to request available data which may lead
@@ -264,23 +306,46 @@ impl Protocol {
// average, so something in the ballpark of 100 should be fine. Nodes will retry on // average, so something in the ballpark of 100 should be fine. Nodes will retry on
// failure, so having a good value here is mostly about performance tuning. // failure, so having a good value here is mostly about performance tuning.
Protocol::DisputeSendingV1 => 100, Protocol::DisputeSendingV1 => 100,
Protocol::AttestedCandidateVStaging => {
// We assume we can utilize up to 70% of the available bandwidth for statements.
// This is just a guess/estimate, with the following considerations: If we are
// faster than that, queue size will stay low anyway, even if not - requesters will
// get an immediate error, but if we are slower, requesters will run in a timeout -
// wasting precious time.
let available_bandwidth = 7 * MIN_BANDWIDTH_BYTES / 10;
let size = u64::saturating_sub(
ATTESTED_CANDIDATE_TIMEOUT.as_millis() as u64 * available_bandwidth /
(1000 * MAX_CODE_SIZE as u64),
MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as u64,
);
debug_assert!(
size > 0,
"We should have a channel size greater zero, otherwise we won't accept any requests."
);
size as usize
},
} }
} }
/// Fallback protocol names of this protocol, as understood by substrate networking. /// Fallback protocol names of this protocol, as understood by substrate networking.
fn get_fallback_names(self) -> Vec<ProtocolName> { fn get_fallback_names(self) -> Vec<ProtocolName> {
std::iter::once(self.get_legacy_name().into()).collect() self.get_legacy_name().into_iter().map(Into::into).collect()
} }
/// Legacy protocol name associated with each peer set. /// Legacy protocol name associated with each peer set, if any.
const fn get_legacy_name(self) -> &'static str { const fn get_legacy_name(self) -> Option<&'static str> {
match self { match self {
Protocol::ChunkFetchingV1 => "/polkadot/req_chunk/1", Protocol::ChunkFetchingV1 => Some("/polkadot/req_chunk/1"),
Protocol::CollationFetchingV1 => "/polkadot/req_collation/1", Protocol::CollationFetchingV1 => Some("/polkadot/req_collation/1"),
Protocol::PoVFetchingV1 => "/polkadot/req_pov/1", Protocol::PoVFetchingV1 => Some("/polkadot/req_pov/1"),
Protocol::AvailableDataFetchingV1 => "/polkadot/req_available_data/1", Protocol::AvailableDataFetchingV1 => Some("/polkadot/req_available_data/1"),
Protocol::StatementFetchingV1 => "/polkadot/req_statement/1", Protocol::StatementFetchingV1 => Some("/polkadot/req_statement/1"),
Protocol::DisputeSendingV1 => "/polkadot/send_dispute/1", Protocol::DisputeSendingV1 => Some("/polkadot/send_dispute/1"),
// Introduced after legacy names became legacy.
Protocol::AttestedCandidateVStaging => None,
Protocol::CollationFetchingVStaging => None,
} }
} }
} }
@@ -336,6 +401,9 @@ impl ReqProtocolNames {
Protocol::AvailableDataFetchingV1 => "/req_available_data/1", Protocol::AvailableDataFetchingV1 => "/req_available_data/1",
Protocol::StatementFetchingV1 => "/req_statement/1", Protocol::StatementFetchingV1 => "/req_statement/1",
Protocol::DisputeSendingV1 => "/send_dispute/1", Protocol::DisputeSendingV1 => "/send_dispute/1",
Protocol::CollationFetchingVStaging => "/req_collation/2",
Protocol::AttestedCandidateVStaging => "/req_attested_candidate/2",
}; };
format!("{}{}", prefix, short_name).into() format!("{}{}", prefix, short_name).into()
@@ -23,7 +23,7 @@ use sc_network::PeerId;
use polkadot_primitives::AuthorityDiscoveryId; use polkadot_primitives::AuthorityDiscoveryId;
use super::{v1, IsRequest, Protocol}; use super::{v1, vstaging, IsRequest, Protocol};
/// All requests that can be sent to the network bridge via `NetworkBridgeTxMessage::SendRequest`. /// All requests that can be sent to the network bridge via `NetworkBridgeTxMessage::SendRequest`.
#[derive(Debug)] #[derive(Debug)]
@@ -40,6 +40,12 @@ pub enum Requests {
StatementFetchingV1(OutgoingRequest<v1::StatementFetchingRequest>), StatementFetchingV1(OutgoingRequest<v1::StatementFetchingRequest>),
/// Requests for notifying about an ongoing dispute. /// Requests for notifying about an ongoing dispute.
DisputeSendingV1(OutgoingRequest<v1::DisputeRequest>), DisputeSendingV1(OutgoingRequest<v1::DisputeRequest>),
/// Request a candidate and attestations.
AttestedCandidateVStaging(OutgoingRequest<vstaging::AttestedCandidateRequest>),
/// Fetch a collation from a collator which previously announced it.
/// Compared to V1 it requires specifying which candidate is requested by its hash.
CollationFetchingVStaging(OutgoingRequest<vstaging::CollationFetchingRequest>),
} }
impl Requests { impl Requests {
@@ -48,10 +54,12 @@ impl Requests {
match self { match self {
Self::ChunkFetchingV1(_) => Protocol::ChunkFetchingV1, Self::ChunkFetchingV1(_) => Protocol::ChunkFetchingV1,
Self::CollationFetchingV1(_) => Protocol::CollationFetchingV1, Self::CollationFetchingV1(_) => Protocol::CollationFetchingV1,
Self::CollationFetchingVStaging(_) => Protocol::CollationFetchingVStaging,
Self::PoVFetchingV1(_) => Protocol::PoVFetchingV1, Self::PoVFetchingV1(_) => Protocol::PoVFetchingV1,
Self::AvailableDataFetchingV1(_) => Protocol::AvailableDataFetchingV1, Self::AvailableDataFetchingV1(_) => Protocol::AvailableDataFetchingV1,
Self::StatementFetchingV1(_) => Protocol::StatementFetchingV1, Self::StatementFetchingV1(_) => Protocol::StatementFetchingV1,
Self::DisputeSendingV1(_) => Protocol::DisputeSendingV1, Self::DisputeSendingV1(_) => Protocol::DisputeSendingV1,
Self::AttestedCandidateVStaging(_) => Protocol::AttestedCandidateVStaging,
} }
} }
@@ -66,10 +74,12 @@ impl Requests {
match self { match self {
Self::ChunkFetchingV1(r) => r.encode_request(), Self::ChunkFetchingV1(r) => r.encode_request(),
Self::CollationFetchingV1(r) => r.encode_request(), Self::CollationFetchingV1(r) => r.encode_request(),
Self::CollationFetchingVStaging(r) => r.encode_request(),
Self::PoVFetchingV1(r) => r.encode_request(), Self::PoVFetchingV1(r) => r.encode_request(),
Self::AvailableDataFetchingV1(r) => r.encode_request(), Self::AvailableDataFetchingV1(r) => r.encode_request(),
Self::StatementFetchingV1(r) => r.encode_request(), Self::StatementFetchingV1(r) => r.encode_request(),
Self::DisputeSendingV1(r) => r.encode_request(), Self::DisputeSendingV1(r) => r.encode_request(),
Self::AttestedCandidateVStaging(r) => r.encode_request(),
} }
} }
} }
@@ -0,0 +1,80 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Requests and responses as sent over the wire for the individual protocols.
use parity_scale_codec::{Decode, Encode};
use polkadot_primitives::vstaging::{
CandidateHash, CommittedCandidateReceipt, Hash, Id as ParaId, PersistedValidationData,
UncheckedSignedStatement,
};
use super::{IsRequest, Protocol};
use crate::vstaging::StatementFilter;
/// Request a candidate with statements.
#[derive(Debug, Clone, Encode, Decode)]
pub struct AttestedCandidateRequest {
/// Hash of the candidate we want to request.
pub candidate_hash: CandidateHash,
/// Statement filter with 'OR' semantics, indicating which validators
/// not to send statements for.
///
/// The filter must have exactly the minimum size required to
/// fit all validators from the backing group.
///
/// The response may not contain any statements masked out by this mask.
pub mask: StatementFilter,
}
/// Response to an `AttestedCandidateRequest`.
#[derive(Debug, Clone, Encode, Decode)]
pub struct AttestedCandidateResponse {
/// The candidate receipt, with commitments.
pub candidate_receipt: CommittedCandidateReceipt,
/// The [`PersistedValidationData`] corresponding to the candidate.
pub persisted_validation_data: PersistedValidationData,
/// All known statements about the candidate, in compact form,
/// omitting `Seconded` statements which were intended to be masked
/// out.
pub statements: Vec<UncheckedSignedStatement>,
}
impl IsRequest for AttestedCandidateRequest {
type Response = AttestedCandidateResponse;
const PROTOCOL: Protocol = Protocol::AttestedCandidateVStaging;
}
/// Responses as sent by collators.
pub type CollationFetchingResponse = super::v1::CollationFetchingResponse;
/// Request the advertised collation at that relay-parent.
#[derive(Debug, Clone, Encode, Decode)]
pub struct CollationFetchingRequest {
/// Relay parent collation is built on top of.
pub relay_parent: Hash,
/// The `ParaId` of the collation.
pub para_id: ParaId,
/// Candidate hash.
pub candidate_hash: CandidateHash,
}
impl IsRequest for CollationFetchingRequest {
// The response is the same as for V1.
type Response = CollationFetchingResponse;
const PROTOCOL: Protocol = Protocol::CollationFetchingVStaging;
}
@@ -22,10 +22,12 @@ indexmap = "1.9.1"
parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] }
thiserror = "1.0.31" thiserror = "1.0.31"
fatality = "0.0.6" fatality = "0.0.6"
bitvec = "1"
[dev-dependencies] [dev-dependencies]
polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } async-channel = "1.8.0"
assert_matches = "1.4.0" assert_matches = "1.4.0"
polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
sp-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -36,3 +38,5 @@ sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "maste
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
futures-timer = "3.0.2" futures-timer = "3.0.2"
polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" } polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" }
rand_chacha = "0.3"
polkadot-node-subsystem-types = { path = "../../subsystem-types" }
@@ -18,9 +18,13 @@
//! Error handling related code and Error/Result definitions. //! Error handling related code and Error/Result definitions.
use polkadot_node_network_protocol::PeerId; use polkadot_node_network_protocol::PeerId;
use polkadot_node_subsystem::SubsystemError; use polkadot_node_subsystem::{RuntimeApiError, SubsystemError};
use polkadot_node_subsystem_util::runtime; use polkadot_node_subsystem_util::{
use polkadot_primitives::{CandidateHash, Hash}; backing_implicit_view::FetchError as ImplicitViewFetchError, runtime,
};
use polkadot_primitives::{CandidateHash, Hash, Id as ParaId};
use futures::channel::oneshot;
use crate::LOG_TARGET; use crate::LOG_TARGET;
@@ -56,6 +60,27 @@ pub enum Error {
#[error("Error while accessing runtime information")] #[error("Error while accessing runtime information")]
Runtime(#[from] runtime::Error), Runtime(#[from] runtime::Error),
#[error("RuntimeAPISubsystem channel closed before receipt")]
RuntimeApiUnavailable(#[source] oneshot::Canceled),
#[error("Fetching persisted validation data for para {0:?}, {1:?}")]
FetchPersistedValidationData(ParaId, RuntimeApiError),
#[error("Fetching session index failed {0:?}")]
FetchSessionIndex(RuntimeApiError),
#[error("Fetching session info failed {0:?}")]
FetchSessionInfo(RuntimeApiError),
#[error("Fetching availability cores failed {0:?}")]
FetchAvailabilityCores(RuntimeApiError),
#[error("Fetching validator groups failed {0:?}")]
FetchValidatorGroups(RuntimeApiError),
#[error("Attempted to share statement when not a validator or not assigned")]
InvalidShare,
#[error("Relay parent could not be found in active heads")] #[error("Relay parent could not be found in active heads")]
NoSuchHead(Hash), NoSuchHead(Hash),
@@ -76,6 +101,10 @@ pub enum Error {
// Responder no longer waits for our data. (Should not happen right now.) // Responder no longer waits for our data. (Should not happen right now.)
#[error("Oneshot `GetData` channel closed")] #[error("Oneshot `GetData` channel closed")]
ResponderGetDataCanceled, ResponderGetDataCanceled,
// Failed to activate leaf due to a fetch error.
#[error("Implicit view failure while activating leaf")]
ActivateLeafFailure(ImplicitViewFetchError),
} }
/// Utility for eating top level errors and log them. /// Utility for eating top level errors and log them.
File diff suppressed because it is too large Load Diff
@@ -32,7 +32,10 @@ use polkadot_node_subsystem::{Span, Stage};
use polkadot_node_subsystem_util::TimeoutExt; use polkadot_node_subsystem_util::TimeoutExt;
use polkadot_primitives::{CandidateHash, CommittedCandidateReceipt, Hash}; use polkadot_primitives::{CandidateHash, CommittedCandidateReceipt, Hash};
use crate::{metrics::Metrics, COST_WRONG_HASH, LOG_TARGET}; use crate::{
legacy_v1::{COST_WRONG_HASH, LOG_TARGET},
metrics::Metrics,
};
// In case we failed fetching from our known peers, how long we should wait before attempting a // In case we failed fetching from our known peers, how long we should wait before attempting a
// retry, even though we have not yet discovered any new peers. Or in other words how long to // retry, even though we have not yet discovered any new peers. Or in other words how long to
@@ -48,8 +48,8 @@ pub enum ResponderMessage {
/// A fetching task, taking care of fetching large statements via request/response. /// A fetching task, taking care of fetching large statements via request/response.
/// ///
/// A fetch task does not know about a particular `Statement` instead it just tries fetching a /// A fetch task does not know about a particular `Statement`, instead it just tries fetching a
/// `CommittedCandidateReceipt` from peers, whether this can be used to re-assemble one ore /// `CommittedCandidateReceipt` from peers, whether this can be used to re-assemble one or
/// many `SignedFullStatement`s needs to be verified by the caller. /// many `SignedFullStatement`s needs to be verified by the caller.
pub async fn respond( pub async fn respond(
mut receiver: IncomingRequestReceiver<StatementFetchingRequest>, mut receiver: IncomingRequestReceiver<StatementFetchingRequest>,
@@ -14,7 +14,11 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. // along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::{metrics::Metrics, *}; #![allow(clippy::clone_on_copy)]
use super::*;
use crate::{metrics::Metrics, *};
use assert_matches::assert_matches; use assert_matches::assert_matches;
use futures::executor; use futures::executor;
use futures_timer::Delay; use futures_timer::Delay;
@@ -26,19 +30,21 @@ use polkadot_node_network_protocol::{
v1::{StatementFetchingRequest, StatementFetchingResponse}, v1::{StatementFetchingRequest, StatementFetchingResponse},
IncomingRequest, Recipient, ReqProtocolNames, Requests, IncomingRequest, Recipient, ReqProtocolNames, Requests,
}, },
view, ObservedRole, view, ObservedRole, VersionedValidationProtocol,
};
use polkadot_node_primitives::{
SignedFullStatementWithPVD, Statement, UncheckedSignedFullStatement,
}; };
use polkadot_node_primitives::{Statement, UncheckedSignedFullStatement};
use polkadot_node_subsystem::{ use polkadot_node_subsystem::{
jaeger, jaeger,
messages::{ messages::{
network_bridge_event, AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest, network_bridge_event, AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest,
}, },
ActivatedLeaf, LeafStatus, ActivatedLeaf, LeafStatus, RuntimeApiError,
}; };
use polkadot_node_subsystem_test_helpers::mock::make_ferdie_keystore; use polkadot_node_subsystem_test_helpers::mock::make_ferdie_keystore;
use polkadot_primitives::{ use polkadot_primitives::{
GroupIndex, Hash, Id as ParaId, IndexedVec, SessionInfo, ValidationCode, ValidatorId, GroupIndex, Hash, HeadData, Id as ParaId, IndexedVec, SessionInfo, ValidationCode,
}; };
use polkadot_primitives_test_helpers::{ use polkadot_primitives_test_helpers::{
dummy_committed_candidate_receipt, dummy_hash, AlwaysZeroRng, dummy_committed_candidate_receipt, dummy_hash, AlwaysZeroRng,
@@ -54,6 +60,30 @@ use util::reputation::add_reputation;
// Some deterministic genesis hash for protocol names // Some deterministic genesis hash for protocol names
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff); const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError =
RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" };
fn dummy_pvd() -> PersistedValidationData {
PersistedValidationData {
parent_head: HeadData(vec![7, 8, 9]),
relay_parent_number: 5,
max_pov_size: 1024,
relay_parent_storage_root: Default::default(),
}
}
fn extend_statement_with_pvd(
statement: SignedFullStatement,
pvd: PersistedValidationData,
) -> SignedFullStatementWithPVD {
statement
.convert_to_superpayload_with(|statement| match statement {
Statement::Seconded(receipt) => StatementWithPVD::Seconded(receipt, pvd),
Statement::Valid(candidate_hash) => StatementWithPVD::Valid(candidate_hash),
})
.unwrap()
}
#[test] #[test]
fn active_head_accepts_only_2_seconded_per_validator() { fn active_head_accepts_only_2_seconded_per_validator() {
let validators = vec![ let validators = vec![
@@ -496,6 +526,7 @@ fn peer_view_update_sends_messages() {
let mut peer_data = PeerData { let mut peer_data = PeerData {
view: old_view, view: old_view,
protocol_version: ValidationVersion::V1,
view_knowledge: { view_knowledge: {
let mut k = HashMap::new(); let mut k = HashMap::new();
@@ -554,8 +585,9 @@ fn peer_view_update_sends_messages() {
for statement in active_head.statements_about(candidate_hash) { for statement in active_head.statements_about(candidate_hash) {
let message = handle.recv().await; let message = handle.recv().await;
let expected_to = vec![peer]; let expected_to = vec![peer];
let expected_payload = let expected_payload = VersionedValidationProtocol::from(Versioned::V1(
statement_message(hash_c, statement.statement.clone(), &Metrics::default()); v1_statement_message(hash_c, statement.statement.clone(), &Metrics::default()),
));
assert_matches!( assert_matches!(
message, message,
@@ -596,6 +628,7 @@ fn circulated_statement_goes_to_all_peers_with_view() {
let peer_data_from_view = |view: View| PeerData { let peer_data_from_view = |view: View| PeerData {
view: view.clone(), view: view.clone(),
protocol_version: ValidationVersion::V1,
view_knowledge: view.iter().map(|v| (*v, Default::default())).collect(), view_knowledge: view.iter().map(|v| (*v, Default::default())).collect(),
maybe_authority: None, maybe_authority: None,
}; };
@@ -697,7 +730,7 @@ fn circulated_statement_goes_to_all_peers_with_view() {
assert_eq!( assert_eq!(
payload, payload,
statement_message(hash_b, statement.statement.clone(), &Metrics::default()), VersionedValidationProtocol::from(Versioned::V1(v1_statement_message(hash_b, statement.statement.clone(), &Metrics::default()))),
); );
} }
) )
@@ -706,12 +739,14 @@ fn circulated_statement_goes_to_all_peers_with_view() {
#[test] #[test]
fn receiving_from_one_sends_to_another_and_to_candidate_backing() { fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
const PARA_ID: ParaId = ParaId::new(1);
let hash_a = Hash::repeat_byte(1); let hash_a = Hash::repeat_byte(1);
let pvd = dummy_pvd();
let candidate = { let candidate = {
let mut c = dummy_committed_candidate_receipt(dummy_hash()); let mut c = dummy_committed_candidate_receipt(dummy_hash());
c.descriptor.relay_parent = hash_a; c.descriptor.relay_parent = hash_a;
c.descriptor.para_id = 1.into(); c.descriptor.para_id = PARA_ID;
c c
}; };
@@ -733,11 +768,13 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names); let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let bg = async move { let bg = async move {
let s = StatementDistributionSubsystem { let s = StatementDistributionSubsystem {
keystore: Arc::new(LocalKeystore::in_memory()), keystore: Arc::new(LocalKeystore::in_memory()),
req_receiver: Some(statement_req_receiver), v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(), metrics: Default::default(),
rng: AlwaysZeroRng, rng: AlwaysZeroRng,
reputation: ReputationAggregator::new(|_| true), reputation: ReputationAggregator::new(|_| true),
@@ -758,6 +795,17 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
))) )))
.await; .await;
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(r, RuntimeApiRequest::StagingAsyncBackingParams(tx))
)
if r == hash_a
=> {
let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR));
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
@@ -862,18 +910,32 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
}) })
.await; .await;
let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone());
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
hash,
RuntimeApiRequest::PersistedValidationData(para_id, assumption, tx),
)) if para_id == PARA_ID &&
assumption == OccupiedCoreAssumption::Free &&
hash == hash_a =>
{
tx.send(Ok(Some(pvd))).unwrap();
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::NetworkBridgeTx( AllMessages::NetworkBridgeTx(
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)) NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => {} ) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => {}
); );
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::CandidateBacking( AllMessages::CandidateBacking(
CandidateBackingMessage::Statement(r, s) CandidateBackingMessage::Statement(r, s)
) if r == hash_a && s == statement => {} ) if r == hash_a && s == statement_with_pvd => {}
); );
assert_matches!( assert_matches!(
@@ -902,6 +964,9 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
#[test] #[test]
fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing() { fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing() {
const PARA_ID: ParaId = ParaId::new(1);
let pvd = dummy_pvd();
sp_tracing::try_init_simple(); sp_tracing::try_init_simple();
let hash_a = Hash::repeat_byte(1); let hash_a = Hash::repeat_byte(1);
let hash_b = Hash::repeat_byte(2); let hash_b = Hash::repeat_byte(2);
@@ -909,7 +974,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
let candidate = { let candidate = {
let mut c = dummy_committed_candidate_receipt(dummy_hash()); let mut c = dummy_committed_candidate_receipt(dummy_hash());
c.descriptor.relay_parent = hash_a; c.descriptor.relay_parent = hash_a;
c.descriptor.para_id = 1.into(); c.descriptor.para_id = PARA_ID;
c.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3])); c.commitments.new_validation_code = Some(ValidationCode(vec![1, 2, 3]));
c c
}; };
@@ -937,11 +1002,13 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, mut req_cfg) = let (statement_req_receiver, mut req_cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names); IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let bg = async move { let bg = async move {
let s = StatementDistributionSubsystem { let s = StatementDistributionSubsystem {
keystore: make_ferdie_keystore(), keystore: make_ferdie_keystore(),
req_receiver: Some(statement_req_receiver), v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(), metrics: Default::default(),
rng: AlwaysZeroRng, rng: AlwaysZeroRng,
reputation: ReputationAggregator::new(|_| true), reputation: ReputationAggregator::new(|_| true),
@@ -962,6 +1029,17 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
))) )))
.await; .await;
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(r, RuntimeApiRequest::StagingAsyncBackingParams(tx))
)
if r == hash_a
=> {
let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR));
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
@@ -1292,6 +1370,20 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
) if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() => {} ) if p == peer_c && r == BENEFIT_VALID_RESPONSE.into() => {}
); );
let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone());
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
hash,
RuntimeApiRequest::PersistedValidationData(para_id, assumption, tx),
)) if para_id == PARA_ID &&
assumption == OccupiedCoreAssumption::Free &&
hash == hash_a =>
{
tx.send(Ok(Some(pvd))).unwrap();
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::NetworkBridgeTx( AllMessages::NetworkBridgeTx(
@@ -1303,7 +1395,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
handle.recv().await, handle.recv().await,
AllMessages::CandidateBacking( AllMessages::CandidateBacking(
CandidateBackingMessage::Statement(r, s) CandidateBackingMessage::Statement(r, s)
) if r == hash_a && s == statement => {} ) if r == hash_a && s == statement_with_pvd => {}
); );
// Now messages should go out: // Now messages should go out:
@@ -1400,6 +1492,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
fn delay_reputation_changes() { fn delay_reputation_changes() {
sp_tracing::try_init_simple(); sp_tracing::try_init_simple();
let hash_a = Hash::repeat_byte(1); let hash_a = Hash::repeat_byte(1);
let pvd = dummy_pvd();
let candidate = { let candidate = {
let mut c = dummy_committed_candidate_receipt(dummy_hash()); let mut c = dummy_committed_candidate_receipt(dummy_hash());
@@ -1431,13 +1524,15 @@ fn delay_reputation_changes() {
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names); let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let reputation_interval = Duration::from_millis(100); let reputation_interval = Duration::from_millis(100);
let bg = async move { let bg = async move {
let s = StatementDistributionSubsystem { let s = StatementDistributionSubsystem {
keystore: make_ferdie_keystore(), keystore: make_ferdie_keystore(),
req_receiver: Some(statement_req_receiver), v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(), metrics: Default::default(),
rng: AlwaysZeroRng, rng: AlwaysZeroRng,
reputation: ReputationAggregator::new(|_| false), reputation: ReputationAggregator::new(|_| false),
@@ -1458,6 +1553,17 @@ fn delay_reputation_changes() {
))) )))
.await; .await;
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(r, RuntimeApiRequest::StagingAsyncBackingParams(tx))
)
if r == hash_a
=> {
let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR));
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
@@ -1768,9 +1874,18 @@ fn delay_reputation_changes() {
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::CandidateBacking( AllMessages::RuntimeApi(RuntimeApiMessage::Request(
CandidateBackingMessage::Statement(r, s) hash,
) if r == hash_a && s == statement => {} RuntimeApiRequest::PersistedValidationData(_, assumption, tx),
)) if assumption == OccupiedCoreAssumption::Free && hash == hash_a =>
{
tx.send(Ok(Some(pvd))).unwrap();
}
);
assert_matches!(
handle.recv().await,
AllMessages::CandidateBacking(CandidateBackingMessage::Statement(..))
); );
// Now messages should go out: // Now messages should go out:
@@ -1885,11 +2000,13 @@ fn share_prioritizes_backing_group() {
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, mut req_cfg) = let (statement_req_receiver, mut req_cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names); IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let bg = async move { let bg = async move {
let s = StatementDistributionSubsystem { let s = StatementDistributionSubsystem {
keystore: make_ferdie_keystore(), keystore: make_ferdie_keystore(),
req_receiver: Some(statement_req_receiver), v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(), metrics: Default::default(),
rng: AlwaysZeroRng, rng: AlwaysZeroRng,
reputation: ReputationAggregator::new(|_| true), reputation: ReputationAggregator::new(|_| true),
@@ -1910,6 +2027,17 @@ fn share_prioritizes_backing_group() {
))) )))
.await; .await;
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(r, RuntimeApiRequest::StagingAsyncBackingParams(tx))
)
if r == hash_a
=> {
let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR));
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
@@ -2069,9 +2197,17 @@ fn share_prioritizes_backing_group() {
) )
.unwrap(); .unwrap();
SignedFullStatement::sign( // note: this is ignored by legacy-v1 code.
let pvd = PersistedValidationData {
parent_head: HeadData::from(vec![1, 2, 3]),
relay_parent_number: 0,
relay_parent_storage_root: Hash::repeat_byte(42),
max_pov_size: 100,
};
SignedFullStatementWithPVD::sign(
&keystore, &keystore,
Statement::Seconded(candidate.clone()), Statement::Seconded(candidate.clone()).supply_pvd(pvd),
&signing_context, &signing_context,
ValidatorIndex(4), ValidatorIndex(4),
&ferdie_public.into(), &ferdie_public.into(),
@@ -2081,14 +2217,15 @@ fn share_prioritizes_backing_group() {
.expect("should be signed") .expect("should be signed")
}; };
let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into());
handle handle
.send(FromOrchestra::Communication { .send(FromOrchestra::Communication {
msg: StatementDistributionMessage::Share(hash_a, statement.clone()), msg: StatementDistributionMessage::Share(hash_a, statement.clone()),
}) })
.await; .await;
let statement = StatementWithPVD::drop_pvd_from_signed(statement);
let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into());
// Messages should go out: // Messages should go out:
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
@@ -2180,10 +2317,12 @@ fn peer_cant_flood_with_large_statements() {
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names); let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let bg = async move { let bg = async move {
let s = StatementDistributionSubsystem { let s = StatementDistributionSubsystem {
keystore: make_ferdie_keystore(), keystore: make_ferdie_keystore(),
req_receiver: Some(statement_req_receiver), v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(), metrics: Default::default(),
rng: AlwaysZeroRng, rng: AlwaysZeroRng,
reputation: ReputationAggregator::new(|_| true), reputation: ReputationAggregator::new(|_| true),
@@ -2204,6 +2343,17 @@ fn peer_cant_flood_with_large_statements() {
))) )))
.await; .await;
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(r, RuntimeApiRequest::StagingAsyncBackingParams(tx))
)
if r == hash_a
=> {
let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR));
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
@@ -2341,6 +2491,7 @@ fn peer_cant_flood_with_large_statements() {
#[test] #[test]
fn handle_multiple_seconded_statements() { fn handle_multiple_seconded_statements() {
let relay_parent_hash = Hash::repeat_byte(1); let relay_parent_hash = Hash::repeat_byte(1);
let pvd = dummy_pvd();
let candidate = dummy_committed_candidate_receipt(relay_parent_hash); let candidate = dummy_committed_candidate_receipt(relay_parent_hash);
let candidate_hash = candidate.hash(); let candidate_hash = candidate.hash();
@@ -2384,11 +2535,13 @@ fn handle_multiple_seconded_statements() {
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None); let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names); let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let virtual_overseer_fut = async move { let virtual_overseer_fut = async move {
let s = StatementDistributionSubsystem { let s = StatementDistributionSubsystem {
keystore: Arc::new(LocalKeystore::in_memory()), keystore: Arc::new(LocalKeystore::in_memory()),
req_receiver: Some(statement_req_receiver), v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(), metrics: Default::default(),
rng: AlwaysZeroRng, rng: AlwaysZeroRng,
reputation: ReputationAggregator::new(|_| true), reputation: ReputationAggregator::new(|_| true),
@@ -2409,6 +2562,17 @@ fn handle_multiple_seconded_statements() {
))) )))
.await; .await;
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(r, RuntimeApiRequest::StagingAsyncBackingParams(tx))
)
if r == relay_parent_hash
=> {
let _ = tx.send(Err(ASYNC_BACKING_DISABLED_ERROR));
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::RuntimeApi( AllMessages::RuntimeApi(
@@ -2575,6 +2739,18 @@ fn handle_multiple_seconded_statements() {
}) })
.await; .await;
let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone());
assert_matches!(
handle.recv().await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
_,
RuntimeApiRequest::PersistedValidationData(_, assumption, tx),
)) if assumption == OccupiedCoreAssumption::Free => {
tx.send(Ok(Some(pvd.clone()))).unwrap();
}
);
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::NetworkBridgeTx( AllMessages::NetworkBridgeTx(
@@ -2592,7 +2768,7 @@ fn handle_multiple_seconded_statements() {
CandidateBackingMessage::Statement(r, s) CandidateBackingMessage::Statement(r, s)
) => { ) => {
assert_eq!(r, relay_parent_hash); assert_eq!(r, relay_parent_hash);
assert_eq!(s, statement); assert_eq!(s, statement_with_pvd);
} }
); );
@@ -2676,6 +2852,10 @@ fn handle_multiple_seconded_statements() {
}) })
.await; .await;
let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone());
// Persisted validation data is cached.
assert_matches!( assert_matches!(
handle.recv().await, handle.recv().await,
AllMessages::NetworkBridgeTx( AllMessages::NetworkBridgeTx(
@@ -2692,7 +2872,7 @@ fn handle_multiple_seconded_statements() {
CandidateBackingMessage::Statement(r, s) CandidateBackingMessage::Statement(r, s)
) => { ) => {
assert_eq!(r, relay_parent_hash); assert_eq!(r, relay_parent_hash);
assert_eq!(s, statement); assert_eq!(s, statement_with_pvd);
} }
); );
@@ -2784,3 +2964,8 @@ fn derive_metadata_assuming_seconded(
signature: statement.unchecked_signature().clone(), signature: statement.unchecked_signature().clone(),
} }
} }
// TODO [now]: adapt most tests to v2 messages.
// TODO [now]: test that v2 peers send v1 messages to v1 peers
// TODO [now]: test that v2 peers handle v1 messages from v1 peers.
// TODO [now]: test that v2 peers send v2 messages to v2 peers.
File diff suppressed because it is too large Load Diff
@@ -29,7 +29,7 @@ struct MetricsInner {
received_responses: prometheus::CounterVec<prometheus::U64>, received_responses: prometheus::CounterVec<prometheus::U64>,
active_leaves_update: prometheus::Histogram, active_leaves_update: prometheus::Histogram,
share: prometheus::Histogram, share: prometheus::Histogram,
network_bridge_update_v1: prometheus::HistogramVec, network_bridge_update: prometheus::HistogramVec,
statements_unexpected: prometheus::CounterVec<prometheus::U64>, statements_unexpected: prometheus::CounterVec<prometheus::U64>,
created_message_size: prometheus::Gauge<prometheus::U64>, created_message_size: prometheus::Gauge<prometheus::U64>,
} }
@@ -77,16 +77,13 @@ impl Metrics {
self.0.as_ref().map(|metrics| metrics.share.start_timer()) self.0.as_ref().map(|metrics| metrics.share.start_timer())
} }
/// Provide a timer for `network_bridge_update_v1` which observes on drop. /// Provide a timer for `network_bridge_update` which observes on drop.
pub fn time_network_bridge_update_v1( pub fn time_network_bridge_update(
&self, &self,
message_type: &'static str, message_type: &'static str,
) -> Option<metrics::prometheus::prometheus::HistogramTimer> { ) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
self.0.as_ref().map(|metrics| { self.0.as_ref().map(|metrics| {
metrics metrics.network_bridge_update.with_label_values(&[message_type]).start_timer()
.network_bridge_update_v1
.with_label_values(&[message_type])
.start_timer()
}) })
} }
@@ -168,11 +165,11 @@ impl metrics::Metrics for Metrics {
)?, )?,
registry, registry,
)?, )?,
network_bridge_update_v1: prometheus::register( network_bridge_update: prometheus::register(
prometheus::HistogramVec::new( prometheus::HistogramVec::new(
prometheus::HistogramOpts::new( prometheus::HistogramOpts::new(
"polkadot_parachain_statement_distribution_network_bridge_update_v1", "polkadot_parachain_statement_distribution_network_bridge_update",
"Time spent within `statement_distribution::network_bridge_update_v1`", "Time spent within `statement_distribution::network_bridge_update`",
) )
.buckets(HISTOGRAM_LATENCY_BUCKETS.into()), .buckets(HISTOGRAM_LATENCY_BUCKETS.into()),
&["message_type"], &["message_type"],
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,70 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! A utility for tracking groups and their members within a session.
use polkadot_node_primitives::minimum_votes;
use polkadot_primitives::vstaging::{GroupIndex, IndexedVec, ValidatorIndex};
use std::collections::HashMap;
/// Validator groups within a session, plus some helpful indexing for
/// looking up groups by validator indices or authority discovery ID.
#[derive(Debug, Clone)]
pub struct Groups {
groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>>,
by_validator_index: HashMap<ValidatorIndex, GroupIndex>,
}
impl Groups {
/// Create a new [`Groups`] tracker with the groups and discovery keys
/// from the session.
pub fn new(groups: IndexedVec<GroupIndex, Vec<ValidatorIndex>>) -> Self {
let mut by_validator_index = HashMap::new();
for (i, group) in groups.iter().enumerate() {
let index = GroupIndex(i as _);
for v in group {
by_validator_index.insert(*v, index);
}
}
Groups { groups, by_validator_index }
}
/// Access all the underlying groups.
pub fn all(&self) -> &IndexedVec<GroupIndex, Vec<ValidatorIndex>> {
&self.groups
}
/// Get the underlying group validators by group index.
pub fn get(&self, group_index: GroupIndex) -> Option<&[ValidatorIndex]> {
self.groups.get(group_index).map(|x| &x[..])
}
/// Get the backing group size and backing threshold.
pub fn get_size_and_backing_threshold(
&self,
group_index: GroupIndex,
) -> Option<(usize, usize)> {
self.get(group_index).map(|g| (g.len(), minimum_votes(g.len())))
}
/// Get the group index for a validator by index.
pub fn by_validator_index(&self, validator_index: ValidatorIndex) -> Option<GroupIndex> {
self.by_validator_index.get(&validator_index).map(|x| *x)
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,283 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! A store of all statements under a given relay-parent.
//!
//! This structure doesn't attempt to do any spam protection, which must
//! be provided at a higher level.
//!
//! This keeps track of statements submitted with a number of different of
//! views into this data: views based on the candidate, views based on the validator
//! groups, and views based on the validators themselves.
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use polkadot_node_network_protocol::vstaging::StatementFilter;
use polkadot_primitives::vstaging::{
CandidateHash, CompactStatement, GroupIndex, SignedStatement, ValidatorIndex,
};
use std::collections::hash_map::{Entry as HEntry, HashMap};
use super::groups::Groups;
/// Possible origins of a statement.
pub enum StatementOrigin {
/// The statement originated locally.
Local,
/// The statement originated from a remote peer.
Remote,
}
impl StatementOrigin {
fn is_local(&self) -> bool {
match *self {
StatementOrigin::Local => true,
StatementOrigin::Remote => false,
}
}
}
struct StoredStatement {
statement: SignedStatement,
known_by_backing: bool,
}
/// Storage for statements. Intended to be used for statements signed under
/// the same relay-parent. See module docs for more details.
pub struct StatementStore {
validator_meta: HashMap<ValidatorIndex, ValidatorMeta>,
// we keep statements per-group because even though only one group _should_ be
// producing statements about a candidate, until we have the candidate receipt
// itself, we can't tell which group that is.
group_statements: HashMap<(GroupIndex, CandidateHash), GroupStatements>,
known_statements: HashMap<Fingerprint, StoredStatement>,
}
impl StatementStore {
/// Create a new [`StatementStore`]
pub fn new(groups: &Groups) -> Self {
let mut validator_meta = HashMap::new();
for (g, group) in groups.all().iter().enumerate() {
for (i, v) in group.iter().enumerate() {
validator_meta.insert(
*v,
ValidatorMeta {
seconded_count: 0,
within_group_index: i,
group: GroupIndex(g as _),
},
);
}
}
StatementStore {
validator_meta,
group_statements: HashMap::new(),
known_statements: HashMap::new(),
}
}
/// Insert a statement. Returns `true` if was not known already, `false` if it was.
/// Ignores statements by unknown validators and returns an error.
pub fn insert(
&mut self,
groups: &Groups,
statement: SignedStatement,
origin: StatementOrigin,
) -> Result<bool, ValidatorUnknown> {
let validator_index = statement.validator_index();
let validator_meta = match self.validator_meta.get_mut(&validator_index) {
None => return Err(ValidatorUnknown),
Some(m) => m,
};
let compact = statement.payload().clone();
let fingerprint = (validator_index, compact.clone());
match self.known_statements.entry(fingerprint) {
HEntry::Occupied(mut e) => {
if let StatementOrigin::Local = origin {
e.get_mut().known_by_backing = true;
}
return Ok(false)
},
HEntry::Vacant(e) => {
e.insert(StoredStatement { statement, known_by_backing: origin.is_local() });
},
}
let candidate_hash = *compact.candidate_hash();
let seconded = if let CompactStatement::Seconded(_) = compact { true } else { false };
// cross-reference updates.
{
let group_index = validator_meta.group;
let group = match groups.get(group_index) {
Some(g) => g,
None => {
gum::error!(
target: crate::LOG_TARGET,
?group_index,
"groups passed into `insert` differ from those used at store creation"
);
return Err(ValidatorUnknown)
},
};
let group_statements = self
.group_statements
.entry((group_index, candidate_hash))
.or_insert_with(|| GroupStatements::with_group_size(group.len()));
if seconded {
validator_meta.seconded_count += 1;
group_statements.note_seconded(validator_meta.within_group_index);
} else {
group_statements.note_validated(validator_meta.within_group_index);
}
}
Ok(true)
}
/// Fill a `StatementFilter` to be used in the grid topology with all statements
/// we are already aware of.
pub fn fill_statement_filter(
&self,
group_index: GroupIndex,
candidate_hash: CandidateHash,
statement_filter: &mut StatementFilter,
) {
if let Some(statements) = self.group_statements.get(&(group_index, candidate_hash)) {
statement_filter.seconded_in_group |= statements.seconded.as_bitslice();
statement_filter.validated_in_group |= statements.valid.as_bitslice();
}
}
/// Get an iterator over stored signed statements by the group conforming to the
/// given filter.
///
/// Seconded statements are provided first.
pub fn group_statements<'a>(
&'a self,
groups: &'a Groups,
group_index: GroupIndex,
candidate_hash: CandidateHash,
filter: &'a StatementFilter,
) -> impl Iterator<Item = &'a SignedStatement> + 'a {
let group_validators = groups.get(group_index);
let seconded_statements = filter
.seconded_in_group
.iter_ones()
.filter_map(move |i| group_validators.as_ref().and_then(|g| g.get(i)))
.filter_map(move |v| {
self.known_statements.get(&(*v, CompactStatement::Seconded(candidate_hash)))
})
.map(|s| &s.statement);
let valid_statements = filter
.validated_in_group
.iter_ones()
.filter_map(move |i| group_validators.as_ref().and_then(|g| g.get(i)))
.filter_map(move |v| {
self.known_statements.get(&(*v, CompactStatement::Valid(candidate_hash)))
})
.map(|s| &s.statement);
seconded_statements.chain(valid_statements)
}
/// Get the full statement of this kind issued by this validator, if it is known.
pub fn validator_statement(
&self,
validator_index: ValidatorIndex,
statement: CompactStatement,
) -> Option<&SignedStatement> {
self.known_statements.get(&(validator_index, statement)).map(|s| &s.statement)
}
/// Get an iterator over all statements marked as being unknown by the backing subsystem.
pub fn fresh_statements_for_backing<'a>(
&'a self,
validators: &'a [ValidatorIndex],
candidate_hash: CandidateHash,
) -> impl Iterator<Item = &SignedStatement> + 'a {
let s_st = CompactStatement::Seconded(candidate_hash);
let v_st = CompactStatement::Valid(candidate_hash);
validators
.iter()
.flat_map(move |v| {
let a = self.known_statements.get(&(*v, s_st.clone()));
let b = self.known_statements.get(&(*v, v_st.clone()));
a.into_iter().chain(b)
})
.filter(|stored| !stored.known_by_backing)
.map(|stored| &stored.statement)
}
/// Get the amount of known `Seconded` statements by the given validator index.
pub fn seconded_count(&self, validator_index: &ValidatorIndex) -> usize {
self.validator_meta.get(validator_index).map_or(0, |m| m.seconded_count)
}
/// Note that a statement is known by the backing subsystem.
pub fn note_known_by_backing(
&mut self,
validator_index: ValidatorIndex,
statement: CompactStatement,
) {
if let Some(stored) = self.known_statements.get_mut(&(validator_index, statement)) {
stored.known_by_backing = true;
}
}
}
/// Error indicating that the validator was unknown.
pub struct ValidatorUnknown;
type Fingerprint = (ValidatorIndex, CompactStatement);
struct ValidatorMeta {
group: GroupIndex,
within_group_index: usize,
seconded_count: usize,
}
struct GroupStatements {
seconded: BitVec<u8, BitOrderLsb0>,
valid: BitVec<u8, BitOrderLsb0>,
}
impl GroupStatements {
fn with_group_size(group_size: usize) -> Self {
GroupStatements {
seconded: BitVec::repeat(false, group_size),
valid: BitVec::repeat(false, group_size),
}
}
fn note_seconded(&mut self, within_group_index: usize) {
self.seconded.set(within_group_index, true);
}
fn note_validated(&mut self, within_group_index: usize) {
self.valid.set(within_group_index, true);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,606 @@
// Copyright 2023 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
#![allow(clippy::clone_on_copy)]
use super::*;
use crate::*;
use polkadot_node_network_protocol::{
grid_topology::TopologyPeerInfo,
request_response::{outgoing::Recipient, ReqProtocolNames},
view, ObservedRole,
};
use polkadot_node_primitives::Statement;
use polkadot_node_subsystem::messages::{
network_bridge_event::NewGossipTopology, AllMessages, ChainApiMessage, FragmentTreeMembership,
HypotheticalCandidate, NetworkBridgeEvent, ProspectiveParachainsMessage, ReportPeerMessage,
RuntimeApiMessage, RuntimeApiRequest,
};
use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_node_subsystem_types::{jaeger, ActivatedLeaf, LeafStatus};
use polkadot_node_subsystem_util::TimeoutExt;
use polkadot_primitives::vstaging::{
AssignmentPair, AsyncBackingParams, BlockNumber, CommittedCandidateReceipt, CoreState,
GroupRotationInfo, HeadData, Header, IndexedVec, PersistedValidationData, ScheduledCore,
SessionIndex, SessionInfo, ValidatorPair,
};
use sc_keystore::LocalKeystore;
use sp_application_crypto::Pair as PairT;
use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair;
use sp_keyring::Sr25519Keyring;
use assert_matches::assert_matches;
use futures::Future;
use parity_scale_codec::Encode;
use rand::{Rng, SeedableRng};
use std::sync::Arc;
mod cluster;
mod grid;
mod requests;
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<StatementDistributionMessage>;
const DEFAULT_ASYNC_BACKING_PARAMETERS: AsyncBackingParams =
AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 };
// Some deterministic genesis hash for req/res protocol names
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
struct TestConfig {
validator_count: usize,
// how many validators to place in each group.
group_size: usize,
// whether the local node should be a validator
local_validator: bool,
async_backing_params: Option<AsyncBackingParams>,
}
#[derive(Debug, Clone)]
struct TestLocalValidator {
validator_index: ValidatorIndex,
group_index: GroupIndex,
}
struct TestState {
config: TestConfig,
local: Option<TestLocalValidator>,
validators: Vec<ValidatorPair>,
session_info: SessionInfo,
req_sender: async_channel::Sender<sc_network::config::IncomingRequest>,
}
impl TestState {
fn from_config(
config: TestConfig,
req_sender: async_channel::Sender<sc_network::config::IncomingRequest>,
rng: &mut impl Rng,
) -> Self {
if config.group_size == 0 {
panic!("group size cannot be 0");
}
let mut validators = Vec::new();
let mut discovery_keys = Vec::new();
let mut assignment_keys = Vec::new();
let mut validator_groups = Vec::new();
let local_validator_pos = if config.local_validator {
// ensure local validator is always in a full group.
Some(rng.gen_range(0..config.validator_count).saturating_sub(config.group_size - 1))
} else {
None
};
for i in 0..config.validator_count {
let validator_pair = if Some(i) == local_validator_pos {
// Note: the specific key is used to ensure the keystore holds
// this key and the subsystem can detect that it is a validator.
Sr25519Keyring::Ferdie.pair().into()
} else {
ValidatorPair::generate().0
};
let assignment_id = AssignmentPair::generate().0.public();
let discovery_id = AuthorityDiscoveryPair::generate().0.public();
let group_index = i / config.group_size;
validators.push(validator_pair);
discovery_keys.push(discovery_id);
assignment_keys.push(assignment_id);
if validator_groups.len() == group_index {
validator_groups.push(vec![ValidatorIndex(i as _)]);
} else {
validator_groups.last_mut().unwrap().push(ValidatorIndex(i as _));
}
}
let local = if let Some(local_pos) = local_validator_pos {
Some(TestLocalValidator {
validator_index: ValidatorIndex(local_pos as _),
group_index: GroupIndex((local_pos / config.group_size) as _),
})
} else {
None
};
let validator_public = validator_pubkeys(&validators);
let session_info = SessionInfo {
validators: validator_public,
discovery_keys,
validator_groups: IndexedVec::from(validator_groups),
assignment_keys,
n_cores: 0,
zeroth_delay_tranche_width: 0,
relay_vrf_modulo_samples: 0,
n_delay_tranches: 0,
no_show_slots: 0,
needed_approvals: 0,
active_validator_indices: vec![],
dispute_period: 6,
random_seed: [0u8; 32],
};
TestState { config, local, validators, session_info, req_sender }
}
fn make_dummy_leaf(&self, relay_parent: Hash) -> TestLeaf {
TestLeaf {
number: 1,
hash: relay_parent,
parent_hash: Hash::repeat_byte(0),
session: 1,
availability_cores: self.make_availability_cores(|i| {
CoreState::Scheduled(ScheduledCore {
para_id: ParaId::from(i as u32),
collator: None,
})
}),
para_data: (0..self.session_info.validator_groups.len())
.map(|i| (ParaId::from(i as u32), PerParaData::new(1, vec![1, 2, 3].into())))
.collect(),
}
}
fn make_availability_cores(&self, f: impl Fn(usize) -> CoreState) -> Vec<CoreState> {
(0..self.session_info.validator_groups.len()).map(f).collect()
}
fn make_dummy_topology(&self) -> NewGossipTopology {
let validator_count = self.config.validator_count;
NewGossipTopology {
session: 1,
topology: SessionGridTopology::new(
(0..validator_count).collect(),
(0..validator_count)
.map(|i| TopologyPeerInfo {
peer_ids: Vec::new(),
validator_index: ValidatorIndex(i as u32),
discovery_id: AuthorityDiscoveryPair::generate().0.public(),
})
.collect(),
),
local_index: self.local.as_ref().map(|local| local.validator_index),
}
}
fn group_validators(
&self,
group_index: GroupIndex,
exclude_local: bool,
) -> Vec<ValidatorIndex> {
self.session_info
.validator_groups
.get(group_index)
.unwrap()
.iter()
.cloned()
.filter(|&i| {
self.local.as_ref().map_or(true, |l| !exclude_local || l.validator_index != i)
})
.collect()
}
fn discovery_id(&self, validator_index: ValidatorIndex) -> AuthorityDiscoveryId {
self.session_info.discovery_keys[validator_index.0 as usize].clone()
}
fn sign_statement(
&self,
validator_index: ValidatorIndex,
statement: CompactStatement,
context: &SigningContext,
) -> SignedStatement {
let payload = statement.signing_payload(context);
let pair = &self.validators[validator_index.0 as usize];
let signature = pair.sign(&payload[..]);
SignedStatement::new(statement, validator_index, signature, context, &pair.public())
.unwrap()
}
fn sign_full_statement(
&self,
validator_index: ValidatorIndex,
statement: Statement,
context: &SigningContext,
pvd: PersistedValidationData,
) -> SignedFullStatementWithPVD {
let payload = statement.to_compact().signing_payload(context);
let pair = &self.validators[validator_index.0 as usize];
let signature = pair.sign(&payload[..]);
SignedFullStatementWithPVD::new(
statement.supply_pvd(pvd),
validator_index,
signature,
context,
&pair.public(),
)
.unwrap()
}
// send a request out, returning a future which expects a response.
async fn send_request(
&mut self,
peer: PeerId,
request: AttestedCandidateRequest,
) -> impl Future<Output = sc_network::config::OutgoingResponse> {
let (tx, rx) = futures::channel::oneshot::channel();
let req = sc_network::config::IncomingRequest {
peer,
payload: request.encode(),
pending_response: tx,
};
self.req_sender.send(req).await.unwrap();
rx.map(|r| r.unwrap())
}
}
fn test_harness<T: Future<Output = VirtualOverseer>>(
config: TestConfig,
test: impl FnOnce(TestState, VirtualOverseer) -> T,
) {
let pool = sp_core::testing::TaskExecutor::new();
let keystore = if config.local_validator {
test_helpers::mock::make_ferdie_keystore()
} else {
Arc::new(LocalKeystore::in_memory()) as KeystorePtr
};
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
let (statement_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
let (candidate_req_receiver, req_cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names);
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let test_state = TestState::from_config(config, req_cfg.inbound_queue.unwrap(), &mut rng);
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
let subsystem = async move {
let subsystem = crate::StatementDistributionSubsystem {
keystore,
v1_req_receiver: Some(statement_req_receiver),
req_receiver: Some(candidate_req_receiver),
metrics: Default::default(),
rng,
reputation: ReputationAggregator::new(|_| true),
};
if let Err(e) = subsystem.run(context).await {
panic!("Fatal error: {:?}", e);
}
};
let test_fut = test(test_state, virtual_overseer);
futures::pin_mut!(test_fut);
futures::pin_mut!(subsystem);
futures::executor::block_on(future::join(
async move {
let mut virtual_overseer = test_fut.await;
// Ensure we have handled all responses.
if let Ok(Some(msg)) = virtual_overseer.rx.try_next() {
panic!("Did not handle all responses: {:?}", msg);
}
// Conclude.
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
},
subsystem,
));
}
struct PerParaData {
min_relay_parent: BlockNumber,
head_data: HeadData,
}
impl PerParaData {
pub fn new(min_relay_parent: BlockNumber, head_data: HeadData) -> Self {
Self { min_relay_parent, head_data }
}
}
struct TestLeaf {
number: BlockNumber,
hash: Hash,
parent_hash: Hash,
session: SessionIndex,
availability_cores: Vec<CoreState>,
para_data: Vec<(ParaId, PerParaData)>,
}
impl TestLeaf {
pub fn para_data(&self, para_id: ParaId) -> &PerParaData {
self.para_data
.iter()
.find_map(|(p_id, data)| if *p_id == para_id { Some(data) } else { None })
.unwrap()
}
}
async fn activate_leaf(
virtual_overseer: &mut VirtualOverseer,
leaf: &TestLeaf,
test_state: &TestState,
expect_session_info_request: bool,
) {
let activated = ActivatedLeaf {
hash: leaf.hash,
number: leaf.number,
status: LeafStatus::Fresh,
span: Arc::new(jaeger::Span::Disabled),
};
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(
activated,
))))
.await;
handle_leaf_activation(virtual_overseer, leaf, test_state, expect_session_info_request).await;
}
async fn handle_leaf_activation(
virtual_overseer: &mut VirtualOverseer,
leaf: &TestLeaf,
test_state: &TestState,
expect_session_info_request: bool,
) {
let TestLeaf { number, hash, parent_hash, para_data, session, availability_cores } = leaf;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::StagingAsyncBackingParams(tx))
) if parent == *hash => {
tx.send(Ok(test_state.config.async_backing_params.unwrap_or(DEFAULT_ASYNC_BACKING_PARAMETERS))).unwrap();
}
);
let mrp_response: Vec<(ParaId, BlockNumber)> = para_data
.iter()
.map(|(para_id, data)| (*para_id, data.min_relay_parent))
.collect();
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetMinimumRelayParents(parent, tx)
) if parent == *hash => {
tx.send(mrp_response).unwrap();
}
);
let header = Header {
parent_hash: *parent_hash,
number: *number,
state_root: Hash::zero(),
extrinsics_root: Hash::zero(),
digest: Default::default(),
};
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ChainApi(
ChainApiMessage::BlockHeader(parent, tx)
) if parent == *hash => {
tx.send(Ok(Some(header))).unwrap();
}
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx))) if parent == *hash => {
tx.send(Ok(*session)).unwrap();
}
);
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx))) if parent == *hash => {
tx.send(Ok(availability_cores.clone())).unwrap();
}
);
let validator_groups = test_state.session_info.validator_groups.to_vec();
let group_rotation_info =
GroupRotationInfo { session_start_block: 1, group_rotation_frequency: 12, now: 1 };
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx))) if parent == *hash => {
tx.send(Ok((validator_groups, group_rotation_info))).unwrap();
}
);
if expect_session_info_request {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionInfo(s, tx))) if parent == *hash && s == *session => {
tx.send(Ok(Some(test_state.session_info.clone()))).unwrap();
}
);
}
}
/// Intercepts an outgoing request, checks the fields, and sends the response.
async fn handle_sent_request(
virtual_overseer: &mut VirtualOverseer,
peer: PeerId,
candidate_hash: CandidateHash,
mask: StatementFilter,
candidate_receipt: CommittedCandidateReceipt,
persisted_validation_data: PersistedValidationData,
statements: Vec<UncheckedSignedStatement>,
) {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendRequests(mut requests, IfDisconnected::ImmediateError)) => {
assert_eq!(requests.len(), 1);
assert_matches!(
requests.pop().unwrap(),
Requests::AttestedCandidateVStaging(outgoing) => {
assert_eq!(outgoing.peer, Recipient::Peer(peer));
assert_eq!(outgoing.payload.candidate_hash, candidate_hash);
assert_eq!(outgoing.payload.mask, mask);
let res = AttestedCandidateResponse {
candidate_receipt,
persisted_validation_data,
statements,
};
outgoing.pending_response.send(Ok(res.encode())).unwrap();
}
);
}
);
}
async fn answer_expected_hypothetical_depth_request(
virtual_overseer: &mut VirtualOverseer,
responses: Vec<(HypotheticalCandidate, FragmentTreeMembership)>,
expected_leaf_hash: Option<Hash>,
expected_backed_in_path_only: bool,
) {
assert_matches!(
virtual_overseer.recv().await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetHypotheticalFrontier(req, tx)
) => {
assert_eq!(req.fragment_tree_relay_parent, expected_leaf_hash);
assert_eq!(req.backed_in_path_only, expected_backed_in_path_only);
for (i, (candidate, _)) in responses.iter().enumerate() {
assert!(
req.candidates.iter().any(|c| &c == &candidate),
"did not receive request for hypothetical candidate {}",
i,
);
}
tx.send(responses).unwrap();
}
)
}
fn validator_pubkeys(val_ids: &[ValidatorPair]) -> IndexedVec<ValidatorIndex, ValidatorId> {
val_ids.iter().map(|v| v.public().into()).collect()
}
async fn connect_peer(
virtual_overseer: &mut VirtualOverseer,
peer: PeerId,
authority_ids: Option<HashSet<AuthorityDiscoveryId>>,
) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Authority,
ValidationVersion::VStaging.into(),
authority_ids,
),
),
})
.await;
}
// TODO: Add some tests using this?
#[allow(dead_code)]
async fn disconnect_peer(virtual_overseer: &mut VirtualOverseer, peer: PeerId) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerDisconnected(peer),
),
})
.await;
}
async fn send_peer_view_change(virtual_overseer: &mut VirtualOverseer, peer: PeerId, view: View) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerViewChange(peer, view),
),
})
.await;
}
async fn send_peer_message(
virtual_overseer: &mut VirtualOverseer,
peer: PeerId,
message: protocol_vstaging::StatementDistributionMessage,
) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(peer, Versioned::VStaging(message)),
),
})
.await;
}
async fn send_new_topology(virtual_overseer: &mut VirtualOverseer, topology: NewGossipTopology) {
virtual_overseer
.send(FromOrchestra::Communication {
msg: StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::NewGossipTopology(topology),
),
})
.await;
}
async fn overseer_recv_with_timeout(
overseer: &mut VirtualOverseer,
timeout: Duration,
) -> Option<AllMessages> {
gum::trace!("waiting for message...");
overseer.recv().timeout(timeout).await
}
fn next_group_index(
group_index: GroupIndex,
validator_count: usize,
group_size: usize,
) -> GroupIndex {
let next_group = group_index.0 + 1;
let num_groups =
validator_count / group_size + if validator_count % group_size > 0 { 1 } else { 0 };
GroupIndex::from(next_group % num_groups as u32)
}
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -89,6 +89,7 @@ pub fn dummy_overseer_builder<Spawner, SupportsParachains>(
DummySubsystem, DummySubsystem,
DummySubsystem, DummySubsystem,
DummySubsystem, DummySubsystem,
DummySubsystem,
>, >,
SubsystemError, SubsystemError,
> >
@@ -131,6 +132,7 @@ pub fn one_for_all_overseer_builder<Spawner, SupportsParachains, Sub>(
Sub, Sub,
Sub, Sub,
Sub, Sub,
Sub,
>, >,
SubsystemError, SubsystemError,
> >
@@ -159,7 +161,8 @@ where
+ Subsystem<OverseerSubsystemContext<DisputeCoordinatorMessage>, SubsystemError> + Subsystem<OverseerSubsystemContext<DisputeCoordinatorMessage>, SubsystemError>
+ Subsystem<OverseerSubsystemContext<DisputeDistributionMessage>, SubsystemError> + Subsystem<OverseerSubsystemContext<DisputeDistributionMessage>, SubsystemError>
+ Subsystem<OverseerSubsystemContext<ChainSelectionMessage>, SubsystemError> + Subsystem<OverseerSubsystemContext<ChainSelectionMessage>, SubsystemError>
+ Subsystem<OverseerSubsystemContext<PvfCheckerMessage>, SubsystemError>, + Subsystem<OverseerSubsystemContext<PvfCheckerMessage>, SubsystemError>
+ Subsystem<OverseerSubsystemContext<ProspectiveParachainsMessage>, SubsystemError>,
{ {
let metrics = <OverseerMetrics as MetricsTrait>::register(registry)?; let metrics = <OverseerMetrics as MetricsTrait>::register(registry)?;
@@ -185,7 +188,8 @@ where
.gossip_support(subsystem.clone()) .gossip_support(subsystem.clone())
.dispute_coordinator(subsystem.clone()) .dispute_coordinator(subsystem.clone())
.dispute_distribution(subsystem.clone()) .dispute_distribution(subsystem.clone())
.chain_selection(subsystem) .chain_selection(subsystem.clone())
.prospective_parachains(subsystem.clone())
.activation_external_listeners(Default::default()) .activation_external_listeners(Default::default())
.span_per_active_leaf(Default::default()) .span_per_active_leaf(Default::default())
.active_leaves(Default::default()) .active_leaves(Default::default())
+15 -1
View File
@@ -81,7 +81,8 @@ use polkadot_node_subsystem_types::messages::{
CandidateBackingMessage, CandidateValidationMessage, ChainApiMessage, ChainSelectionMessage, CandidateBackingMessage, CandidateValidationMessage, ChainApiMessage, ChainSelectionMessage,
CollationGenerationMessage, CollatorProtocolMessage, DisputeCoordinatorMessage, CollationGenerationMessage, CollatorProtocolMessage, DisputeCoordinatorMessage,
DisputeDistributionMessage, GossipSupportMessage, NetworkBridgeRxMessage, DisputeDistributionMessage, GossipSupportMessage, NetworkBridgeRxMessage,
NetworkBridgeTxMessage, ProvisionerMessage, RuntimeApiMessage, StatementDistributionMessage, NetworkBridgeTxMessage, ProspectiveParachainsMessage, ProvisionerMessage, RuntimeApiMessage,
StatementDistributionMessage,
}; };
pub use polkadot_node_subsystem_types::{ pub use polkadot_node_subsystem_types::{
@@ -466,11 +467,13 @@ pub struct Overseer<SupportsParachains> {
#[subsystem(CandidateBackingMessage, sends: [ #[subsystem(CandidateBackingMessage, sends: [
CandidateValidationMessage, CandidateValidationMessage,
CollatorProtocolMessage, CollatorProtocolMessage,
ChainApiMessage,
AvailabilityDistributionMessage, AvailabilityDistributionMessage,
AvailabilityStoreMessage, AvailabilityStoreMessage,
StatementDistributionMessage, StatementDistributionMessage,
ProvisionerMessage, ProvisionerMessage,
RuntimeApiMessage, RuntimeApiMessage,
ProspectiveParachainsMessage,
])] ])]
candidate_backing: CandidateBacking, candidate_backing: CandidateBacking,
@@ -478,6 +481,8 @@ pub struct Overseer<SupportsParachains> {
NetworkBridgeTxMessage, NetworkBridgeTxMessage,
CandidateBackingMessage, CandidateBackingMessage,
RuntimeApiMessage, RuntimeApiMessage,
ProspectiveParachainsMessage,
ChainApiMessage,
])] ])]
statement_distribution: StatementDistribution, statement_distribution: StatementDistribution,
@@ -516,6 +521,7 @@ pub struct Overseer<SupportsParachains> {
CandidateBackingMessage, CandidateBackingMessage,
ChainApiMessage, ChainApiMessage,
DisputeCoordinatorMessage, DisputeCoordinatorMessage,
ProspectiveParachainsMessage,
])] ])]
provisioner: Provisioner, provisioner: Provisioner,
@@ -555,6 +561,8 @@ pub struct Overseer<SupportsParachains> {
NetworkBridgeTxMessage, NetworkBridgeTxMessage,
RuntimeApiMessage, RuntimeApiMessage,
CandidateBackingMessage, CandidateBackingMessage,
ChainApiMessage,
ProspectiveParachainsMessage,
])] ])]
collator_protocol: CollatorProtocol, collator_protocol: CollatorProtocol,
@@ -605,6 +613,12 @@ pub struct Overseer<SupportsParachains> {
#[subsystem(blocking, ChainSelectionMessage, sends: [ChainApiMessage])] #[subsystem(blocking, ChainSelectionMessage, sends: [ChainApiMessage])]
chain_selection: ChainSelection, chain_selection: ChainSelection,
#[subsystem(ProspectiveParachainsMessage, sends: [
RuntimeApiMessage,
ChainApiMessage,
])]
prospective_parachains: ProspectiveParachains,
/// External listeners waiting for a hash to be in the active-leave set. /// External listeners waiting for a hash to be in the active-leave set.
pub activation_external_listeners: HashMap<Hash, Vec<oneshot::Sender<SubsystemResult<()>>>>, pub activation_external_listeners: HashMap<Hash, Vec<oneshot::Sender<SubsystemResult<()>>>>,
+19 -5
View File
@@ -30,8 +30,8 @@ use polkadot_node_subsystem_types::{
ActivatedLeaf, LeafStatus, ActivatedLeaf, LeafStatus,
}; };
use polkadot_primitives::{ use polkadot_primitives::{
CandidateHash, CandidateReceipt, CollatorPair, InvalidDisputeStatementKind, PvfExecTimeoutKind, CandidateHash, CandidateReceipt, CollatorPair, Id as ParaId, InvalidDisputeStatementKind,
SessionIndex, ValidDisputeStatementKind, ValidatorIndex, PvfExecTimeoutKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex,
}; };
use crate::{ use crate::{
@@ -786,7 +786,7 @@ fn test_candidate_validation_msg() -> CandidateValidationMessage {
fn test_candidate_backing_msg() -> CandidateBackingMessage { fn test_candidate_backing_msg() -> CandidateBackingMessage {
let (sender, _) = oneshot::channel(); let (sender, _) = oneshot::channel();
CandidateBackingMessage::GetBackedCandidates(Default::default(), Vec::new(), sender) CandidateBackingMessage::GetBackedCandidates(Vec::new(), sender)
} }
fn test_chain_api_msg() -> ChainApiMessage { fn test_chain_api_msg() -> ChainApiMessage {
@@ -797,7 +797,7 @@ fn test_chain_api_msg() -> ChainApiMessage {
fn test_collator_generation_msg() -> CollationGenerationMessage { fn test_collator_generation_msg() -> CollationGenerationMessage {
CollationGenerationMessage::Initialize(CollationGenerationConfig { CollationGenerationMessage::Initialize(CollationGenerationConfig {
key: CollatorPair::generate().0, key: CollatorPair::generate().0,
collator: Box::new(|_, _| TestCollator.boxed()), collator: Some(Box::new(|_, _| TestCollator.boxed())),
para_id: Default::default(), para_id: Default::default(),
}) })
} }
@@ -912,10 +912,17 @@ fn test_chain_selection_msg() -> ChainSelectionMessage {
ChainSelectionMessage::Approved(Default::default()) ChainSelectionMessage::Approved(Default::default())
} }
fn test_prospective_parachains_msg() -> ProspectiveParachainsMessage {
ProspectiveParachainsMessage::CandidateBacked(
ParaId::from(5),
CandidateHash(Hash::repeat_byte(0)),
)
}
// Checks that `stop`, `broadcast_signal` and `broadcast_message` are implemented correctly. // Checks that `stop`, `broadcast_signal` and `broadcast_message` are implemented correctly.
#[test] #[test]
fn overseer_all_subsystems_receive_signals_and_messages() { fn overseer_all_subsystems_receive_signals_and_messages() {
const NUM_SUBSYSTEMS: usize = 22; const NUM_SUBSYSTEMS: usize = 23;
// -4 for BitfieldSigning, GossipSupport, AvailabilityDistribution and PvfCheckerSubsystem. // -4 for BitfieldSigning, GossipSupport, AvailabilityDistribution and PvfCheckerSubsystem.
const NUM_SUBSYSTEMS_MESSAGED: usize = NUM_SUBSYSTEMS - 4; const NUM_SUBSYSTEMS_MESSAGED: usize = NUM_SUBSYSTEMS - 4;
@@ -1003,6 +1010,9 @@ fn overseer_all_subsystems_receive_signals_and_messages() {
handle handle
.send_msg_anon(AllMessages::ChainSelection(test_chain_selection_msg())) .send_msg_anon(AllMessages::ChainSelection(test_chain_selection_msg()))
.await; .await;
handle
.send_msg_anon(AllMessages::ProspectiveParachains(test_prospective_parachains_msg()))
.await;
// handle.send_msg_anon(AllMessages::PvfChecker(test_pvf_checker_msg())).await; // handle.send_msg_anon(AllMessages::PvfChecker(test_pvf_checker_msg())).await;
// Wait until all subsystems have received. Otherwise the messages might race against // Wait until all subsystems have received. Otherwise the messages might race against
@@ -1059,6 +1069,7 @@ fn context_holds_onto_message_until_enough_signals_received() {
let (dispute_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); let (dispute_distribution_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY);
let (chain_selection_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); let (chain_selection_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY);
let (pvf_checker_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); let (pvf_checker_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY);
let (prospective_parachains_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY);
let (candidate_validation_unbounded_tx, _) = metered::unbounded(); let (candidate_validation_unbounded_tx, _) = metered::unbounded();
let (candidate_backing_unbounded_tx, _) = metered::unbounded(); let (candidate_backing_unbounded_tx, _) = metered::unbounded();
@@ -1082,6 +1093,7 @@ fn context_holds_onto_message_until_enough_signals_received() {
let (dispute_distribution_unbounded_tx, _) = metered::unbounded(); let (dispute_distribution_unbounded_tx, _) = metered::unbounded();
let (chain_selection_unbounded_tx, _) = metered::unbounded(); let (chain_selection_unbounded_tx, _) = metered::unbounded();
let (pvf_checker_unbounded_tx, _) = metered::unbounded(); let (pvf_checker_unbounded_tx, _) = metered::unbounded();
let (prospective_parachains_unbounded_tx, _) = metered::unbounded();
let channels_out = ChannelsOut { let channels_out = ChannelsOut {
candidate_validation: candidate_validation_bounded_tx.clone(), candidate_validation: candidate_validation_bounded_tx.clone(),
@@ -1106,6 +1118,7 @@ fn context_holds_onto_message_until_enough_signals_received() {
dispute_distribution: dispute_distribution_bounded_tx.clone(), dispute_distribution: dispute_distribution_bounded_tx.clone(),
chain_selection: chain_selection_bounded_tx.clone(), chain_selection: chain_selection_bounded_tx.clone(),
pvf_checker: pvf_checker_bounded_tx.clone(), pvf_checker: pvf_checker_bounded_tx.clone(),
prospective_parachains: prospective_parachains_bounded_tx.clone(),
candidate_validation_unbounded: candidate_validation_unbounded_tx.clone(), candidate_validation_unbounded: candidate_validation_unbounded_tx.clone(),
candidate_backing_unbounded: candidate_backing_unbounded_tx.clone(), candidate_backing_unbounded: candidate_backing_unbounded_tx.clone(),
@@ -1129,6 +1142,7 @@ fn context_holds_onto_message_until_enough_signals_received() {
dispute_distribution_unbounded: dispute_distribution_unbounded_tx.clone(), dispute_distribution_unbounded: dispute_distribution_unbounded_tx.clone(),
chain_selection_unbounded: chain_selection_unbounded_tx.clone(), chain_selection_unbounded: chain_selection_unbounded_tx.clone(),
pvf_checker_unbounded: pvf_checker_unbounded_tx.clone(), pvf_checker_unbounded: pvf_checker_unbounded_tx.clone(),
prospective_parachains_unbounded: prospective_parachains_unbounded_tx.clone(),
}; };
let (mut signal_tx, signal_rx) = metered::channel(CHANNEL_CAPACITY); let (mut signal_tx, signal_rx) = metered::channel(CHANNEL_CAPACITY);
+15 -11
View File
@@ -24,10 +24,10 @@ use parity_scale_codec::{Decode, Encode};
use sp_application_crypto::AppCrypto; use sp_application_crypto::AppCrypto;
use sp_keystore::{Error as KeystoreError, KeystorePtr}; use sp_keystore::{Error as KeystoreError, KeystorePtr};
use super::{Statement, UncheckedSignedFullStatement};
use polkadot_primitives::{ use polkadot_primitives::{
CandidateHash, CandidateReceipt, DisputeStatement, InvalidDisputeStatementKind, SessionIndex, CandidateHash, CandidateReceipt, CompactStatement, DisputeStatement, EncodeAs,
SigningContext, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature, InvalidDisputeStatementKind, SessionIndex, SigningContext, UncheckedSigned,
ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
}; };
/// `DisputeMessage` and related types. /// `DisputeMessage` and related types.
@@ -270,19 +270,23 @@ impl SignedDisputeStatement {
/// along with the signing context. /// along with the signing context.
/// ///
/// This does signature checks again with the data provided. /// This does signature checks again with the data provided.
pub fn from_backing_statement( pub fn from_backing_statement<T>(
backing_statement: &UncheckedSignedFullStatement, backing_statement: &UncheckedSigned<T, CompactStatement>,
signing_context: SigningContext, signing_context: SigningContext,
validator_public: ValidatorId, validator_public: ValidatorId,
) -> Result<Self, ()> { ) -> Result<Self, ()>
let (statement_kind, candidate_hash) = match backing_statement.unchecked_payload() { where
Statement::Seconded(candidate) => ( for<'a> &'a T: Into<CompactStatement>,
T: EncodeAs<CompactStatement>,
{
let (statement_kind, candidate_hash) = match backing_statement.unchecked_payload().into() {
CompactStatement::Seconded(candidate_hash) => (
ValidDisputeStatementKind::BackingSeconded(signing_context.parent_hash), ValidDisputeStatementKind::BackingSeconded(signing_context.parent_hash),
candidate.hash(), candidate_hash,
), ),
Statement::Valid(candidate_hash) => ( CompactStatement::Valid(candidate_hash) => (
ValidDisputeStatementKind::BackingValid(signing_context.parent_hash), ValidDisputeStatementKind::BackingValid(signing_context.parent_hash),
*candidate_hash, candidate_hash,
), ),
}; };
+138 -4
View File
@@ -32,8 +32,8 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use polkadot_primitives::{ use polkadot_primitives::{
BlakeTwo256, BlockNumber, CandidateCommitments, CandidateHash, CollatorPair, BlakeTwo256, BlockNumber, CandidateCommitments, CandidateHash, CollatorPair,
CommittedCandidateReceipt, CompactStatement, EncodeAs, Hash, HashT, HeadData, Id as ParaId, CommittedCandidateReceipt, CompactStatement, EncodeAs, Hash, HashT, HeadData, Id as ParaId,
PersistedValidationData, SessionIndex, Signed, UncheckedSigned, ValidationCode, ValidatorIndex, PersistedValidationData, SessionIndex, Signed, UncheckedSigned, ValidationCode,
MAX_CODE_SIZE, MAX_POV_SIZE, ValidationCodeHash, ValidatorIndex, MAX_CODE_SIZE, MAX_POV_SIZE,
}; };
pub use sp_consensus_babe::{ pub use sp_consensus_babe::{
AllowedSlots as BabeAllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch, AllowedSlots as BabeAllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch,
@@ -197,6 +197,14 @@ impl Statement {
Statement::Valid(hash) => CompactStatement::Valid(hash), Statement::Valid(hash) => CompactStatement::Valid(hash),
} }
} }
/// Add the [`PersistedValidationData`] to the statement, if seconded.
pub fn supply_pvd(self, pvd: PersistedValidationData) -> StatementWithPVD {
match self {
Statement::Seconded(c) => StatementWithPVD::Seconded(c, pvd),
Statement::Valid(hash) => StatementWithPVD::Valid(hash),
}
}
} }
impl From<&'_ Statement> for CompactStatement { impl From<&'_ Statement> for CompactStatement {
@@ -211,6 +219,84 @@ impl EncodeAs<CompactStatement> for Statement {
} }
} }
/// A statement, exactly the same as [`Statement`] but where seconded messages carry
/// the [`PersistedValidationData`].
#[derive(Clone, PartialEq, Eq)]
pub enum StatementWithPVD {
/// A statement that a validator seconds a candidate.
Seconded(CommittedCandidateReceipt, PersistedValidationData),
/// A statement that a validator has deemed a candidate valid.
Valid(CandidateHash),
}
impl std::fmt::Debug for StatementWithPVD {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StatementWithPVD::Seconded(seconded, _) =>
write!(f, "Seconded: {:?}", seconded.descriptor),
StatementWithPVD::Valid(hash) => write!(f, "Valid: {:?}", hash),
}
}
}
impl StatementWithPVD {
/// Get the candidate hash referenced by this statement.
///
/// If this is a `Statement::Seconded`, this does hash the candidate receipt, which may be
/// expensive for large candidates.
pub fn candidate_hash(&self) -> CandidateHash {
match *self {
StatementWithPVD::Valid(ref h) => *h,
StatementWithPVD::Seconded(ref c, _) => c.hash(),
}
}
/// Transform this statement into its compact version, which references only the hash
/// of the candidate.
pub fn to_compact(&self) -> CompactStatement {
match *self {
StatementWithPVD::Seconded(ref c, _) => CompactStatement::Seconded(c.hash()),
StatementWithPVD::Valid(hash) => CompactStatement::Valid(hash),
}
}
/// Drop the [`PersistedValidationData`] from the statement.
pub fn drop_pvd(self) -> Statement {
match self {
StatementWithPVD::Seconded(c, _) => Statement::Seconded(c),
StatementWithPVD::Valid(c_h) => Statement::Valid(c_h),
}
}
/// Drop the [`PersistedValidationData`] from the statement in a signed
/// variant.
pub fn drop_pvd_from_signed(signed: SignedFullStatementWithPVD) -> SignedFullStatement {
signed
.convert_to_superpayload_with(|s| s.drop_pvd())
.expect("persisted_validation_data doesn't affect encode_as; qed")
}
/// Converts the statement to a compact signed statement by dropping the
/// [`CommittedCandidateReceipt`] and the [`PersistedValidationData`].
pub fn signed_to_compact(signed: SignedFullStatementWithPVD) -> Signed<CompactStatement> {
signed
.convert_to_superpayload_with(|s| s.to_compact())
.expect("doesn't affect encode_as; qed")
}
}
impl From<&'_ StatementWithPVD> for CompactStatement {
fn from(stmt: &StatementWithPVD) -> Self {
stmt.to_compact()
}
}
impl EncodeAs<CompactStatement> for StatementWithPVD {
fn encode_as(&self) -> Vec<u8> {
self.to_compact().encode()
}
}
/// A statement, the corresponding signature, and the index of the sender. /// A statement, the corresponding signature, and the index of the sender.
/// ///
/// Signing context and validator set should be apparent from context. /// Signing context and validator set should be apparent from context.
@@ -222,6 +308,13 @@ pub type SignedFullStatement = Signed<Statement, CompactStatement>;
/// Variant of `SignedFullStatement` where the signature has not yet been verified. /// Variant of `SignedFullStatement` where the signature has not yet been verified.
pub type UncheckedSignedFullStatement = UncheckedSigned<Statement, CompactStatement>; pub type UncheckedSignedFullStatement = UncheckedSigned<Statement, CompactStatement>;
/// A statement, the corresponding signature, and the index of the sender.
///
/// Seconded statements are accompanied by the [`PersistedValidationData`]
///
/// Signing context and validator set should be apparent from context.
pub type SignedFullStatementWithPVD = Signed<StatementWithPVD, CompactStatement>;
/// Candidate invalidity details /// Candidate invalidity details
#[derive(Debug)] #[derive(Debug)]
pub enum InvalidCandidate { pub enum InvalidCandidate {
@@ -287,6 +380,18 @@ pub enum MaybeCompressedPoV {
Compressed(PoV), Compressed(PoV),
} }
#[cfg(not(target_os = "unknown"))]
impl std::fmt::Debug for MaybeCompressedPoV {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let (variant, size) = match self {
MaybeCompressedPoV::Raw(pov) => ("Raw", pov.block_data.0.len()),
MaybeCompressedPoV::Compressed(pov) => ("Compressed", pov.block_data.0.len()),
};
write!(f, "{} PoV ({} bytes)", variant, size)
}
}
#[cfg(not(target_os = "unknown"))] #[cfg(not(target_os = "unknown"))]
impl MaybeCompressedPoV { impl MaybeCompressedPoV {
/// Convert into a compressed [`PoV`]. /// Convert into a compressed [`PoV`].
@@ -306,7 +411,7 @@ impl MaybeCompressedPoV {
/// ///
/// - does not contain the erasure root; that's computed at the Polkadot level, not at Cumulus /// - does not contain the erasure root; that's computed at the Polkadot level, not at Cumulus
/// - contains a proof of validity. /// - contains a proof of validity.
#[derive(Clone, Encode, Decode)] #[derive(Debug, Clone, Encode, Decode)]
#[cfg(not(target_os = "unknown"))] #[cfg(not(target_os = "unknown"))]
pub struct Collation<BlockNumber = polkadot_primitives::BlockNumber> { pub struct Collation<BlockNumber = polkadot_primitives::BlockNumber> {
/// Messages destined to be interpreted by the Relay chain itself. /// Messages destined to be interpreted by the Relay chain itself.
@@ -384,7 +489,10 @@ pub struct CollationGenerationConfig {
/// Collator's authentication key, so it can sign things. /// Collator's authentication key, so it can sign things.
pub key: CollatorPair, pub key: CollatorPair,
/// Collation function. See [`CollatorFn`] for more details. /// Collation function. See [`CollatorFn`] for more details.
pub collator: CollatorFn, ///
/// If this is `None`, it implies that collations are intended to be submitted
/// out-of-band and not pulled out of the function.
pub collator: Option<CollatorFn>,
/// The parachain that this collator collates for /// The parachain that this collator collates for
pub para_id: ParaId, pub para_id: ParaId,
} }
@@ -396,6 +504,25 @@ impl std::fmt::Debug for CollationGenerationConfig {
} }
} }
/// Parameters for [`CollationGenerationMessage::SubmitCollation`].
#[derive(Debug)]
pub struct SubmitCollationParams {
/// The relay-parent the collation is built against.
pub relay_parent: Hash,
/// The collation itself (PoV and commitments)
pub collation: Collation,
/// The parent block's head-data.
pub parent_head: HeadData,
/// The hash of the validation code the collation was created against.
pub validation_code_hash: ValidationCodeHash,
/// An optional result sender that should be informed about a successfully seconded collation.
///
/// There is no guarantee that this sender is informed ever about any result, it is completely
/// okay to just drop it. However, if it is called, it should be called with the signed
/// statement of a parachain validator seconding the collation.
pub result_sender: Option<futures::channel::oneshot::Sender<CollationSecondedSignal>>,
}
/// This is the data we keep available for each candidate included in the relay chain. /// This is the data we keep available for each candidate included in the relay chain.
#[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)] #[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
pub struct AvailableData { pub struct AvailableData {
@@ -528,3 +655,10 @@ pub fn maybe_compress_pov(pov: PoV) -> PoV {
let pov = PoV { block_data: BlockData(raw) }; let pov = PoV { block_data: BlockData(raw) };
pov pov
} }
/// How many votes we need to consider a candidate backed.
///
/// WARNING: This has to be kept in sync with the runtime check in the inclusion module.
pub fn minimum_votes(n_validators: usize) -> usize {
std::cmp::min(2, n_validators)
}
+4
View File
@@ -135,6 +135,7 @@ polkadot-node-core-candidate-validation = { path = "../core/candidate-validation
polkadot-node-core-chain-api = { path = "../core/chain-api", optional = true } polkadot-node-core-chain-api = { path = "../core/chain-api", optional = true }
polkadot-node-core-chain-selection = { path = "../core/chain-selection", optional = true } polkadot-node-core-chain-selection = { path = "../core/chain-selection", optional = true }
polkadot-node-core-dispute-coordinator = { path = "../core/dispute-coordinator", optional = true } polkadot-node-core-dispute-coordinator = { path = "../core/dispute-coordinator", optional = true }
polkadot-node-core-prospective-parachains = { path = "../core/prospective-parachains", optional = true }
polkadot-node-core-provisioner = { path = "../core/provisioner", optional = true } polkadot-node-core-provisioner = { path = "../core/provisioner", optional = true }
polkadot-node-core-pvf = { path = "../core/pvf", optional = true } polkadot-node-core-pvf = { path = "../core/pvf", optional = true }
polkadot-node-core-pvf-checker = { path = "../core/pvf-checker", optional = true } polkadot-node-core-pvf-checker = { path = "../core/pvf-checker", optional = true }
@@ -173,6 +174,7 @@ full-node = [
"polkadot-node-core-chain-api", "polkadot-node-core-chain-api",
"polkadot-node-core-chain-selection", "polkadot-node-core-chain-selection",
"polkadot-node-core-dispute-coordinator", "polkadot-node-core-dispute-coordinator",
"polkadot-node-core-prospective-parachains",
"polkadot-node-core-provisioner", "polkadot-node-core-provisioner",
"polkadot-node-core-runtime-api", "polkadot-node-core-runtime-api",
"polkadot-statement-distribution", "polkadot-statement-distribution",
@@ -220,3 +222,5 @@ runtime-metrics = [
"polkadot-runtime?/runtime-metrics", "polkadot-runtime?/runtime-metrics",
"polkadot-runtime-parachains/runtime-metrics" "polkadot-runtime-parachains/runtime-metrics"
] ]
network-protocol-staging = ["polkadot-node-network-protocol/network-protocol-staging"]
+11 -2
View File
@@ -859,13 +859,20 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
net_config.add_request_response_protocol(cfg); net_config.add_request_response_protocol(cfg);
let (chunk_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); let (chunk_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names);
net_config.add_request_response_protocol(cfg); net_config.add_request_response_protocol(cfg);
let (collation_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); let (collation_req_v1_receiver, cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (collation_req_vstaging_receiver, cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names);
net_config.add_request_response_protocol(cfg); net_config.add_request_response_protocol(cfg);
let (available_data_req_receiver, cfg) = let (available_data_req_receiver, cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names); IncomingRequest::get_config_receiver(&req_protocol_names);
net_config.add_request_response_protocol(cfg); net_config.add_request_response_protocol(cfg);
let (statement_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); let (statement_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names);
net_config.add_request_response_protocol(cfg); net_config.add_request_response_protocol(cfg);
let (candidate_req_vstaging_receiver, cfg) =
IncomingRequest::get_config_receiver(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (dispute_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names); let (dispute_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names);
net_config.add_request_response_protocol(cfg); net_config.add_request_response_protocol(cfg);
@@ -1050,9 +1057,11 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
authority_discovery_service, authority_discovery_service,
pov_req_receiver, pov_req_receiver,
chunk_req_receiver, chunk_req_receiver,
collation_req_receiver, collation_req_v1_receiver,
collation_req_vstaging_receiver,
available_data_req_receiver, available_data_req_receiver,
statement_req_receiver, statement_req_receiver,
candidate_req_vstaging_receiver,
dispute_req_receiver, dispute_req_receiver,
registry: prometheus_registry.as_ref(), registry: prometheus_registry.as_ref(),
spawner, spawner,
+29 -9
View File
@@ -28,7 +28,9 @@ use polkadot_node_core_chain_selection::Config as ChainSelectionConfig;
use polkadot_node_core_dispute_coordinator::Config as DisputeCoordinatorConfig; use polkadot_node_core_dispute_coordinator::Config as DisputeCoordinatorConfig;
use polkadot_node_network_protocol::{ use polkadot_node_network_protocol::{
peer_set::PeerSetProtocolNames, peer_set::PeerSetProtocolNames,
request_response::{v1 as request_v1, IncomingRequestReceiver, ReqProtocolNames}, request_response::{
v1 as request_v1, vstaging as request_vstaging, IncomingRequestReceiver, ReqProtocolNames,
},
}; };
#[cfg(any(feature = "malus", test))] #[cfg(any(feature = "malus", test))]
pub use polkadot_overseer::{ pub use polkadot_overseer::{
@@ -70,6 +72,7 @@ pub use polkadot_node_core_candidate_validation::CandidateValidationSubsystem;
pub use polkadot_node_core_chain_api::ChainApiSubsystem; pub use polkadot_node_core_chain_api::ChainApiSubsystem;
pub use polkadot_node_core_chain_selection::ChainSelectionSubsystem; pub use polkadot_node_core_chain_selection::ChainSelectionSubsystem;
pub use polkadot_node_core_dispute_coordinator::DisputeCoordinatorSubsystem; pub use polkadot_node_core_dispute_coordinator::DisputeCoordinatorSubsystem;
pub use polkadot_node_core_prospective_parachains::ProspectiveParachainsSubsystem;
pub use polkadot_node_core_provisioner::ProvisionerSubsystem; pub use polkadot_node_core_provisioner::ProvisionerSubsystem;
pub use polkadot_node_core_pvf_checker::PvfCheckerSubsystem; pub use polkadot_node_core_pvf_checker::PvfCheckerSubsystem;
pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem; pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem;
@@ -95,13 +98,24 @@ where
pub sync_service: Arc<sc_network_sync::SyncingService<Block>>, pub sync_service: Arc<sc_network_sync::SyncingService<Block>>,
/// Underlying authority discovery service. /// Underlying authority discovery service.
pub authority_discovery_service: AuthorityDiscoveryService, pub authority_discovery_service: AuthorityDiscoveryService,
/// POV request receiver /// POV request receiver.
pub pov_req_receiver: IncomingRequestReceiver<request_v1::PoVFetchingRequest>, pub pov_req_receiver: IncomingRequestReceiver<request_v1::PoVFetchingRequest>,
/// Erasure chunks request receiver.
pub chunk_req_receiver: IncomingRequestReceiver<request_v1::ChunkFetchingRequest>, pub chunk_req_receiver: IncomingRequestReceiver<request_v1::ChunkFetchingRequest>,
pub collation_req_receiver: IncomingRequestReceiver<request_v1::CollationFetchingRequest>, /// Collations request receiver for network protocol v1.
pub collation_req_v1_receiver: IncomingRequestReceiver<request_v1::CollationFetchingRequest>,
/// Collations request receiver for network protocol vstaging.
pub collation_req_vstaging_receiver:
IncomingRequestReceiver<request_vstaging::CollationFetchingRequest>,
/// Receiver for available data requests.
pub available_data_req_receiver: pub available_data_req_receiver:
IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>, IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
/// Receiver for incoming large statement requests.
pub statement_req_receiver: IncomingRequestReceiver<request_v1::StatementFetchingRequest>, pub statement_req_receiver: IncomingRequestReceiver<request_v1::StatementFetchingRequest>,
/// Receiver for incoming candidate requests.
pub candidate_req_vstaging_receiver:
IncomingRequestReceiver<request_vstaging::AttestedCandidateRequest>,
/// Receiver for incoming disputes.
pub dispute_req_receiver: IncomingRequestReceiver<request_v1::DisputeRequest>, pub dispute_req_receiver: IncomingRequestReceiver<request_v1::DisputeRequest>,
/// Prometheus registry, commonly used for production systems, less so for test. /// Prometheus registry, commonly used for production systems, less so for test.
pub registry: Option<&'a Registry>, pub registry: Option<&'a Registry>,
@@ -143,9 +157,11 @@ pub fn prepared_overseer_builder<Spawner, RuntimeClient>(
authority_discovery_service, authority_discovery_service,
pov_req_receiver, pov_req_receiver,
chunk_req_receiver, chunk_req_receiver,
collation_req_receiver, collation_req_v1_receiver,
collation_req_vstaging_receiver,
available_data_req_receiver, available_data_req_receiver,
statement_req_receiver, statement_req_receiver,
candidate_req_vstaging_receiver,
dispute_req_receiver, dispute_req_receiver,
registry, registry,
spawner, spawner,
@@ -193,6 +209,7 @@ pub fn prepared_overseer_builder<Spawner, RuntimeClient>(
DisputeCoordinatorSubsystem, DisputeCoordinatorSubsystem,
DisputeDistributionSubsystem<AuthorityDiscoveryService>, DisputeDistributionSubsystem<AuthorityDiscoveryService>,
ChainSelectionSubsystem, ChainSelectionSubsystem,
ProspectiveParachainsSubsystem,
>, >,
Error, Error,
> >
@@ -267,12 +284,13 @@ where
.collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?))
.collator_protocol({ .collator_protocol({
let side = match is_parachain_node { let side = match is_parachain_node {
IsParachainNode::Collator(collator_pair) => ProtocolSide::Collator( IsParachainNode::Collator(collator_pair) => ProtocolSide::Collator {
network_service.local_peer_id(), peer_id: network_service.local_peer_id(),
collator_pair, collator_pair,
collation_req_receiver, request_receiver_v1: collation_req_v1_receiver,
Metrics::register(registry)?, request_receiver_vstaging: collation_req_vstaging_receiver,
), metrics: Metrics::register(registry)?,
},
IsParachainNode::FullNode => ProtocolSide::None, IsParachainNode::FullNode => ProtocolSide::None,
IsParachainNode::No => ProtocolSide::Validator { IsParachainNode::No => ProtocolSide::Validator {
keystore: keystore.clone(), keystore: keystore.clone(),
@@ -291,6 +309,7 @@ where
.statement_distribution(StatementDistributionSubsystem::new( .statement_distribution(StatementDistributionSubsystem::new(
keystore.clone(), keystore.clone(),
statement_req_receiver, statement_req_receiver,
candidate_req_vstaging_receiver,
Metrics::register(registry)?, Metrics::register(registry)?,
rand::rngs::StdRng::from_entropy(), rand::rngs::StdRng::from_entropy(),
)) ))
@@ -320,6 +339,7 @@ where
Metrics::register(registry)?, Metrics::register(registry)?,
)) ))
.chain_selection(ChainSelectionSubsystem::new(chain_selection_config, parachains_db)) .chain_selection(ChainSelectionSubsystem::new(chain_selection_config, parachains_db))
.prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?))
.activation_external_listeners(Default::default()) .activation_external_listeners(Default::default())
.span_per_active_leaf(Default::default()) .span_per_active_leaf(Default::default())
.active_leaves(Default::default()) .active_leaves(Default::default())
+254 -20
View File
@@ -35,16 +35,18 @@ use polkadot_node_primitives::{
approval::{BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote}, approval::{BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote},
AvailableData, BabeEpoch, BlockWeight, CandidateVotes, CollationGenerationConfig, AvailableData, BabeEpoch, BlockWeight, CandidateVotes, CollationGenerationConfig,
CollationSecondedSignal, DisputeMessage, DisputeStatus, ErasureChunk, PoV, CollationSecondedSignal, DisputeMessage, DisputeStatus, ErasureChunk, PoV,
SignedDisputeStatement, SignedFullStatement, ValidationResult, SignedDisputeStatement, SignedFullStatement, SignedFullStatementWithPVD, SubmitCollationParams,
ValidationResult,
}; };
use polkadot_primitives::{ use polkadot_primitives::{
slashing, AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, CandidateHash, slashing, vstaging as vstaging_primitives, AuthorityDiscoveryId, BackedCandidate, BlockNumber,
CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreState, CandidateEvent, CandidateHash, CandidateIndex, CandidateReceipt, CollatorId,
DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Header as BlockHeader, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupIndex,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, GroupRotationInfo, Hash, Header as BlockHeader, Id as ParaId, InboundDownwardMessage,
OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, PvfExecTimeoutKind, InboundHrmpMessage, MultiDisputeStatementSet, OccupiedCoreAssumption, PersistedValidationData,
SessionIndex, SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, PvfCheckStatement, PvfExecTimeoutKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield,
ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
ValidatorSignature,
}; };
use polkadot_statement_table::v2::Misbehavior; use polkadot_statement_table::v2::Misbehavior;
use std::{ use std::{
@@ -56,20 +58,42 @@ use std::{
pub mod network_bridge_event; pub mod network_bridge_event;
pub use network_bridge_event::NetworkBridgeEvent; pub use network_bridge_event::NetworkBridgeEvent;
/// A request to the candidate backing subsystem to check whether
/// there exists vacant membership in some fragment tree.
#[derive(Debug, Copy, Clone)]
pub struct CanSecondRequest {
/// Para id of the candidate.
pub candidate_para_id: ParaId,
/// The relay-parent of the candidate.
pub candidate_relay_parent: Hash,
/// Hash of the candidate.
pub candidate_hash: CandidateHash,
/// Parent head data hash.
pub parent_head_data_hash: Hash,
}
/// Messages received by the Candidate Backing subsystem. /// Messages received by the Candidate Backing subsystem.
#[derive(Debug)] #[derive(Debug)]
pub enum CandidateBackingMessage { pub enum CandidateBackingMessage {
/// Requests a set of backable candidates that could be backed in a child of the given /// Requests a set of backable candidates attested by the subsystem.
/// relay-parent, referenced by its hash. ///
GetBackedCandidates(Hash, Vec<CandidateHash>, oneshot::Sender<Vec<BackedCandidate>>), /// Each pair is (candidate_hash, candidate_relay_parent).
GetBackedCandidates(Vec<(CandidateHash, Hash)>, oneshot::Sender<Vec<BackedCandidate>>),
/// Request the subsystem to check whether it's allowed to second given candidate.
/// The rule is to only fetch collations that are either built on top of the root
/// of some fragment tree or have a parent node which represents backed candidate.
///
/// Always responses with `false` if async backing is disabled for candidate's relay
/// parent.
CanSecond(CanSecondRequest, oneshot::Sender<bool>),
/// Note that the Candidate Backing subsystem should second the given candidate in the context /// Note that the Candidate Backing subsystem should second the given candidate in the context
/// of the given relay-parent (ref. by hash). This candidate must be validated. /// of the given relay-parent (ref. by hash). This candidate must be validated.
Second(Hash, CandidateReceipt, PoV), Second(Hash, CandidateReceipt, PersistedValidationData, PoV),
/// Note a validator's statement about a particular candidate. Disagreements about validity /// Note a validator's statement about a particular candidate in the context of the given
/// must be escalated to a broader check by the Disputes Subsystem, though that escalation is /// relay-parent. Disagreements about validity must be escalated to a broader check by the
/// deferred until the approval voting stage to guarantee availability. Agreements are simply /// Disputes Subsystem, though that escalation is deferred until the approval voting stage to
/// tallied until a quorum is reached. /// guarantee availability. Agreements are simply tallied until a quorum is reached.
Statement(Hash, SignedFullStatement), Statement(Hash, SignedFullStatementWithPVD),
} }
/// Blanket error for validation failing for internal reasons. /// Blanket error for validation failing for internal reasons.
@@ -165,10 +189,16 @@ pub enum CollatorProtocolMessage {
/// This should be sent before any `DistributeCollation` message. /// This should be sent before any `DistributeCollation` message.
CollateOn(ParaId), CollateOn(ParaId),
/// Provide a collation to distribute to validators with an optional result sender. /// Provide a collation to distribute to validators with an optional result sender.
/// The second argument is the parent head-data hash.
/// ///
/// The result sender should be informed when at least one parachain validator seconded the /// The result sender should be informed when at least one parachain validator seconded the
/// collation. It is also completely okay to just drop the sender. /// collation. It is also completely okay to just drop the sender.
DistributeCollation(CandidateReceipt, PoV, Option<oneshot::Sender<CollationSecondedSignal>>), DistributeCollation(
CandidateReceipt,
Hash,
PoV,
Option<oneshot::Sender<CollationSecondedSignal>>,
),
/// Report a collator as having provided an invalid collation. This should lead to disconnect /// Report a collator as having provided an invalid collation. This should lead to disconnect
/// and blacklist of the collator. /// and blacklist of the collator.
ReportCollator(CollatorId), ReportCollator(CollatorId),
@@ -184,6 +214,13 @@ pub enum CollatorProtocolMessage {
/// ///
/// The hash is the relay parent. /// The hash is the relay parent.
Seconded(Hash, SignedFullStatement), Seconded(Hash, SignedFullStatement),
/// The candidate received enough validity votes from the backing group.
Backed {
/// Candidate's para id.
para_id: ParaId,
/// Hash of the para head generated by candidate.
para_head: Hash,
},
} }
impl Default for CollatorProtocolMessage { impl Default for CollatorProtocolMessage {
@@ -527,7 +564,7 @@ pub enum ChainApiMessage {
/// Request the last finalized block number. /// Request the last finalized block number.
/// This request always succeeds. /// This request always succeeds.
FinalizedBlockNumber(ChainApiResponseChannel<BlockNumber>), FinalizedBlockNumber(ChainApiResponseChannel<BlockNumber>),
/// Request the `k` ancestors block hashes of a block with the given hash. /// Request the `k` ancestor block hashes of a block with the given hash.
/// The response channel may return a `Vec` of size up to `k` /// The response channel may return a `Vec` of size up to `k`
/// filled with ancestors hashes with the following order: /// filled with ancestors hashes with the following order:
/// `parent`, `grandparent`, ... up to the hash of genesis block /// `parent`, `grandparent`, ... up to the hash of genesis block
@@ -654,6 +691,14 @@ pub enum RuntimeApiRequest {
slashing::OpaqueKeyOwnershipProof, slashing::OpaqueKeyOwnershipProof,
RuntimeApiSender<Option<()>>, RuntimeApiSender<Option<()>>,
), ),
/// Get the backing state of the given para.
/// This is a staging API that will not be available on production runtimes.
StagingParaBackingState(ParaId, RuntimeApiSender<Option<vstaging_primitives::BackingState>>),
/// Get candidate's acceptance limitations for asynchronous backing for a relay parent.
///
/// If it's not supported by the Runtime, the async backing is said to be disabled.
StagingAsyncBackingParams(RuntimeApiSender<vstaging_primitives::AsyncBackingParams>),
} }
impl RuntimeApiRequest { impl RuntimeApiRequest {
@@ -673,6 +718,11 @@ impl RuntimeApiRequest {
/// `SubmitReportDisputeLost` /// `SubmitReportDisputeLost`
pub const SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT: u32 = 5; pub const SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT: u32 = 5;
/// Minimum version for backing state, required for async backing.
///
/// 99 for now, should be adjusted to VSTAGING/actual runtime version once released.
pub const STAGING_BACKING_STATE: u32 = 99;
} }
/// A message to the Runtime API subsystem. /// A message to the Runtime API subsystem.
@@ -687,7 +737,14 @@ pub enum RuntimeApiMessage {
pub enum StatementDistributionMessage { pub enum StatementDistributionMessage {
/// We have originated a signed statement in the context of /// We have originated a signed statement in the context of
/// given relay-parent hash and it should be distributed to other validators. /// given relay-parent hash and it should be distributed to other validators.
Share(Hash, SignedFullStatement), Share(Hash, SignedFullStatementWithPVD),
/// The candidate received enough validity votes from the backing group.
///
/// If the candidate is backed as a result of a local statement, this message MUST
/// be preceded by a `Share` message for that statement. This ensures that Statement
/// Distribution is always aware of full candidates prior to receiving the `Backed`
/// notification, even when the group size is 1 and the candidate is seconded locally.
Backed(CandidateHash),
/// Event from the network bridge. /// Event from the network bridge.
#[from] #[from]
NetworkBridgeUpdate(NetworkBridgeEvent<net_protocol::StatementDistributionMessage>), NetworkBridgeUpdate(NetworkBridgeEvent<net_protocol::StatementDistributionMessage>),
@@ -740,6 +797,11 @@ pub enum ProvisionerMessage {
pub enum CollationGenerationMessage { pub enum CollationGenerationMessage {
/// Initialize the collation generation subsystem /// Initialize the collation generation subsystem
Initialize(CollationGenerationConfig), Initialize(CollationGenerationConfig),
/// Submit a collation to the subsystem. This will package it into a signed
/// [`CommittedCandidateReceipt`] and distribute along the network to validators.
///
/// If sent before `Initialize`, this will be ignored.
SubmitCollation(SubmitCollationParams),
} }
/// The result type of [`ApprovalVotingMessage::CheckAndImportAssignment`] request. /// The result type of [`ApprovalVotingMessage::CheckAndImportAssignment`] request.
@@ -897,3 +959,175 @@ pub enum GossipSupportMessage {
#[from] #[from]
NetworkBridgeUpdate(NetworkBridgeEvent<net_protocol::GossipSupportNetworkMessage>), NetworkBridgeUpdate(NetworkBridgeEvent<net_protocol::GossipSupportNetworkMessage>),
} }
/// Request introduction of a candidate into the prospective parachains subsystem.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct IntroduceCandidateRequest {
/// The para-id of the candidate.
pub candidate_para: ParaId,
/// The candidate receipt itself.
pub candidate_receipt: CommittedCandidateReceipt,
/// The persisted validation data of the candidate.
pub persisted_validation_data: PersistedValidationData,
}
/// A hypothetical candidate to be evaluated for frontier membership
/// in the prospective parachains subsystem.
///
/// Hypothetical candidates are either complete or incomplete.
/// Complete candidates have already had their (potentially heavy)
/// candidate receipt fetched, while incomplete candidates are simply
/// claims about properties that a fetched candidate would have.
///
/// Complete candidates can be evaluated more strictly than incomplete candidates.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum HypotheticalCandidate {
/// A complete candidate.
Complete {
/// The hash of the candidate.
candidate_hash: CandidateHash,
/// The receipt of the candidate.
receipt: Arc<CommittedCandidateReceipt>,
/// The persisted validation data of the candidate.
persisted_validation_data: PersistedValidationData,
},
/// An incomplete candidate.
Incomplete {
/// The claimed hash of the candidate.
candidate_hash: CandidateHash,
/// The claimed para-ID of the candidate.
candidate_para: ParaId,
/// The claimed head-data hash of the candidate.
parent_head_data_hash: Hash,
/// The claimed relay parent of the candidate.
candidate_relay_parent: Hash,
},
}
impl HypotheticalCandidate {
/// Get the `CandidateHash` of the hypothetical candidate.
pub fn candidate_hash(&self) -> CandidateHash {
match *self {
HypotheticalCandidate::Complete { candidate_hash, .. } => candidate_hash,
HypotheticalCandidate::Incomplete { candidate_hash, .. } => candidate_hash,
}
}
/// Get the `ParaId` of the hypothetical candidate.
pub fn candidate_para(&self) -> ParaId {
match *self {
HypotheticalCandidate::Complete { ref receipt, .. } => receipt.descriptor().para_id,
HypotheticalCandidate::Incomplete { candidate_para, .. } => candidate_para,
}
}
/// Get parent head data hash of the hypothetical candidate.
pub fn parent_head_data_hash(&self) -> Hash {
match *self {
HypotheticalCandidate::Complete { ref persisted_validation_data, .. } =>
persisted_validation_data.parent_head.hash(),
HypotheticalCandidate::Incomplete { parent_head_data_hash, .. } =>
parent_head_data_hash,
}
}
/// Get candidate's relay parent.
pub fn relay_parent(&self) -> Hash {
match *self {
HypotheticalCandidate::Complete { ref receipt, .. } =>
receipt.descriptor().relay_parent,
HypotheticalCandidate::Incomplete { candidate_relay_parent, .. } =>
candidate_relay_parent,
}
}
}
/// Request specifying which candidates are either already included
/// or might be included in the hypothetical frontier of fragment trees
/// under a given active leaf.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct HypotheticalFrontierRequest {
/// Candidates, in arbitrary order, which should be checked for
/// possible membership in fragment trees.
pub candidates: Vec<HypotheticalCandidate>,
/// Either a specific fragment tree to check, otherwise all.
pub fragment_tree_relay_parent: Option<Hash>,
/// Only return membership if all candidates in the path from the
/// root are backed.
pub backed_in_path_only: bool,
}
/// A request for the persisted validation data stored in the prospective
/// parachains subsystem.
#[derive(Debug)]
pub struct ProspectiveValidationDataRequest {
/// The para-id of the candidate.
pub para_id: ParaId,
/// The relay-parent of the candidate.
pub candidate_relay_parent: Hash,
/// The parent head-data hash.
pub parent_head_data_hash: Hash,
}
/// Indicates the relay-parents whose fragment tree a candidate
/// is present in and the depths of that tree the candidate is present in.
pub type FragmentTreeMembership = Vec<(Hash, Vec<usize>)>;
/// Messages sent to the Prospective Parachains subsystem.
#[derive(Debug)]
pub enum ProspectiveParachainsMessage {
/// Inform the Prospective Parachains Subsystem of a new candidate.
///
/// The response sender accepts the candidate membership, which is the existing
/// membership of the candidate if it was already known.
IntroduceCandidate(IntroduceCandidateRequest, oneshot::Sender<FragmentTreeMembership>),
/// Inform the Prospective Parachains Subsystem that a previously introduced candidate
/// has been seconded. This requires that the candidate was successfully introduced in
/// the past.
CandidateSeconded(ParaId, CandidateHash),
/// Inform the Prospective Parachains Subsystem that a previously introduced candidate
/// has been backed. This requires that the candidate was successfully introduced in
/// the past.
CandidateBacked(ParaId, CandidateHash),
/// Get a backable candidate hash along with its relay parent for the given parachain,
/// under the given relay-parent hash, which is a descendant of the given candidate hashes.
/// Returns `None` on the channel if no such candidate exists.
GetBackableCandidate(
Hash,
ParaId,
Vec<CandidateHash>,
oneshot::Sender<Option<(CandidateHash, Hash)>>,
),
/// Get the hypothetical frontier membership of candidates with the given properties
/// under the specified active leaves' fragment trees.
///
/// For any candidate which is already known, this returns the depths the candidate
/// occupies.
GetHypotheticalFrontier(
HypotheticalFrontierRequest,
oneshot::Sender<Vec<(HypotheticalCandidate, FragmentTreeMembership)>>,
),
/// Get the membership of the candidate in all fragment trees.
GetTreeMembership(ParaId, CandidateHash, oneshot::Sender<FragmentTreeMembership>),
/// Get the minimum accepted relay-parent number for each para in the fragment tree
/// for the given relay-chain block hash.
///
/// That is, if the block hash is known and is an active leaf, this returns the
/// minimum relay-parent block number in the same branch of the relay chain which
/// is accepted in the fragment tree for each para-id.
///
/// If the block hash is not an active leaf, this will return an empty vector.
///
/// Para-IDs which are omitted from this list can be assumed to have no
/// valid candidate relay-parents under the given relay-chain block hash.
///
/// Para-IDs are returned in no particular order.
GetMinimumRelayParents(Hash, oneshot::Sender<Vec<(ParaId, BlockNumber)>>),
/// Get the validation data of some prospective candidate. The candidate doesn't need
/// to be part of any fragment tree, but this only succeeds if the parent head-data and
/// relay-parent are part of some fragment tree.
GetProspectiveValidationData(
ProspectiveValidationDataRequest,
oneshot::Sender<Option<PersistedValidationData>>,
),
}
@@ -212,13 +212,6 @@ pub trait RuntimeApiSubsystemClient {
key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof,
) -> Result<Option<()>, ApiError>; ) -> Result<Option<()>, ApiError>;
/// Get the execution environment parameter set by parent hash, if stored
async fn session_executor_params(
&self,
at: Hash,
session_index: SessionIndex,
) -> Result<Option<ExecutorParams>, ApiError>;
// === BABE API === // === BABE API ===
/// Returns information regarding the current epoch. /// Returns information regarding the current epoch.
@@ -231,6 +224,29 @@ pub trait RuntimeApiSubsystemClient {
&self, &self,
at: Hash, at: Hash,
) -> std::result::Result<Vec<sp_authority_discovery::AuthorityId>, ApiError>; ) -> std::result::Result<Vec<sp_authority_discovery::AuthorityId>, ApiError>;
/// Get the execution environment parameter set by parent hash, if stored
async fn session_executor_params(
&self,
at: Hash,
session_index: SessionIndex,
) -> Result<Option<ExecutorParams>, ApiError>;
// === Asynchronous backing API ===
/// Returns candidate's acceptance limitations for asynchronous backing for a relay parent.
async fn staging_async_backing_params(
&self,
at: Hash,
) -> Result<polkadot_primitives::vstaging::AsyncBackingParams, ApiError>;
/// Returns the state of parachain backing for a given para.
/// This is a staging method! Do not use on production runtimes!
async fn staging_para_backing_state(
&self,
at: Hash,
para_id: Id,
) -> Result<Option<polkadot_primitives::vstaging::BackingState>, ApiError>;
} }
/// Default implementation of [`RuntimeApiSubsystemClient`] using the client. /// Default implementation of [`RuntimeApiSubsystemClient`] using the client.
@@ -456,4 +472,20 @@ where
runtime_api.submit_report_dispute_lost(at, dispute_proof, key_ownership_proof) runtime_api.submit_report_dispute_lost(at, dispute_proof, key_ownership_proof)
} }
async fn staging_para_backing_state(
&self,
at: Hash,
para_id: Id,
) -> Result<Option<polkadot_primitives::vstaging::BackingState>, ApiError> {
self.client.runtime_api().staging_para_backing_state(at, para_id)
}
/// Returns candidate's acceptance limitations for asynchronous backing for a relay parent.
async fn staging_async_backing_params(
&self,
at: Hash,
) -> Result<polkadot_primitives::vstaging::AsyncBackingParams, ApiError> {
self.client.runtime_api().staging_async_backing_params(at)
}
} }
@@ -0,0 +1,739 @@
// Copyright 2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use futures::channel::oneshot;
use polkadot_node_subsystem::{
errors::ChainApiError,
messages::{ChainApiMessage, ProspectiveParachainsMessage},
SubsystemSender,
};
use polkadot_primitives::vstaging::{BlockNumber, Hash, Id as ParaId};
use std::collections::HashMap;
// Always aim to retain 1 block before the active leaves.
const MINIMUM_RETAIN_LENGTH: BlockNumber = 2;
/// Handles the implicit view of the relay chain derived from the immediate view, which
/// is composed of active leaves, and the minimum relay-parents allowed for
/// candidates of various parachains at those leaves.
#[derive(Default, Clone)]
pub struct View {
leaves: HashMap<Hash, ActiveLeafPruningInfo>,
block_info_storage: HashMap<Hash, BlockInfo>,
}
// Minimum relay parents implicitly relative to a particular block.
#[derive(Debug, Clone)]
struct AllowedRelayParents {
// minimum relay parents can only be fetched for active leaves,
// so this will be empty for all blocks that haven't ever been
// witnessed as active leaves.
minimum_relay_parents: HashMap<ParaId, BlockNumber>,
// Ancestry, in descending order, starting from the block hash itself down
// to and including the minimum of `minimum_relay_parents`.
allowed_relay_parents_contiguous: Vec<Hash>,
}
impl AllowedRelayParents {
fn allowed_relay_parents_for(
&self,
para_id: Option<ParaId>,
base_number: BlockNumber,
) -> &[Hash] {
let para_id = match para_id {
None => return &self.allowed_relay_parents_contiguous[..],
Some(p) => p,
};
let para_min = match self.minimum_relay_parents.get(&para_id) {
Some(p) => *p,
None => return &[],
};
if base_number < para_min {
return &[]
}
let diff = base_number - para_min;
// difference of 0 should lead to slice len of 1
let slice_len = ((diff + 1) as usize).min(self.allowed_relay_parents_contiguous.len());
&self.allowed_relay_parents_contiguous[..slice_len]
}
}
#[derive(Debug, Clone)]
struct ActiveLeafPruningInfo {
// The minimum block in the same branch of the relay-chain that should be
// preserved.
retain_minimum: BlockNumber,
}
#[derive(Debug, Clone)]
struct BlockInfo {
block_number: BlockNumber,
// If this was previously an active leaf, this will be `Some`
// and is useful for understanding the views of peers in the network
// which may not be in perfect synchrony with our own view.
//
// If they are ahead of us in getting a new leaf, there's nothing we
// can do as it's an unrecognized block hash. But if they're behind us,
// it's useful for us to retain some information about previous leaves'
// implicit views so we can continue to send relevant messages to them
// until they catch up.
maybe_allowed_relay_parents: Option<AllowedRelayParents>,
parent_hash: Hash,
}
impl View {
/// Get an iterator over active leaves in the view.
pub fn leaves(&self) -> impl Iterator<Item = &Hash> {
self.leaves.keys()
}
/// Activate a leaf in the view.
/// This will request the minimum relay parents from the
/// Prospective Parachains subsystem for each leaf and will load headers in the ancestry of each
/// leaf in the view as needed. These are the 'implicit ancestors' of the leaf.
///
/// To maximize reuse of outdated leaves, it's best to activate new leaves before
/// deactivating old ones.
///
/// This returns a list of para-ids which are relevant to the leaf,
/// and the allowed relay parents for these paras under this leaf can be
/// queried with [`View::known_allowed_relay_parents_under`].
///
/// No-op for known leaves.
pub async fn activate_leaf<Sender>(
&mut self,
sender: &mut Sender,
leaf_hash: Hash,
) -> Result<Vec<ParaId>, FetchError>
where
Sender: SubsystemSender<ChainApiMessage>,
Sender: SubsystemSender<ProspectiveParachainsMessage>,
{
if self.leaves.contains_key(&leaf_hash) {
return Err(FetchError::AlreadyKnown)
}
let res = fetch_fresh_leaf_and_insert_ancestry(
leaf_hash,
&mut self.block_info_storage,
&mut *sender,
)
.await;
match res {
Ok(fetched) => {
// Retain at least `MINIMUM_RETAIN_LENGTH` blocks in storage.
// This helps to avoid Chain API calls when activating leaves in the
// same chain.
let retain_minimum = std::cmp::min(
fetched.minimum_ancestor_number,
fetched.leaf_number.saturating_sub(MINIMUM_RETAIN_LENGTH),
);
self.leaves.insert(leaf_hash, ActiveLeafPruningInfo { retain_minimum });
Ok(fetched.relevant_paras)
},
Err(e) => Err(e),
}
}
/// Deactivate a leaf in the view. This prunes any outdated implicit ancestors as well.
///
/// Returns hashes of blocks pruned from storage.
pub fn deactivate_leaf(&mut self, leaf_hash: Hash) -> Vec<Hash> {
let mut removed = Vec::new();
if self.leaves.remove(&leaf_hash).is_none() {
return removed
}
// Prune everything before the minimum out of all leaves,
// pruning absolutely everything if there are no leaves (empty view)
//
// Pruning by block number does leave behind orphaned forks slightly longer
// but the memory overhead is negligible.
{
let minimum = self.leaves.values().map(|l| l.retain_minimum).min();
self.block_info_storage.retain(|hash, i| {
let keep = minimum.map_or(false, |m| i.block_number >= m);
if !keep {
removed.push(*hash);
}
keep
});
removed
}
}
/// Get an iterator over all allowed relay-parents in the view with no particular order.
///
/// **Important**: not all blocks are guaranteed to be allowed for some leaves, it may
/// happen that a block info is only kept in the view storage because of a retaining rule.
///
/// For getting relay-parents that are valid for parachain candidates use
/// [`View::known_allowed_relay_parents_under`].
pub fn all_allowed_relay_parents(&self) -> impl Iterator<Item = &Hash> {
self.block_info_storage.keys()
}
/// Get the known, allowed relay-parents that are valid for parachain candidates
/// which could be backed in a child of a given block for a given para ID.
///
/// This is expressed as a contiguous slice of relay-chain block hashes which may
/// include the provided block hash itself.
///
/// If `para_id` is `None`, this returns all valid relay-parents across all paras
/// for the leaf.
///
/// `None` indicates that the block hash isn't part of the implicit view or that
/// there are no known allowed relay parents.
///
/// This always returns `Some` for active leaves or for blocks that previously
/// were active leaves.
///
/// This can return the empty slice, which indicates that no relay-parents are allowed
/// for the para, e.g. if the para is not scheduled at the given block hash.
pub fn known_allowed_relay_parents_under(
&self,
block_hash: &Hash,
para_id: Option<ParaId>,
) -> Option<&[Hash]> {
let block_info = self.block_info_storage.get(block_hash)?;
block_info
.maybe_allowed_relay_parents
.as_ref()
.map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number))
}
}
/// Errors when fetching a leaf and associated ancestry.
#[fatality::fatality]
pub enum FetchError {
/// Activated leaf is already present in view.
#[error("Leaf was already known")]
AlreadyKnown,
/// Request to the prospective parachains subsystem failed.
#[error("The prospective parachains subsystem was unavailable")]
ProspectiveParachainsUnavailable,
/// Failed to fetch the block header.
#[error("A block header was unavailable")]
BlockHeaderUnavailable(Hash, BlockHeaderUnavailableReason),
/// A block header was unavailable due to a chain API error.
#[error("A block header was unavailable due to a chain API error")]
ChainApiError(Hash, ChainApiError),
/// Request to the Chain API subsystem failed.
#[error("The chain API subsystem was unavailable")]
ChainApiUnavailable,
}
/// Reasons a block header might have been unavailable.
#[derive(Debug)]
pub enum BlockHeaderUnavailableReason {
/// Block header simply unknown.
Unknown,
/// Internal Chain API error.
Internal(ChainApiError),
/// The subsystem was unavailable.
SubsystemUnavailable,
}
struct FetchSummary {
minimum_ancestor_number: BlockNumber,
leaf_number: BlockNumber,
relevant_paras: Vec<ParaId>,
}
async fn fetch_fresh_leaf_and_insert_ancestry<Sender>(
leaf_hash: Hash,
block_info_storage: &mut HashMap<Hash, BlockInfo>,
sender: &mut Sender,
) -> Result<FetchSummary, FetchError>
where
Sender: SubsystemSender<ChainApiMessage>,
Sender: SubsystemSender<ProspectiveParachainsMessage>,
{
let min_relay_parents_raw = {
let (tx, rx) = oneshot::channel();
sender
.send_message(ProspectiveParachainsMessage::GetMinimumRelayParents(leaf_hash, tx))
.await;
match rx.await {
Ok(m) => m,
Err(_) => return Err(FetchError::ProspectiveParachainsUnavailable),
}
};
let leaf_header = {
let (tx, rx) = oneshot::channel();
sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await;
match rx.await {
Ok(Ok(Some(header))) => header,
Ok(Ok(None)) =>
return Err(FetchError::BlockHeaderUnavailable(
leaf_hash,
BlockHeaderUnavailableReason::Unknown,
)),
Ok(Err(e)) =>
return Err(FetchError::BlockHeaderUnavailable(
leaf_hash,
BlockHeaderUnavailableReason::Internal(e),
)),
Err(_) =>
return Err(FetchError::BlockHeaderUnavailable(
leaf_hash,
BlockHeaderUnavailableReason::SubsystemUnavailable,
)),
}
};
let min_min = min_relay_parents_raw.iter().map(|x| x.1).min().unwrap_or(leaf_header.number);
let relevant_paras = min_relay_parents_raw.iter().map(|x| x.0).collect();
let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1;
let ancestry = if leaf_header.number > 0 {
let mut next_ancestor_number = leaf_header.number - 1;
let mut next_ancestor_hash = leaf_header.parent_hash;
let mut ancestry = Vec::with_capacity(expected_ancestry_len);
ancestry.push(leaf_hash);
// Ensure all ancestors up to and including `min_min` are in the
// block storage. When views advance incrementally, everything
// should already be present.
while next_ancestor_number >= min_min {
let parent_hash = if let Some(info) = block_info_storage.get(&next_ancestor_hash) {
info.parent_hash
} else {
// load the header and insert into block storage.
let (tx, rx) = oneshot::channel();
sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await;
let header = match rx.await {
Ok(Ok(Some(header))) => header,
Ok(Ok(None)) =>
return Err(FetchError::BlockHeaderUnavailable(
next_ancestor_hash,
BlockHeaderUnavailableReason::Unknown,
)),
Ok(Err(e)) =>
return Err(FetchError::BlockHeaderUnavailable(
next_ancestor_hash,
BlockHeaderUnavailableReason::Internal(e),
)),
Err(_) =>
return Err(FetchError::BlockHeaderUnavailable(
next_ancestor_hash,
BlockHeaderUnavailableReason::SubsystemUnavailable,
)),
};
block_info_storage.insert(
next_ancestor_hash,
BlockInfo {
block_number: next_ancestor_number,
parent_hash: header.parent_hash,
maybe_allowed_relay_parents: None,
},
);
header.parent_hash
};
ancestry.push(next_ancestor_hash);
if next_ancestor_number == 0 {
break
}
next_ancestor_number -= 1;
next_ancestor_hash = parent_hash;
}
ancestry
} else {
vec![leaf_hash]
};
let fetched_ancestry = FetchSummary {
minimum_ancestor_number: min_min,
leaf_number: leaf_header.number,
relevant_paras,
};
let allowed_relay_parents = AllowedRelayParents {
minimum_relay_parents: min_relay_parents_raw.iter().cloned().collect(),
allowed_relay_parents_contiguous: ancestry,
};
let leaf_block_info = BlockInfo {
parent_hash: leaf_header.parent_hash,
block_number: leaf_header.number,
maybe_allowed_relay_parents: Some(allowed_relay_parents),
};
block_info_storage.insert(leaf_hash, leaf_block_info);
Ok(fetched_ancestry)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TimeoutExt;
use assert_matches::assert_matches;
use futures::future::{join, FutureExt};
use polkadot_node_subsystem::AllMessages;
use polkadot_node_subsystem_test_helpers::{
make_subsystem_context, TestSubsystemContextHandle,
};
use polkadot_overseer::SubsystemContext;
use polkadot_primitives::Header;
use sp_core::testing::TaskExecutor;
use std::time::Duration;
const PARA_A: ParaId = ParaId::new(0);
const PARA_B: ParaId = ParaId::new(1);
const PARA_C: ParaId = ParaId::new(2);
const GENESIS_HASH: Hash = Hash::repeat_byte(0xFF);
const GENESIS_NUMBER: BlockNumber = 0;
// Chains A and B are forks of genesis.
const CHAIN_A: &[Hash] =
&[Hash::repeat_byte(0x01), Hash::repeat_byte(0x02), Hash::repeat_byte(0x03)];
const CHAIN_B: &[Hash] = &[
Hash::repeat_byte(0x04),
Hash::repeat_byte(0x05),
Hash::repeat_byte(0x06),
Hash::repeat_byte(0x07),
Hash::repeat_byte(0x08),
Hash::repeat_byte(0x09),
];
type VirtualOverseer = TestSubsystemContextHandle<AllMessages>;
const TIMEOUT: Duration = Duration::from_secs(2);
async fn overseer_recv(virtual_overseer: &mut VirtualOverseer) -> AllMessages {
virtual_overseer
.recv()
.timeout(TIMEOUT)
.await
.expect("overseer `recv` timed out")
}
fn default_header() -> Header {
Header {
parent_hash: Hash::zero(),
number: 0,
state_root: Hash::zero(),
extrinsics_root: Hash::zero(),
digest: Default::default(),
}
}
fn get_block_header(chain: &[Hash], hash: &Hash) -> Option<Header> {
let idx = chain.iter().position(|h| h == hash)?;
let parent_hash = idx.checked_sub(1).map(|i| chain[i]).unwrap_or(GENESIS_HASH);
let number =
if *hash == GENESIS_HASH { GENESIS_NUMBER } else { GENESIS_NUMBER + idx as u32 + 1 };
Some(Header { parent_hash, number, ..default_header() })
}
async fn assert_block_header_requests(
virtual_overseer: &mut VirtualOverseer,
chain: &[Hash],
blocks: &[Hash],
) {
for block in blocks.iter().rev() {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ChainApi(
ChainApiMessage::BlockHeader(hash, tx)
) => {
assert_eq!(*block, hash, "unexpected block header request");
let header = if block == &GENESIS_HASH {
Header {
number: GENESIS_NUMBER,
..default_header()
}
} else {
get_block_header(chain, block).expect("unknown block")
};
tx.send(Ok(Some(header))).unwrap();
}
);
}
}
async fn assert_min_relay_parents_request(
virtual_overseer: &mut VirtualOverseer,
leaf: &Hash,
response: Vec<(ParaId, u32)>,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetMinimumRelayParents(
leaf_hash,
tx
)
) => {
assert_eq!(*leaf, leaf_hash, "received unexpected leaf hash");
tx.send(response).unwrap();
}
);
}
#[test]
fn construct_fresh_view() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
let mut view = View::default();
// Chain B.
const PARA_A_MIN_PARENT: u32 = 4;
const PARA_B_MIN_PARENT: u32 = 3;
let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT), (PARA_B, PARA_B_MIN_PARENT)];
let leaf = CHAIN_B.last().unwrap();
let min_min_idx = (PARA_B_MIN_PARENT - GENESIS_NUMBER - 1) as usize;
let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_A, PARA_B]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await;
assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[min_min_idx..]).await;
};
futures::executor::block_on(join(fut, overseer_fut));
for i in min_min_idx..(CHAIN_B.len() - 1) {
// No allowed relay parents constructed for ancestry.
assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i], None).is_none());
}
let leaf_info =
view.block_info_storage.get(leaf).expect("block must be present in storage");
assert_matches!(
leaf_info.maybe_allowed_relay_parents,
Some(ref allowed_relay_parents) => {
assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], PARA_A_MIN_PARENT);
assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_B], PARA_B_MIN_PARENT);
let expected_ancestry: Vec<Hash> =
CHAIN_B[min_min_idx..].iter().rev().copied().collect();
assert_eq!(
allowed_relay_parents.allowed_relay_parents_contiguous,
expected_ancestry
);
}
);
// Suppose the whole test chain A is allowed up to genesis for para C.
const PARA_C_MIN_PARENT: u32 = 0;
let prospective_response = vec![(PARA_C, PARA_C_MIN_PARENT)];
let leaf = CHAIN_A.last().unwrap();
let blocks = [&[GENESIS_HASH], CHAIN_A].concat();
let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_C]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await;
assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks).await;
};
futures::executor::block_on(join(fut, overseer_fut));
assert_eq!(view.leaves.len(), 2);
}
#[test]
fn reuse_block_info_storage() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
let mut view = View::default();
const PARA_A_MIN_PARENT: u32 = 1;
let leaf_a_number = 3;
let leaf_a = CHAIN_B[leaf_a_number - 1];
let min_min_idx = (PARA_A_MIN_PARENT - GENESIS_NUMBER - 1) as usize;
let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)];
let fut = view.activate_leaf(ctx.sender(), leaf_a).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_A]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_a, prospective_response).await;
assert_block_header_requests(
&mut ctx_handle,
CHAIN_B,
&CHAIN_B[min_min_idx..leaf_a_number],
)
.await;
};
futures::executor::block_on(join(fut, overseer_fut));
// Blocks up to the 3rd are present in storage.
const PARA_B_MIN_PARENT: u32 = 2;
let leaf_b_number = 5;
let leaf_b = CHAIN_B[leaf_b_number - 1];
let prospective_response = vec![(PARA_B, PARA_B_MIN_PARENT)];
let fut = view.activate_leaf(ctx.sender(), leaf_b).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_B]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_b, prospective_response).await;
assert_block_header_requests(
&mut ctx_handle,
CHAIN_B,
&CHAIN_B[leaf_a_number..leaf_b_number], // Note the expected range.
)
.await;
};
futures::executor::block_on(join(fut, overseer_fut));
// Allowed relay parents for leaf A are preserved.
let leaf_a_info =
view.block_info_storage.get(&leaf_a).expect("block must be present in storage");
assert_matches!(
leaf_a_info.maybe_allowed_relay_parents,
Some(ref allowed_relay_parents) => {
assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], PARA_A_MIN_PARENT);
let expected_ancestry: Vec<Hash> =
CHAIN_B[min_min_idx..leaf_a_number].iter().rev().copied().collect();
let ancestry = view.known_allowed_relay_parents_under(&leaf_a, Some(PARA_A)).unwrap().to_vec();
assert_eq!(ancestry, expected_ancestry);
}
);
}
#[test]
fn pruning() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
let mut view = View::default();
const PARA_A_MIN_PARENT: u32 = 3;
let leaf_a = CHAIN_B.iter().rev().nth(1).unwrap();
let leaf_a_idx = CHAIN_B.len() - 2;
let min_a_idx = (PARA_A_MIN_PARENT - GENESIS_NUMBER - 1) as usize;
let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)];
let fut = view
.activate_leaf(ctx.sender(), *leaf_a)
.timeout(TIMEOUT)
.map(|res| res.unwrap().unwrap());
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_a, prospective_response).await;
assert_block_header_requests(
&mut ctx_handle,
CHAIN_B,
&CHAIN_B[min_a_idx..=leaf_a_idx],
)
.await;
};
futures::executor::block_on(join(fut, overseer_fut));
// Also activate a leaf with a lesser minimum relay parent.
const PARA_B_MIN_PARENT: u32 = 2;
let leaf_b = CHAIN_B.last().unwrap();
let min_b_idx = (PARA_B_MIN_PARENT - GENESIS_NUMBER - 1) as usize;
let prospective_response = vec![(PARA_B, PARA_B_MIN_PARENT)];
// Headers will be requested for the minimum block and the leaf.
let blocks = &[CHAIN_B[min_b_idx], *leaf_b];
let fut = view
.activate_leaf(ctx.sender(), *leaf_b)
.timeout(TIMEOUT)
.map(|res| res.expect("`activate_leaf` timed out").unwrap());
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_b, prospective_response).await;
assert_block_header_requests(&mut ctx_handle, CHAIN_B, blocks).await;
};
futures::executor::block_on(join(fut, overseer_fut));
// Prune implicit ancestor (no-op).
let block_info_len = view.block_info_storage.len();
view.deactivate_leaf(CHAIN_B[leaf_a_idx - 1]);
assert_eq!(block_info_len, view.block_info_storage.len());
// Prune a leaf with a greater minimum relay parent.
view.deactivate_leaf(*leaf_b);
for hash in CHAIN_B.iter().take(PARA_B_MIN_PARENT as usize) {
assert!(!view.block_info_storage.contains_key(hash));
}
// Prune the last leaf.
view.deactivate_leaf(*leaf_a);
assert!(view.block_info_storage.is_empty());
}
#[test]
fn genesis_ancestry() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);
let mut view = View::default();
const PARA_A_MIN_PARENT: u32 = 0;
let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)];
let fut = view.activate_leaf(ctx.sender(), GENESIS_HASH).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_A]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &GENESIS_HASH, prospective_response)
.await;
assert_block_header_requests(&mut ctx_handle, &[GENESIS_HASH], &[GENESIS_HASH]).await;
};
futures::executor::block_on(join(fut, overseer_fut));
assert_matches!(
view.known_allowed_relay_parents_under(&GENESIS_HASH, None),
Some(hashes) if !hashes.is_empty()
);
}
}
@@ -0,0 +1,14 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
pub mod staging;
File diff suppressed because it is too large Load Diff
+24 -12
View File
@@ -43,11 +43,11 @@ use futures::channel::{mpsc, oneshot};
use parity_scale_codec::Encode; use parity_scale_codec::Encode;
use polkadot_primitives::{ use polkadot_primitives::{
AuthorityDiscoveryId, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, vstaging as vstaging_primitives, AuthorityDiscoveryId, CandidateEvent, CandidateHash,
EncodeAs, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption, CommittedCandidateReceipt, CoreState, EncodeAs, GroupIndex, GroupRotationInfo, Hash,
PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes,
SigningContext, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, SessionIndex, SessionInfo, Signed, SigningContext, ValidationCode, ValidationCodeHash,
ValidatorSignature, ValidatorId, ValidatorIndex, ValidatorSignature,
}; };
pub use rand; pub use rand;
use sp_application_crypto::AppCrypto; use sp_application_crypto::AppCrypto;
@@ -67,11 +67,17 @@ pub mod reexports {
pub use polkadot_overseer::gen::{SpawnedSubsystem, Spawner, Subsystem, SubsystemContext}; pub use polkadot_overseer::gen::{SpawnedSubsystem, Spawner, Subsystem, SubsystemContext};
} }
/// Convenient and efficient runtime info access. /// A utility for managing the implicit view of the relay-chain derived from active
pub mod runtime; /// leaves and the minimum allowed relay-parents that parachain candidates can have
/// and be backed in those leaves' children.
pub mod backing_implicit_view;
/// Database trait for subsystem. /// Database trait for subsystem.
pub mod database; pub mod database;
/// An emulator for node-side code to predict the results of on-chain parachain inclusion
/// and predict future constraints.
pub mod inclusion_emulator;
/// Convenient and efficient runtime info access.
pub mod runtime;
/// Nested message sending /// Nested message sending
/// ///
@@ -200,6 +206,7 @@ macro_rules! specialize_requests {
} }
specialize_requests! { specialize_requests! {
fn request_runtime_api_version() -> u32; Version;
fn request_authorities() -> Vec<AuthorityDiscoveryId>; Authorities; fn request_authorities() -> Vec<AuthorityDiscoveryId>; Authorities;
fn request_validators() -> Vec<ValidatorId>; Validators; fn request_validators() -> Vec<ValidatorId>; Validators;
fn request_validator_groups() -> (Vec<Vec<ValidatorIndex>>, GroupRotationInfo); ValidatorGroups; fn request_validator_groups() -> (Vec<Vec<ValidatorIndex>>, GroupRotationInfo); ValidatorGroups;
@@ -219,6 +226,8 @@ specialize_requests! {
fn request_unapplied_slashes() -> Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>; UnappliedSlashes; fn request_unapplied_slashes() -> Vec<(SessionIndex, CandidateHash, slashing::PendingSlashes)>; UnappliedSlashes;
fn request_key_ownership_proof(validator_id: ValidatorId) -> Option<slashing::OpaqueKeyOwnershipProof>; KeyOwnershipProof; fn request_key_ownership_proof(validator_id: ValidatorId) -> Option<slashing::OpaqueKeyOwnershipProof>; KeyOwnershipProof;
fn request_submit_report_dispute_lost(dp: slashing::DisputeProof, okop: slashing::OpaqueKeyOwnershipProof) -> Option<()>; SubmitReportDisputeLost; fn request_submit_report_dispute_lost(dp: slashing::DisputeProof, okop: slashing::OpaqueKeyOwnershipProof) -> Option<()>; SubmitReportDisputeLost;
fn request_staging_async_backing_params() -> vstaging_primitives::AsyncBackingParams; StagingAsyncBackingParams;
} }
/// Requests executor parameters from the runtime effective at given relay-parent. First obtains /// Requests executor parameters from the runtime effective at given relay-parent. First obtains
@@ -270,17 +279,20 @@ pub async fn executor_params_at_relay_parent(
} }
/// From the given set of validators, find the first key we can sign with, if any. /// From the given set of validators, find the first key we can sign with, if any.
pub fn signing_key(validators: &[ValidatorId], keystore: &KeystorePtr) -> Option<ValidatorId> { pub fn signing_key<'a>(
validators: impl IntoIterator<Item = &'a ValidatorId>,
keystore: &KeystorePtr,
) -> Option<ValidatorId> {
signing_key_and_index(validators, keystore).map(|(k, _)| k) signing_key_and_index(validators, keystore).map(|(k, _)| k)
} }
/// From the given set of validators, find the first key we can sign with, if any, and return it /// From the given set of validators, find the first key we can sign with, if any, and return it
/// along with the validator index. /// along with the validator index.
pub fn signing_key_and_index( pub fn signing_key_and_index<'a>(
validators: &[ValidatorId], validators: impl IntoIterator<Item = &'a ValidatorId>,
keystore: &KeystorePtr, keystore: &KeystorePtr,
) -> Option<(ValidatorId, ValidatorIndex)> { ) -> Option<(ValidatorId, ValidatorIndex)> {
for (i, v) in validators.iter().enumerate() { for (i, v) in validators.into_iter().enumerate() {
if keystore.has_keys(&[(v.to_raw_vec(), ValidatorId::ID)]) { if keystore.has_keys(&[(v.to_raw_vec(), ValidatorId::ID)]) {
return Some((v.clone(), ValidatorIndex(i as _))) return Some((v.clone(), ValidatorIndex(i as _)))
} }
@@ -25,7 +25,9 @@ use sp_application_crypto::AppCrypto;
use sp_core::crypto::ByteArray; use sp_core::crypto::ByteArray;
use sp_keystore::{Keystore, KeystorePtr}; use sp_keystore::{Keystore, KeystorePtr};
use polkadot_node_subsystem::{messages::RuntimeApiMessage, overseer, SubsystemSender}; use polkadot_node_subsystem::{
errors::RuntimeApiError, messages::RuntimeApiMessage, overseer, SubsystemSender,
};
use polkadot_primitives::{ use polkadot_primitives::{
vstaging, CandidateEvent, CandidateHash, CoreState, EncodeAs, GroupIndex, GroupRotationInfo, vstaging, CandidateEvent, CandidateHash, CoreState, EncodeAs, GroupIndex, GroupRotationInfo,
Hash, IndexedVec, OccupiedCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed, Hash, IndexedVec, OccupiedCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed,
@@ -36,8 +38,8 @@ use polkadot_primitives::{
use crate::{ use crate::{
request_availability_cores, request_candidate_events, request_key_ownership_proof, request_availability_cores, request_candidate_events, request_key_ownership_proof,
request_on_chain_votes, request_session_index_for_child, request_session_info, request_on_chain_votes, request_session_index_for_child, request_session_info,
request_submit_report_dispute_lost, request_unapplied_slashes, request_validation_code_by_hash, request_staging_async_backing_params, request_submit_report_dispute_lost,
request_validator_groups, request_unapplied_slashes, request_validation_code_by_hash, request_validator_groups,
}; };
/// Errors that can happen on runtime fetches. /// Errors that can happen on runtime fetches.
@@ -46,6 +48,8 @@ mod error;
use error::{recv_runtime, Result}; use error::{recv_runtime, Result};
pub use error::{Error, FatalError, JfyiError}; pub use error::{Error, FatalError, JfyiError};
const LOG_TARGET: &'static str = "parachain::runtime-info";
/// Configuration for construction a `RuntimeInfo`. /// Configuration for construction a `RuntimeInfo`.
pub struct Config { pub struct Config {
/// Needed for retrieval of `ValidatorInfo` /// Needed for retrieval of `ValidatorInfo`
@@ -393,3 +397,62 @@ where
) )
.await .await
} }
/// Prospective parachains mode of a relay parent. Defined by
/// the Runtime API version.
///
/// Needed for the period of transition to asynchronous backing.
#[derive(Debug, Copy, Clone)]
pub enum ProspectiveParachainsMode {
/// Runtime API without support of `async_backing_params`: no prospective parachains.
Disabled,
/// vstaging runtime API: prospective parachains.
Enabled {
/// The maximum number of para blocks between the para head in a relay parent
/// and a new candidate. Restricts nodes from building arbitrary long chains
/// and spamming other validators.
max_candidate_depth: usize,
/// How many ancestors of a relay parent are allowed to build candidates on top
/// of.
allowed_ancestry_len: usize,
},
}
impl ProspectiveParachainsMode {
/// Returns `true` if mode is enabled, `false` otherwise.
pub fn is_enabled(&self) -> bool {
matches!(self, ProspectiveParachainsMode::Enabled { .. })
}
}
/// Requests prospective parachains mode for a given relay parent based on
/// the Runtime API version.
pub async fn prospective_parachains_mode<Sender>(
sender: &mut Sender,
relay_parent: Hash,
) -> Result<ProspectiveParachainsMode>
where
Sender: SubsystemSender<RuntimeApiMessage>,
{
let result =
recv_runtime(request_staging_async_backing_params(relay_parent, sender).await).await;
if let Err(error::Error::RuntimeRequest(RuntimeApiError::NotSupported { runtime_api_name })) =
&result
{
gum::trace!(
target: LOG_TARGET,
?relay_parent,
"Prospective parachains are disabled, {} is not supported by the current Runtime API",
runtime_api_name,
);
Ok(ProspectiveParachainsMode::Disabled)
} else {
let vstaging::AsyncBackingParams { max_candidate_depth, allowed_ancestry_len } = result?;
Ok(ProspectiveParachainsMode::Enabled {
max_candidate_depth: max_candidate_depth as _,
allowed_ancestry_len: allowed_ancestry_len as _,
})
}
}
+2 -1
View File
@@ -340,7 +340,8 @@ impl PolkadotTestNode {
para_id: ParaId, para_id: ParaId,
collator: CollatorFn, collator: CollatorFn,
) { ) {
let config = CollationGenerationConfig { key: collator_key, collator, para_id }; let config =
CollationGenerationConfig { key: collator_key, collator: Some(collator), para_id };
self.overseer_handle self.overseer_handle
.send_msg(CollationGenerationMessage::Initialize(config), "Collator") .send_msg(CollationGenerationMessage::Initialize(config), "Collator")
+2 -1
View File
@@ -80,7 +80,8 @@ impl ValidationCode {
} }
} }
/// Unit type wrapper around [`type@Hash`] that represents a validation code hash. /// Unit type wrapper around [`type@Hash`] that represents the blake2-256 hash
/// of validation code in particular.
/// ///
/// This type is produced by [`ValidationCode::hash`]. /// This type is produced by [`ValidationCode::hash`].
/// ///
@@ -51,6 +51,7 @@ test-parachain-adder-collator = { path = ".", features = ["test-utils"] }
tokio = { version = "1.24.2", features = ["macros"] } tokio = { version = "1.24.2", features = ["macros"] }
[features] [features]
network-protocol-staging = ["polkadot-cli/network-protocol-staging"]
# This feature is used to export test code to other crates without putting it in the production build. # This feature is used to export test code to other crates without putting it in the production build.
# This is also used by the `puppet_worker` binary. # This is also used by the `puppet_worker` binary.
test-utils = ["polkadot-node-core-pvf/test-utils"] test-utils = ["polkadot-node-core-pvf/test-utils"]
@@ -96,8 +96,9 @@ fn main() -> Result<()> {
let config = CollationGenerationConfig { let config = CollationGenerationConfig {
key: collator.collator_key(), key: collator.collator_key(),
collator: collator collator: Some(
.create_collation_function(full_node.task_manager.spawn_handle()), collator.create_collation_function(full_node.task_manager.spawn_handle()),
),
para_id, para_id,
}; };
overseer_handle overseer_handle
@@ -33,7 +33,9 @@ use std::{
}, },
time::Duration, time::Duration,
}; };
use test_parachain_undying::{execute, hash_state, BlockData, GraveyardState, HeadData}; use test_parachain_undying::{
execute, hash_state, BlockData, GraveyardState, HeadData, StateMismatch,
};
/// Default PoV size which also drives state size. /// Default PoV size which also drives state size.
const DEFAULT_POV_SIZE: usize = 1000; const DEFAULT_POV_SIZE: usize = 1000;
@@ -45,7 +47,7 @@ fn calculate_head_and_state_for_number(
number: u64, number: u64,
graveyard_size: usize, graveyard_size: usize,
pvf_complexity: u32, pvf_complexity: u32,
) -> (HeadData, GraveyardState) { ) -> Result<(HeadData, GraveyardState), StateMismatch> {
let index = 0u64; let index = 0u64;
let mut graveyard = vec![0u8; graveyard_size * graveyard_size]; let mut graveyard = vec![0u8; graveyard_size * graveyard_size];
let zombies = 0; let zombies = 0;
@@ -62,13 +64,12 @@ fn calculate_head_and_state_for_number(
while head.number < number { while head.number < number {
let block = BlockData { state, tombstones: 1_000, iterations: pvf_complexity }; let block = BlockData { state, tombstones: 1_000, iterations: pvf_complexity };
let (new_head, new_state) = let (new_head, new_state) = execute(head.hash(), head.clone(), block)?;
execute(head.hash(), head.clone(), block).expect("Produces valid block");
head = new_head; head = new_head;
state = new_state; state = new_state;
} }
(head, state) Ok((head, state))
} }
/// The state of the undying parachain. /// The state of the undying parachain.
@@ -122,39 +123,35 @@ impl State {
/// Advance the state and produce a new block based on the given `parent_head`. /// Advance the state and produce a new block based on the given `parent_head`.
/// ///
/// Returns the new [`BlockData`] and the new [`HeadData`]. /// Returns the new [`BlockData`] and the new [`HeadData`].
fn advance(&mut self, parent_head: HeadData) -> (BlockData, HeadData) { fn advance(&mut self, parent_head: HeadData) -> Result<(BlockData, HeadData), StateMismatch> {
self.best_block = parent_head.number; self.best_block = parent_head.number;
let state = if let Some(head_data) = self.number_to_head.get(&self.best_block) { let state = if let Some(state) = self
self.head_to_state.get(head_data).cloned().unwrap_or_else(|| { .number_to_head
calculate_head_and_state_for_number( .get(&self.best_block)
parent_head.number, .and_then(|head_data| self.head_to_state.get(head_data).cloned())
self.graveyard_size, {
self.pvf_complexity, state
)
.1
})
} else { } else {
let (_, state) = calculate_head_and_state_for_number( let (_, state) = calculate_head_and_state_for_number(
parent_head.number, parent_head.number,
self.graveyard_size, self.graveyard_size,
self.pvf_complexity, self.pvf_complexity,
); )?;
state state
}; };
// Start with prev state and transaction to execute (place 1000 tombstones). // Start with prev state and transaction to execute (place 1000 tombstones).
let block = BlockData { state, tombstones: 1000, iterations: self.pvf_complexity }; let block = BlockData { state, tombstones: 1000, iterations: self.pvf_complexity };
let (new_head, new_state) = let (new_head, new_state) = execute(parent_head.hash(), parent_head, block.clone())?;
execute(parent_head.hash(), parent_head, block.clone()).expect("Produces valid block");
let new_head_arc = Arc::new(new_head.clone()); let new_head_arc = Arc::new(new_head.clone());
self.head_to_state.insert(new_head_arc.clone(), new_state); self.head_to_state.insert(new_head_arc.clone(), new_state);
self.number_to_head.insert(new_head.number, new_head_arc); self.number_to_head.insert(new_head.number, new_head_arc);
(block, new_head) Ok((block, new_head))
} }
} }
@@ -233,10 +230,21 @@ impl Collator {
let seconded_collations = self.seconded_collations.clone(); let seconded_collations = self.seconded_collations.clone();
Box::new(move |relay_parent, validation_data| { Box::new(move |relay_parent, validation_data| {
let parent = HeadData::decode(&mut &validation_data.parent_head.0[..]) let parent = match HeadData::decode(&mut &validation_data.parent_head.0[..]) {
.expect("Decodes parent head"); Err(err) => {
log::error!("Requested to build on top of malformed head-data: {:?}", err);
return futures::future::ready(None).boxed()
},
Ok(p) => p,
};
let (block_data, head_data) = state.lock().unwrap().advance(parent); let (block_data, head_data) = match state.lock().unwrap().advance(parent.clone()) {
Err(err) => {
log::error!("Unable to build on top of {:?}: {:?}", parent, err);
return futures::future::ready(None).boxed()
},
Ok(x) => x,
};
log::info!( log::info!(
"created a new collation on relay-parent({}): {:?}", "created a new collation on relay-parent({}): {:?}",
@@ -280,7 +288,6 @@ impl Collator {
"Seconded statement should match our collation: {:?}", "Seconded statement should match our collation: {:?}",
res.statement.payload() res.statement.payload()
); );
std::process::exit(-1);
} }
seconded_collations.fetch_add(1, Ordering::Relaxed); seconded_collations.fetch_add(1, Ordering::Relaxed);
@@ -394,10 +401,10 @@ mod tests {
let collator = Collator::new(1_000, 1); let collator = Collator::new(1_000, 1);
let graveyard_size = collator.state.lock().unwrap().graveyard_size; let graveyard_size = collator.state.lock().unwrap().graveyard_size;
let mut head = calculate_head_and_state_for_number(10, graveyard_size, 1).0; let mut head = calculate_head_and_state_for_number(10, graveyard_size, 1).unwrap().0;
for i in 1..10 { for i in 1..10 {
head = collator.state.lock().unwrap().advance(head).1; head = collator.state.lock().unwrap().advance(head).unwrap().1;
assert_eq!(10 + i, head.number); assert_eq!(10 + i, head.number);
} }
@@ -414,7 +421,7 @@ mod tests {
.clone(); .clone();
for _ in 1..20 { for _ in 1..20 {
second_head = collator.state.lock().unwrap().advance(second_head.clone()).1; second_head = collator.state.lock().unwrap().advance(second_head.clone()).unwrap().1;
} }
assert_eq!(second_head, head); assert_eq!(second_head, head);
@@ -96,8 +96,9 @@ fn main() -> Result<()> {
let config = CollationGenerationConfig { let config = CollationGenerationConfig {
key: collator.collator_key(), key: collator.collator_key(),
collator: collator collator: Some(
.create_collation_function(full_node.task_manager.spawn_handle()), collator.create_collation_function(full_node.task_manager.spawn_handle()),
),
para_id, para_id,
}; };
overseer_handle overseer_handle
+11
View File
@@ -239,5 +239,16 @@ sp_api::decl_runtime_apis! {
dispute_proof: vstaging::slashing::DisputeProof, dispute_proof: vstaging::slashing::DisputeProof,
key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof,
) -> Option<()>; ) -> Option<()>;
/***** Asynchronous backing *****/
/// Returns the state of parachain backing for a given para.
/// This is a staging method! Do not use on production runtimes!
#[api_version(99)]
fn staging_para_backing_state(_: ppp::Id) -> Option<vstaging::BackingState<H, N>>;
/// Returns candidate's acceptance limitations for asynchronous backing for a relay parent.
#[api_version(99)]
fn staging_async_backing_params() -> vstaging::AsyncBackingParams;
} }
} }
+13 -15
View File
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. // along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! `V1` Primitives. //! `V2` Primitives.
use bitvec::vec::BitVec; use bitvec::vec::BitVec;
use parity_scale_codec::{Decode, Encode}; use parity_scale_codec::{Decode, Encode};
@@ -797,7 +797,7 @@ impl TypeIndex for CoreIndex {
} }
/// The unique (during session) index of a validator group. /// The unique (during session) index of a validator group.
#[derive(Encode, Decode, Default, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] #[derive(Encode, Decode, Default, Clone, Copy, Debug, PartialEq, Eq, TypeInfo, PartialOrd, Ord)]
#[cfg_attr(feature = "std", derive(Hash))] #[cfg_attr(feature = "std", derive(Hash))]
pub struct GroupIndex(pub u32); pub struct GroupIndex(pub u32);
@@ -813,7 +813,7 @@ impl TypeIndex for GroupIndex {
} }
} }
/// A claim on authoring the next block for a given parathread. /// A claim on authoring the next block for a given parathread (on-demand parachain).
#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)] #[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)]
pub struct ParathreadClaim(pub Id, pub Option<CollatorId>); pub struct ParathreadClaim(pub Id, pub Option<CollatorId>);
@@ -1233,10 +1233,10 @@ pub const POLKADOT_ENGINE_ID: runtime_primitives::ConsensusEngineId = *b"POL1";
/// A consensus log item for polkadot validation. To be used with [`POLKADOT_ENGINE_ID`]. /// A consensus log item for polkadot validation. To be used with [`POLKADOT_ENGINE_ID`].
#[derive(Decode, Encode, Clone, PartialEq, Eq)] #[derive(Decode, Encode, Clone, PartialEq, Eq)]
pub enum ConsensusLog { pub enum ConsensusLog {
/// A parachain or parathread upgraded its code. /// A parachain upgraded its code.
#[codec(index = 1)] #[codec(index = 1)]
ParaUpgradeCode(Id, ValidationCodeHash), ParaUpgradeCode(Id, ValidationCodeHash),
/// A parachain or parathread scheduled a code upgrade. /// A parachain scheduled a code upgrade.
#[codec(index = 2)] #[codec(index = 2)]
ParaScheduleUpgradeCode(Id, ValidationCodeHash, BlockNumber), ParaScheduleUpgradeCode(Id, ValidationCodeHash, BlockNumber),
/// Governance requests to auto-approve every candidate included up to the given block /// Governance requests to auto-approve every candidate included up to the given block
@@ -1544,7 +1544,7 @@ const BACKING_STATEMENT_MAGIC: [u8; 4] = *b"BKNG";
/// Statements that can be made about parachain candidates. These are the /// Statements that can be made about parachain candidates. These are the
/// actual values that are signed. /// actual values that are signed.
#[derive(Clone, PartialEq, Eq, RuntimeDebug)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Hash))] #[cfg_attr(feature = "std", derive(Hash))]
pub enum CompactStatement { pub enum CompactStatement {
/// Proposal of a parachain candidate. /// Proposal of a parachain candidate.
@@ -1559,6 +1559,13 @@ impl CompactStatement {
pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> { pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> {
(self, context).encode() (self, context).encode()
} }
/// Get the underlying candidate hash this references.
pub fn candidate_hash(&self) -> &CandidateHash {
match *self {
CompactStatement::Seconded(ref h) | CompactStatement::Valid(ref h) => h,
}
}
} }
// Inner helper for codec on `CompactStatement`. // Inner helper for codec on `CompactStatement`.
@@ -1607,15 +1614,6 @@ impl parity_scale_codec::Decode for CompactStatement {
} }
} }
impl CompactStatement {
/// Get the underlying candidate hash this references.
pub fn candidate_hash(&self) -> &CandidateHash {
match *self {
CompactStatement::Seconded(ref h) | CompactStatement::Valid(ref h) => h,
}
}
}
/// `IndexedVec` struct indexed by type specific indices. /// `IndexedVec` struct indexed by type specific indices.
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(PartialEq))] #[cfg_attr(feature = "std", derive(PartialEq))]

Some files were not shown because too many files have changed in this diff Show More