mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 19:11:04 +00:00
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:
@@ -21,3 +21,5 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [
|
||||
[dev-dependencies]
|
||||
polkadot-node-subsystem-test-helpers = { path = "../subsystem-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" }
|
||||
|
||||
@@ -31,21 +31,25 @@
|
||||
|
||||
#![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 polkadot_node_primitives::{AvailableData, CollationGenerationConfig, PoV};
|
||||
use polkadot_node_primitives::{
|
||||
AvailableData, Collation, CollationGenerationConfig, CollationSecondedSignal, PoV,
|
||||
SubmitCollationParams,
|
||||
};
|
||||
use polkadot_node_subsystem::{
|
||||
messages::{CollationGenerationMessage, CollatorProtocolMessage},
|
||||
overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem,
|
||||
overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, RuntimeApiError, SpawnedSubsystem,
|
||||
SubsystemContext, SubsystemError, SubsystemResult,
|
||||
};
|
||||
use polkadot_node_subsystem_util::{
|
||||
request_availability_cores, request_persisted_validation_data, request_validation_code,
|
||||
request_validation_code_hash, request_validators,
|
||||
request_availability_cores, request_persisted_validation_data,
|
||||
request_staging_async_backing_params, request_validation_code, request_validation_code_hash,
|
||||
request_validators,
|
||||
};
|
||||
use polkadot_primitives::{
|
||||
collator_signature_payload, CandidateCommitments, CandidateDescriptor, CandidateReceipt,
|
||||
CoreState, Hash, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData,
|
||||
CollatorPair, CoreState, Hash, Id as ParaId, OccupiedCoreAssumption, PersistedValidationData,
|
||||
ValidationCodeHash,
|
||||
};
|
||||
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.
|
||||
/// Otherwise, most are logged and then discarded.
|
||||
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 {
|
||||
select! {
|
||||
incoming = ctx.recv().fuse() => {
|
||||
if self.handle_incoming::<Context>(incoming, &mut ctx, &sender).await {
|
||||
if self.handle_incoming::<Context>(incoming, &mut ctx).await {
|
||||
break;
|
||||
}
|
||||
},
|
||||
msg = receiver.next() => {
|
||||
if let Some(msg) = msg {
|
||||
ctx.send_message(msg).await;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +110,6 @@ impl CollationGenerationSubsystem {
|
||||
&mut self,
|
||||
incoming: SubsystemResult<FromOrchestra<<Context as SubsystemContext>::Message>>,
|
||||
ctx: &mut Context,
|
||||
sender: &mpsc::Sender<overseer::CollationGenerationOutgoingMessages>,
|
||||
) -> bool {
|
||||
match incoming {
|
||||
Ok(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
|
||||
@@ -134,7 +124,6 @@ impl CollationGenerationSubsystem {
|
||||
activated.into_iter().map(|v| v.hash),
|
||||
ctx,
|
||||
metrics,
|
||||
sender,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -155,6 +144,21 @@ impl CollationGenerationSubsystem {
|
||||
}
|
||||
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,
|
||||
Err(err) => {
|
||||
gum::error!(
|
||||
@@ -188,23 +192,28 @@ async fn handle_new_activations<Context>(
|
||||
activated: impl IntoIterator<Item = Hash>,
|
||||
ctx: &mut Context,
|
||||
metrics: Metrics,
|
||||
sender: &mpsc::Sender<overseer::CollationGenerationOutgoingMessages>,
|
||||
) -> crate::error::Result<()> {
|
||||
// follow the procedure from the guide:
|
||||
// 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();
|
||||
|
||||
for relay_parent in activated {
|
||||
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_validators(relay_parent, ctx.sender()).await,
|
||||
request_staging_async_backing_params(relay_parent, ctx.sender()).await,
|
||||
);
|
||||
|
||||
let availability_cores = availability_cores??;
|
||||
let n_validators = validators??.len();
|
||||
let async_backing_params = async_backing_params?.ok();
|
||||
|
||||
for (core_idx, core) in availability_cores.into_iter().enumerate() {
|
||||
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 {
|
||||
CoreState::Scheduled(scheduled_core) =>
|
||||
(scheduled_core, OccupiedCoreAssumption::Free),
|
||||
CoreState::Occupied(_occupied_core) => {
|
||||
// TODO: https://github.com/paritytech/polkadot/issues/1573
|
||||
gum::trace!(
|
||||
target: LOG_TARGET,
|
||||
core_idx = %core_idx,
|
||||
relay_parent = ?relay_parent,
|
||||
"core is occupied. Keep going.",
|
||||
);
|
||||
continue
|
||||
CoreState::Occupied(occupied_core) => match async_backing_params {
|
||||
Some(params) if params.max_candidate_depth >= 1 => {
|
||||
// maximum candidate depth when building on top of a block
|
||||
// pending availability is necessarily 1 - the depth of the
|
||||
// pending block is 0 so the child has depth 1.
|
||||
|
||||
// TODO [now]: this assumes that next up == current.
|
||||
// in practice we should only set `OccupiedCoreAssumption::Included`
|
||||
// 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 => {
|
||||
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,
|
||||
scheduled_core.para_id,
|
||||
assumption,
|
||||
@@ -294,15 +318,18 @@ async fn handle_new_activations<Context>(
|
||||
};
|
||||
|
||||
let task_config = config.clone();
|
||||
let mut task_sender = sender.clone();
|
||||
let metrics = metrics.clone();
|
||||
let mut task_sender = ctx.sender().clone();
|
||||
ctx.spawn(
|
||||
"collation-builder",
|
||||
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) =
|
||||
match (task_config.collator)(relay_parent, &validation_data).await {
|
||||
match collator_fn(relay_parent, &validation_data).await {
|
||||
Some(collation) => collation.into_inner(),
|
||||
None => {
|
||||
gum::debug!(
|
||||
@@ -314,104 +341,21 @@ async fn handle_new_activations<Context>(
|
||||
},
|
||||
};
|
||||
|
||||
// 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 = %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),
|
||||
construct_and_distribute_receipt(
|
||||
PreparedCollation {
|
||||
collation,
|
||||
para_id: scheduled_core.para_id,
|
||||
relay_parent,
|
||||
collator: task_config.key.public(),
|
||||
persisted_validation_data_hash,
|
||||
pov_hash,
|
||||
erasure_root,
|
||||
para_head: commitments.head_data.hash(),
|
||||
validation_data,
|
||||
validation_code_hash,
|
||||
n_validators,
|
||||
},
|
||||
};
|
||||
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
candidate_hash = ?ccr.hash(),
|
||||
?pov_hash,
|
||||
?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",
|
||||
);
|
||||
}
|
||||
task_config.key.clone(),
|
||||
&mut task_sender,
|
||||
result_sender,
|
||||
&metrics,
|
||||
)
|
||||
.await;
|
||||
}),
|
||||
)?;
|
||||
}
|
||||
@@ -420,14 +364,199 @@ async fn handle_new_activations<Context>(
|
||||
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,
|
||||
¶_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,
|
||||
para_id: ParaId,
|
||||
assumption: OccupiedCoreAssumption,
|
||||
sender: &mut impl overseer::CollationGenerationSenderTrait,
|
||||
) -> Result<Option<ValidationCodeHash>, crate::error::Error> {
|
||||
use polkadot_node_subsystem::RuntimeApiError;
|
||||
|
||||
) -> crate::error::Result<Option<ValidationCodeHash>> {
|
||||
match request_validation_code_hash(relay_parent, para_id, assumption, sender)
|
||||
.await
|
||||
.await?
|
||||
|
||||
@@ -22,6 +22,7 @@ pub(crate) struct MetricsInner {
|
||||
pub(crate) new_activations_overall: prometheus::Histogram,
|
||||
pub(crate) new_activations_per_relay_parent: prometheus::Histogram,
|
||||
pub(crate) new_activations_per_availability_core: prometheus::Histogram,
|
||||
pub(crate) submit_collation: prometheus::Histogram,
|
||||
}
|
||||
|
||||
/// `CollationGenerationSubsystem` metrics.
|
||||
@@ -57,6 +58,11 @@ impl Metrics {
|
||||
.as_ref()
|
||||
.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 {
|
||||
@@ -96,6 +102,15 @@ impl metrics::Metrics for Metrics {
|
||||
)?,
|
||||
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)))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,10 +19,10 @@ use futures::channel::{mpsc, oneshot};
|
||||
|
||||
use polkadot_node_subsystem::{
|
||||
messages::{StoreAvailableDataError, ValidationFailed},
|
||||
SubsystemError,
|
||||
RuntimeApiError, SubsystemError,
|
||||
};
|
||||
use polkadot_node_subsystem_util::Error as UtilError;
|
||||
use polkadot_primitives::BackedCandidate;
|
||||
use polkadot_node_subsystem_util::{runtime, Error as UtilError};
|
||||
use polkadot_primitives::{BackedCandidate, ValidationCodeHash};
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
@@ -33,6 +33,18 @@ pub type FatalResult<T> = std::result::Result<T, FatalError>;
|
||||
#[allow(missing_docs)]
|
||||
#[fatality::fatality(splitable)]
|
||||
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")]
|
||||
CandidateNotFound,
|
||||
|
||||
@@ -45,16 +57,27 @@ pub enum Error {
|
||||
#[error("FetchPoV failed")]
|
||||
FetchPoV,
|
||||
|
||||
#[fatal]
|
||||
#[error("Failed to spawn background task")]
|
||||
FailedToSpawnBackgroundTask,
|
||||
#[error("Fetching validation code by hash failed {0:?}, {1:?}")]
|
||||
FetchValidationCode(ValidationCodeHash, RuntimeApiError),
|
||||
|
||||
#[error("ValidateFromChainState channel closed before receipt")]
|
||||
ValidateFromChainState(#[source] oneshot::Canceled),
|
||||
#[error("Fetching Runtime API version failed {0:?}")]
|
||||
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")]
|
||||
StoreAvailableDataChannel(#[source] oneshot::Canceled),
|
||||
|
||||
#[error("RuntimeAPISubsystem channel closed before receipt")]
|
||||
RuntimeApiUnavailable(#[source] oneshot::Canceled),
|
||||
|
||||
#[error("a channel was closed before receipt in try_join!")]
|
||||
JoinMultiple(#[source] oneshot::Canceled),
|
||||
|
||||
@@ -64,10 +87,6 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
ValidationFailed(#[from] ValidationFailed),
|
||||
|
||||
#[fatal]
|
||||
#[error(transparent)]
|
||||
BackgroundValidationMpsc(#[from] mpsc::SendError),
|
||||
|
||||
#[error(transparent)]
|
||||
UtilError(#[from] UtilError),
|
||||
|
||||
|
||||
+1457
-749
File diff suppressed because it is too large
Load Diff
+618
-139
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,
|
||||
validation_code_hash: zeros.into(),
|
||||
};
|
||||
let candidate = CandidateReceipt { descriptor, commitments_hash: zeros };
|
||||
candidate
|
||||
CandidateReceipt { descriptor, commitments_hash: zeros }
|
||||
}
|
||||
|
||||
/// 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(¶_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(¶) {
|
||||
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(¶) {
|
||||
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(¶) {
|
||||
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(¶) {
|
||||
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(¶) {
|
||||
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(¶) {
|
||||
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(¶) {
|
||||
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
@@ -28,6 +28,10 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[allow(missing_docs)]
|
||||
#[fatality::fatality(splitable)]
|
||||
pub enum Error {
|
||||
#[fatal(forward)]
|
||||
#[error("Error while accessing runtime information")]
|
||||
Runtime(#[from] util::runtime::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Util(#[from] util::Error),
|
||||
|
||||
@@ -46,11 +50,14 @@ pub enum Error {
|
||||
#[error("failed to get votes on dispute")]
|
||||
CanceledCandidateVotes(#[source] oneshot::Canceled),
|
||||
|
||||
#[error("failed to get backable candidate from prospective parachains")]
|
||||
CanceledBackableCandidate(#[source] oneshot::Canceled),
|
||||
|
||||
#[error(transparent)]
|
||||
ChainApi(#[from] ChainApiError),
|
||||
|
||||
#[error(transparent)]
|
||||
Runtime(#[from] RuntimeApiError),
|
||||
RuntimeApi(#[from] RuntimeApiError),
|
||||
|
||||
#[error("failed to send message to ChainAPI")]
|
||||
ChainApiMessageSend(#[source] mpsc::SendError),
|
||||
|
||||
@@ -28,18 +28,20 @@ use futures_timer::Delay;
|
||||
use polkadot_node_subsystem::{
|
||||
jaeger,
|
||||
messages::{
|
||||
CandidateBackingMessage, ChainApiMessage, ProvisionableData, ProvisionerInherentData,
|
||||
ProvisionerMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
CandidateBackingMessage, ChainApiMessage, ProspectiveParachainsMessage, ProvisionableData,
|
||||
ProvisionerInherentData, ProvisionerMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
},
|
||||
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, LeafStatus, OverseerSignal,
|
||||
PerLeafSpan, RuntimeApiError, SpawnedSubsystem, SubsystemError,
|
||||
};
|
||||
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::{
|
||||
BackedCandidate, BlockNumber, CandidateReceipt, CoreState, Hash, OccupiedCoreAssumption,
|
||||
SignedAvailabilityBitfield, ValidatorIndex,
|
||||
BackedCandidate, BlockNumber, CandidateHash, CandidateReceipt, CoreState, Hash, Id as ParaId,
|
||||
OccupiedCoreAssumption, SignedAvailabilityBitfield, ValidatorIndex,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
@@ -79,6 +81,7 @@ impl ProvisionerSubsystem {
|
||||
pub struct PerRelayParent {
|
||||
leaf: ActivatedLeaf,
|
||||
backed_candidates: Vec<CandidateReceipt>,
|
||||
prospective_parachains_mode: ProspectiveParachainsMode,
|
||||
signed_bitfields: Vec<SignedAvailabilityBitfield>,
|
||||
is_inherent_ready: bool,
|
||||
awaiting_inherent: Vec<oneshot::Sender<ProvisionerInherentData>>,
|
||||
@@ -86,12 +89,13 @@ pub struct 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");
|
||||
|
||||
Self {
|
||||
leaf,
|
||||
backed_candidates: Vec::new(),
|
||||
prospective_parachains_mode,
|
||||
signed_bitfields: Vec::new(),
|
||||
is_inherent_ready: false,
|
||||
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.
|
||||
match from_overseer.map_err(Error::OverseerExited)? {
|
||||
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::Conclude) => return Ok(()),
|
||||
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,
|
||||
per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
|
||||
inherent_delays: &mut InherentDelays,
|
||||
) {
|
||||
) -> Result<(), Error> {
|
||||
gum::trace!(target: LOG_TARGET, "Handle ActiveLeavesUpdate");
|
||||
for deactivated in &update.deactivated {
|
||||
per_relay_parent.remove(deactivated);
|
||||
@@ -187,10 +192,13 @@ fn handle_active_leaves_update(
|
||||
|
||||
if let Some(leaf) = update.activated {
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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 signed_bitfields = per_relay_parent.signed_bitfields.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 mut sender = ctx.sender().clone();
|
||||
@@ -262,6 +271,7 @@ async fn send_inherent_data_bg<Context>(
|
||||
&leaf,
|
||||
&signed_bitfields,
|
||||
&backed_candidates,
|
||||
mode,
|
||||
return_senders,
|
||||
&mut sender,
|
||||
&metrics,
|
||||
@@ -290,7 +300,6 @@ async fn send_inherent_data_bg<Context>(
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
signed_bitfield_count = signed_bitfields.len(),
|
||||
backed_candidates_count = backed_candidates.len(),
|
||||
leaf_hash = ?leaf.hash,
|
||||
"inherent data sent successfully"
|
||||
);
|
||||
@@ -325,7 +334,7 @@ fn note_provisionable_data(
|
||||
.child("provisionable-backed")
|
||||
.with_candidate(candidate_hash)
|
||||
.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.
|
||||
// Risks from misbehavior are sufficiently mitigated at the protocol level
|
||||
@@ -373,6 +382,7 @@ async fn send_inherent_data(
|
||||
leaf: &ActivatedLeaf,
|
||||
bitfields: &[SignedAvailabilityBitfield],
|
||||
candidates: &[CandidateReceipt],
|
||||
prospective_parachains_mode: ProspectiveParachainsMode,
|
||||
return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>,
|
||||
from_job: &mut impl overseer::ProvisionerSenderTrait,
|
||||
metrics: &Metrics,
|
||||
@@ -424,8 +434,16 @@ async fn send_inherent_data(
|
||||
relay_parent = ?leaf.hash,
|
||||
"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!(
|
||||
target: LOG_TARGET,
|
||||
@@ -532,15 +550,16 @@ fn select_availability_bitfields(
|
||||
selected.into_values().collect()
|
||||
}
|
||||
|
||||
/// Determine which cores are free, and then to the degree possible, pick a candidate appropriate to
|
||||
/// each free core.
|
||||
async fn select_candidates(
|
||||
/// Selects candidates from tracked ones to note in a relay chain block.
|
||||
///
|
||||
/// Should be called when prospective parachains are disabled.
|
||||
async fn select_candidate_hashes_from_tracked(
|
||||
availability_cores: &[CoreState],
|
||||
bitfields: &[SignedAvailabilityBitfield],
|
||||
candidates: &[CandidateReceipt],
|
||||
relay_parent: Hash,
|
||||
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 mut selected_candidates =
|
||||
@@ -611,18 +630,112 @@ async fn select_candidates(
|
||||
"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,
|
||||
leaf_hash=?relay_parent,
|
||||
"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
|
||||
let (tx, rx) = oneshot::channel();
|
||||
sender.send_unbounded_message(CandidateBackingMessage::GetBackedCandidates(
|
||||
relay_parent,
|
||||
selected_candidates.clone(),
|
||||
tx,
|
||||
));
|
||||
@@ -638,7 +751,7 @@ async fn select_candidates(
|
||||
// checking them in order, we can ensure that the backed candidates are also in order.
|
||||
let mut backed_idx = 0;
|
||||
for selected in selected_candidates {
|
||||
if selected ==
|
||||
if selected.0 ==
|
||||
candidates.get(backed_idx).ok_or(Error::BackedCandidateOrderingProblem)?.hash()
|
||||
{
|
||||
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
|
||||
/// of a set of signed availability bitfields. It goes like this:
|
||||
///
|
||||
|
||||
@@ -19,6 +19,8 @@ use ::test_helpers::{dummy_candidate_descriptor, dummy_hash};
|
||||
use bitvec::bitvec;
|
||||
use polkadot_primitives::{OccupiedCore, ScheduledCore};
|
||||
|
||||
const MOCK_GROUP_SIZE: usize = 5;
|
||||
|
||||
pub fn occupied_core(para_id: u32) -> CoreState {
|
||||
CoreState::Occupied(OccupiedCore {
|
||||
group_responsible: para_id.into(),
|
||||
@@ -46,8 +48,8 @@ where
|
||||
CoreState::Occupied(core)
|
||||
}
|
||||
|
||||
pub fn default_bitvec(n_cores: usize) -> CoreAvailability {
|
||||
bitvec![u8, bitvec::order::Lsb0; 0; n_cores]
|
||||
pub fn default_bitvec(size: usize) -> CoreAvailability {
|
||||
bitvec![u8, bitvec::order::Lsb0; 0; size]
|
||||
}
|
||||
|
||||
pub fn scheduled_core(id: u32) -> ScheduledCore {
|
||||
@@ -237,7 +239,7 @@ pub(crate) mod common {
|
||||
mod select_candidates {
|
||||
use super::{
|
||||
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 futures::channel::mpsc;
|
||||
@@ -248,6 +250,7 @@ mod select_candidates {
|
||||
},
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers::TestSubsystemSender;
|
||||
use polkadot_node_subsystem_util::runtime::ProspectiveParachainsMode;
|
||||
use polkadot_primitives::{
|
||||
BlockNumber, CandidateCommitments, CommittedCandidateReceipt, PersistedValidationData,
|
||||
};
|
||||
@@ -333,10 +336,17 @@ mod select_candidates {
|
||||
async fn mock_overseer(
|
||||
mut receiver: mpsc::UnboundedReceiver<AllMessages>,
|
||||
expected: Vec<BackedCandidate>,
|
||||
prospective_parachains_mode: ProspectiveParachainsMode,
|
||||
) {
|
||||
use ChainApiMessage::BlockNumber;
|
||||
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 {
|
||||
match from_job {
|
||||
AllMessages::ChainApi(BlockNumber(_relay_parent, tx)) =>
|
||||
@@ -348,11 +358,28 @@ mod select_candidates {
|
||||
AllMessages::RuntimeApi(Request(_parent_hash, AvailabilityCores(tx))) =>
|
||||
tx.send(Ok(mock_availability_cores())).unwrap(),
|
||||
AllMessages::CandidateBacking(CandidateBackingMessage::GetBackedCandidates(
|
||||
_,
|
||||
_,
|
||||
hashes,
|
||||
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),
|
||||
}
|
||||
@@ -362,9 +389,19 @@ mod select_candidates {
|
||||
#[test]
|
||||
fn can_succeed() {
|
||||
test_harness(
|
||||
|r| mock_overseer(r, Vec::new()),
|
||||
|r| mock_overseer(r, Vec::new(), ProspectiveParachainsMode::Disabled),
|
||||
|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]
|
||||
fn selects_correct_candidates() {
|
||||
let mock_cores = mock_availability_cores();
|
||||
let n_cores = mock_cores.len();
|
||||
|
||||
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()
|
||||
let expected_candidates: Vec<_> =
|
||||
[1, 4, 7, 8, 10].iter().map(|&idx| candidates[idx].clone()).collect();
|
||||
let prospective_parachains_mode = ProspectiveParachainsMode::Disabled;
|
||||
|
||||
let expected_backed = expected_candidates
|
||||
.iter()
|
||||
@@ -424,17 +461,23 @@ mod select_candidates {
|
||||
commitments: Default::default(),
|
||||
},
|
||||
validity_votes: Vec::new(),
|
||||
validator_indices: default_bitvec(n_cores),
|
||||
validator_indices: default_bitvec(MOCK_GROUP_SIZE),
|
||||
})
|
||||
.collect();
|
||||
|
||||
test_harness(
|
||||
|r| mock_overseer(r, expected_backed),
|
||||
|r| mock_overseer(r, expected_backed, prospective_parachains_mode),
|
||||
|mut tx: TestSubsystemSender| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = select_candidates(
|
||||
&mock_cores,
|
||||
&[],
|
||||
&candidates,
|
||||
prospective_parachains_mode,
|
||||
Default::default(),
|
||||
&mut tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
result.into_iter().for_each(|c| {
|
||||
assert!(
|
||||
@@ -450,15 +493,16 @@ mod select_candidates {
|
||||
#[test]
|
||||
fn selects_max_one_code_upgrade() {
|
||||
let mock_cores = mock_availability_cores();
|
||||
let n_cores = mock_cores.len();
|
||||
|
||||
let empty_hash = PersistedValidationData::<Hash, BlockNumber>::default().hash();
|
||||
|
||||
// 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].
|
||||
let cores = [1, 7, 10];
|
||||
let cores = [1, 4, 7, 8, 10];
|
||||
let cores_with_code = [1, 4, 8];
|
||||
|
||||
let expected_cores = [1, 7, 10];
|
||||
|
||||
let committed_receipts: Vec<_> = (0..mock_cores.len())
|
||||
.map(|i| {
|
||||
let mut descriptor = dummy_candidate_descriptor(dummy_hash());
|
||||
@@ -478,27 +522,173 @@ mod select_candidates {
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Input to select_candidates
|
||||
let candidates: Vec<_> = committed_receipts.iter().map(|r| r.to_plain()).collect();
|
||||
|
||||
let expected_candidates: Vec<_> =
|
||||
cores.iter().map(|&idx| candidates[idx].clone()).collect();
|
||||
|
||||
let expected_backed: Vec<_> = cores
|
||||
// Build possible outputs from select_candidates
|
||||
let backed_candidates: Vec<_> = committed_receipts
|
||||
.iter()
|
||||
.map(|&idx| BackedCandidate {
|
||||
candidate: committed_receipts[idx].clone(),
|
||||
.map(|committed_receipt| BackedCandidate {
|
||||
candidate: committed_receipt.clone(),
|
||||
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();
|
||||
|
||||
test_harness(
|
||||
|r| mock_overseer(r, expected_backed),
|
||||
|r| mock_overseer(r, expected_backed, prospective_parachains_mode),
|
||||
|mut tx: TestSubsystemSender| async move {
|
||||
let result =
|
||||
select_candidates(&mock_cores, &[], &candidates, Default::default(), &mut tx)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = select_candidates(
|
||||
&mock_cores,
|
||||
&[],
|
||||
&[],
|
||||
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| {
|
||||
assert!(
|
||||
|
||||
@@ -68,6 +68,9 @@ pub(crate) struct RequestResultCache {
|
||||
LruCache<Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>>,
|
||||
key_ownership_proof:
|
||||
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 {
|
||||
@@ -97,6 +100,9 @@ impl Default for RequestResultCache {
|
||||
disputes: LruCache::new(DEFAULT_CACHE_CAP),
|
||||
unapplied_slashes: 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<()>> {
|
||||
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 {
|
||||
@@ -476,4 +512,7 @@ pub(crate) enum RequestResult {
|
||||
vstaging::slashing::OpaqueKeyOwnershipProof,
|
||||
Option<()>,
|
||||
),
|
||||
|
||||
StagingParaBackingState(Hash, ParaId, Option<vstaging::BackingState>),
|
||||
StagingAsyncBackingParams(Hash, vstaging::AsyncBackingParams),
|
||||
}
|
||||
|
||||
@@ -163,6 +163,12 @@ where
|
||||
.requests_cache
|
||||
.cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof),
|
||||
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::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,
|
||||
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> {
|
||||
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]
|
||||
|
||||
@@ -16,14 +16,14 @@ bob: reports block height is at least 2
|
||||
bob: reports peers count is at least 2
|
||||
charlie: reports block height 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
|
||||
bob: reports parachain_candidate_disputes_total is at least 1 within 90 seconds
|
||||
charlie: reports 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
|
||||
bob: reports 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
|
||||
alice: reports 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
|
||||
bob: reports 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 parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds
|
||||
alice: reports polkadot_parachain_candidate_disputes_total is at least 1 within 250 seconds
|
||||
bob: reports polkadot_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 polkadot_parachain_candidate_dispute_votes{validity="valid"} is at least 1 within 90 seconds
|
||||
bob: reports polkadot_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 polkadot_parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds
|
||||
alice: reports polkadot_parachain_candidate_dispute_concluded{validity="invalid"} is 0 within 90 seconds
|
||||
bob: reports polkadot_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 polkadot_parachain_candidate_dispute_concluded{validity="valid"} is at least 1 within 90 seconds
|
||||
|
||||
@@ -30,6 +30,7 @@ use polkadot_node_subsystem::{
|
||||
|
||||
use polkadot_primitives::{
|
||||
CandidateCommitments, CandidateDescriptor, CandidateReceipt, PersistedValidationData,
|
||||
PvfExecTimeoutKind,
|
||||
};
|
||||
|
||||
use futures::channel::oneshot;
|
||||
@@ -48,6 +49,55 @@ pub enum FakeCandidateValidation {
|
||||
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
|
||||
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)]
|
||||
#[value(rename_all = "kebab-case")]
|
||||
@@ -162,11 +212,20 @@ where
|
||||
pub fn create_fake_candidate_commitments(
|
||||
persisted_validation_data: &PersistedValidationData,
|
||||
) -> 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 {
|
||||
upward_messages: Default::default(),
|
||||
horizontal_messages: Default::default(),
|
||||
new_validation_code: None,
|
||||
head_data: persisted_validation_data.parent_head.clone(),
|
||||
head_data,
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: persisted_validation_data.relay_parent_number,
|
||||
}
|
||||
@@ -224,8 +283,7 @@ where
|
||||
),
|
||||
} => {
|
||||
match self.fake_validation {
|
||||
FakeCandidateValidation::ApprovalValid |
|
||||
FakeCandidateValidation::BackingAndApprovalValid => {
|
||||
x if x.misbehaves_valid() && x.should_misbehave(timeout) => {
|
||||
// Behave normally if the `PoV` is not known to be malicious.
|
||||
if pov.block_data.0.as_slice() != MALICIOUS_POV {
|
||||
return Some(FromOrchestra::Communication {
|
||||
@@ -278,8 +336,7 @@ where
|
||||
},
|
||||
}
|
||||
},
|
||||
FakeCandidateValidation::ApprovalInvalid |
|
||||
FakeCandidateValidation::BackingAndApprovalInvalid => {
|
||||
x if x.misbehaves_invalid() && x.should_misbehave(timeout) => {
|
||||
// Set the validation result to invalid with probability `p` and trigger a
|
||||
// dispute
|
||||
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
|
||||
@@ -342,8 +399,7 @@ where
|
||||
),
|
||||
} => {
|
||||
match self.fake_validation {
|
||||
FakeCandidateValidation::BackingValid |
|
||||
FakeCandidateValidation::BackingAndApprovalValid => {
|
||||
x if x.misbehaves_valid() && x.should_misbehave(timeout) => {
|
||||
// Behave normally if the `PoV` is not known to be malicious.
|
||||
if pov.block_data.0.as_slice() != MALICIOUS_POV {
|
||||
return Some(FromOrchestra::Communication {
|
||||
@@ -385,8 +441,7 @@ where
|
||||
}),
|
||||
}
|
||||
},
|
||||
FakeCandidateValidation::BackingInvalid |
|
||||
FakeCandidateValidation::BackingAndApprovalInvalid => {
|
||||
x if x.misbehaves_invalid() && x.should_misbehave(timeout) => {
|
||||
// Maliciously set the validation result to invalid for a valid candidate
|
||||
// with probability `p`
|
||||
let behave_maliciously = self.distribution.sample(&mut rand::thread_rng());
|
||||
|
||||
@@ -79,7 +79,13 @@ where
|
||||
) -> Option<FromOrchestra<Self::Message>> {
|
||||
match msg {
|
||||
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!(
|
||||
target: MALUS,
|
||||
@@ -156,8 +162,10 @@ where
|
||||
"Fetched validation data."
|
||||
);
|
||||
|
||||
let malicious_available_data =
|
||||
AvailableData { pov: Arc::new(pov.clone()), validation_data };
|
||||
let malicious_available_data = AvailableData {
|
||||
pov: Arc::new(pov.clone()),
|
||||
validation_data: validation_data.clone(),
|
||||
};
|
||||
|
||||
let pov_hash = pov.hash();
|
||||
let erasure_root = {
|
||||
@@ -211,6 +219,7 @@ where
|
||||
msg: CandidateBackingMessage::Second(
|
||||
relay_parent,
|
||||
malicious_candidate,
|
||||
validation_data,
|
||||
pov,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -25,8 +25,9 @@ use polkadot_node_jaeger as jaeger;
|
||||
use polkadot_node_network_protocol::{
|
||||
self as net_protocol,
|
||||
grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology},
|
||||
peer_set::MAX_NOTIFICATION_SIZE,
|
||||
v1 as protocol_v1, PeerId, UnifiedReputationChange as Rep, Versioned, View,
|
||||
peer_set::{ValidationVersion, MAX_NOTIFICATION_SIZE},
|
||||
v1 as protocol_v1, vstaging as protocol_vstaging, PeerId, UnifiedReputationChange as Rep,
|
||||
Versioned, VersionedValidationProtocol, View,
|
||||
};
|
||||
use polkadot_node_primitives::approval::{
|
||||
AssignmentCert, BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote,
|
||||
@@ -159,6 +160,15 @@ enum Resend {
|
||||
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.
|
||||
///
|
||||
/// It tracks metadata about our view of the unfinalized chain,
|
||||
@@ -179,7 +189,7 @@ struct State {
|
||||
pending_known: HashMap<Hash, Vec<(PeerId, PendingMessage)>>,
|
||||
|
||||
/// 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.
|
||||
topologies: SessionGridTopologies,
|
||||
@@ -349,14 +359,30 @@ impl State {
|
||||
rng: &mut (impl CryptoRng + Rng),
|
||||
) {
|
||||
match event {
|
||||
NetworkBridgeEvent::PeerConnected(peer_id, role, _, _) => {
|
||||
NetworkBridgeEvent::PeerConnected(peer_id, role, version, _) => {
|
||||
// insert a blank view if none already present
|
||||
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) => {
|
||||
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)| {
|
||||
entry.known_by.remove(&peer_id);
|
||||
})
|
||||
@@ -393,12 +419,12 @@ impl State {
|
||||
live
|
||||
});
|
||||
},
|
||||
NetworkBridgeEvent::PeerMessage(peer_id, msg) => {
|
||||
self.process_incoming_peer_message(ctx, metrics, peer_id, msg, rng).await;
|
||||
},
|
||||
NetworkBridgeEvent::UpdatedAuthorityIds { .. } => {
|
||||
// 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();
|
||||
for (peer_id, view) in self.peer_views.iter() {
|
||||
let intersection = view.iter().filter(|h| new_hashes.contains(h));
|
||||
let view_intersection = View::new(intersection.cloned(), view.finalized_number);
|
||||
for (peer_id, data) in self.peer_data.iter() {
|
||||
let intersection = data.view.iter().filter(|h| new_hashes.contains(h));
|
||||
let view_intersection =
|
||||
View::new(intersection.cloned(), data.view.finalized_number);
|
||||
Self::unify_with_peer(
|
||||
sender,
|
||||
metrics,
|
||||
&mut self.blocks,
|
||||
&self.topologies,
|
||||
self.peer_views.len(),
|
||||
self.peer_data.len(),
|
||||
*peer_id,
|
||||
data.version,
|
||||
view_intersection,
|
||||
rng,
|
||||
)
|
||||
@@ -547,6 +575,7 @@ impl State {
|
||||
|
||||
adjust_required_routing_and_propagate(
|
||||
ctx,
|
||||
&self.peer_data,
|
||||
&mut self.blocks,
|
||||
&self.topologies,
|
||||
|block_entry| block_entry.session == session,
|
||||
@@ -566,13 +595,16 @@ impl State {
|
||||
ctx: &mut Context,
|
||||
metrics: &Metrics,
|
||||
peer_id: PeerId,
|
||||
msg: protocol_v1::ApprovalDistributionMessage,
|
||||
msg: net_protocol::ApprovalDistributionMessage,
|
||||
rng: &mut R,
|
||||
) where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
match msg {
|
||||
protocol_v1::ApprovalDistributionMessage::Assignments(assignments) => {
|
||||
Versioned::V1(protocol_v1::ApprovalDistributionMessage::Assignments(assignments)) |
|
||||
Versioned::VStaging(protocol_vstaging::ApprovalDistributionMessage::Assignments(
|
||||
assignments,
|
||||
)) => {
|
||||
gum::trace!(
|
||||
target: LOG_TARGET,
|
||||
peer_id = %peer_id,
|
||||
@@ -611,7 +643,10 @@ impl State {
|
||||
.await;
|
||||
}
|
||||
},
|
||||
protocol_v1::ApprovalDistributionMessage::Approvals(approvals) => {
|
||||
Versioned::V1(protocol_v1::ApprovalDistributionMessage::Approvals(approvals)) |
|
||||
Versioned::VStaging(protocol_vstaging::ApprovalDistributionMessage::Approvals(
|
||||
approvals,
|
||||
)) => {
|
||||
gum::trace!(
|
||||
target: LOG_TARGET,
|
||||
peer_id = %peer_id,
|
||||
@@ -664,9 +699,14 @@ impl State {
|
||||
{
|
||||
gum::trace!(target: LOG_TARGET, ?view, "Peer view change");
|
||||
let finalized_number = view.finalized_number;
|
||||
let old_view =
|
||||
self.peer_views.get_mut(&peer_id).map(|d| std::mem::replace(d, view.clone()));
|
||||
let old_finalized_number = old_view.map(|v| v.finalized_number).unwrap_or(0);
|
||||
let (peer_protocol_version, old_finalized_number) = match self
|
||||
.peer_data
|
||||
.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
|
||||
let blocks = &mut self.blocks;
|
||||
@@ -691,8 +731,9 @@ impl State {
|
||||
metrics,
|
||||
&mut self.blocks,
|
||||
&self.topologies,
|
||||
self.peer_views.len(),
|
||||
self.peer_data.len(),
|
||||
peer_id,
|
||||
peer_protocol_version,
|
||||
view,
|
||||
rng,
|
||||
)
|
||||
@@ -992,7 +1033,7 @@ impl State {
|
||||
// then messages will be sent when we get it.
|
||||
|
||||
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 mut peer_filter = move |peer| {
|
||||
@@ -1019,31 +1060,53 @@ impl State {
|
||||
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.
|
||||
for peer in peers.iter() {
|
||||
// we already filtered peers above, so this should always be Some
|
||||
if let Some(peer_knowledge) = entry.known_by.get_mut(peer) {
|
||||
peer_knowledge.sent.insert(message_subject.clone(), message_kind);
|
||||
// Add the metadata of the assignment to the knowledge of each peer.
|
||||
for (peer, _) in peers.iter() {
|
||||
// we already filtered peers above, so this should always be Some
|
||||
if let Some(peer_knowledge) = entry.known_by.get_mut(peer) {
|
||||
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() {
|
||||
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",
|
||||
);
|
||||
|
||||
if !vstaging_peers.is_empty() {
|
||||
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
|
||||
peers,
|
||||
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
|
||||
protocol_v1::ApprovalDistributionMessage::Assignments(assignments),
|
||||
)),
|
||||
vstaging_peers,
|
||||
versioned_assignments_packet(ValidationVersion::VStaging, assignments.clone()),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
@@ -1332,38 +1395,55 @@ impl State {
|
||||
in_topology || knowledge.sent.contains(message_subject, MessageKind::Assignment)
|
||||
};
|
||||
|
||||
let peers = entry
|
||||
.known_by
|
||||
.iter()
|
||||
.filter(|(p, k)| peer_filter(p, k))
|
||||
.map(|(p, _)| p)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let (v1_peers, vstaging_peers) = {
|
||||
let peer_data = &self.peer_data;
|
||||
let peers = entry
|
||||
.known_by
|
||||
.iter()
|
||||
.filter_map(|(p, k)| peer_data.get(&p).map(|pd| (p, k, pd.version)))
|
||||
.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.
|
||||
for peer in peers.iter() {
|
||||
// we already filtered peers above, so this should always be Some
|
||||
if let Some(entry) = entry.known_by.get_mut(peer) {
|
||||
entry.sent.insert(message_subject.clone(), message_kind);
|
||||
// Add the metadata of the assignment to the knowledge of each peer.
|
||||
for (peer, _) in peers.iter() {
|
||||
// we already filtered peers above, so this should always be Some
|
||||
if let Some(peer_knowledge) = entry.known_by.get_mut(peer) {
|
||||
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() {
|
||||
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",
|
||||
);
|
||||
|
||||
if !vstaging_peers.is_empty() {
|
||||
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
|
||||
peers,
|
||||
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
|
||||
protocol_v1::ApprovalDistributionMessage::Approvals(approvals),
|
||||
)),
|
||||
vstaging_peers,
|
||||
versioned_approvals_packet(ValidationVersion::VStaging, approvals),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
@@ -1427,6 +1507,7 @@ impl State {
|
||||
topologies: &SessionGridTopologies,
|
||||
total_peers: usize,
|
||||
peer_id: PeerId,
|
||||
peer_protocol_version: ValidationVersion,
|
||||
view: View,
|
||||
rng: &mut (impl CryptoRng + Rng),
|
||||
) {
|
||||
@@ -1536,7 +1617,8 @@ impl State {
|
||||
"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() {
|
||||
@@ -1547,7 +1629,7 @@ impl State {
|
||||
"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(
|
||||
ctx,
|
||||
&self.peer_data,
|
||||
&mut self.blocks,
|
||||
&self.topologies,
|
||||
|block_entry| {
|
||||
@@ -1610,6 +1693,7 @@ impl State {
|
||||
|
||||
adjust_required_routing_and_propagate(
|
||||
ctx,
|
||||
&self.peer_data,
|
||||
&mut self.blocks,
|
||||
&self.topologies,
|
||||
|block_entry| {
|
||||
@@ -1669,6 +1753,7 @@ impl State {
|
||||
#[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)]
|
||||
async fn adjust_required_routing_and_propagate<Context, BlockFilter, RoutingModifier>(
|
||||
ctx: &mut Context,
|
||||
peer_data: &HashMap<PeerId, PeerData>,
|
||||
blocks: &mut HashMap<Hash, BlockEntry>,
|
||||
topologies: &SessionGridTopologies,
|
||||
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.
|
||||
|
||||
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 {
|
||||
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)]
|
||||
impl<Context> ApprovalDistribution {
|
||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||
@@ -1954,19 +2093,16 @@ pub(crate) async fn send_assignments_batched(
|
||||
sender: &mut impl overseer::ApprovalDistributionSenderTrait,
|
||||
assignments: Vec<(IndirectAssignmentCert, CandidateIndex)>,
|
||||
peer: PeerId,
|
||||
protocol_version: ValidationVersion,
|
||||
) {
|
||||
let mut batches = assignments.into_iter().peekable();
|
||||
|
||||
while batches.peek().is_some() {
|
||||
let batch: Vec<_> = batches.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect();
|
||||
let versioned = versioned_assignments_packet(protocol_version, batch);
|
||||
|
||||
sender
|
||||
.send_message(NetworkBridgeTxMessage::SendValidationMessage(
|
||||
vec![peer],
|
||||
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
|
||||
protocol_v1::ApprovalDistributionMessage::Assignments(batch),
|
||||
)),
|
||||
))
|
||||
.send_message(NetworkBridgeTxMessage::SendValidationMessage(vec![peer], versioned))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -1976,19 +2112,16 @@ pub(crate) async fn send_approvals_batched(
|
||||
sender: &mut impl overseer::ApprovalDistributionSenderTrait,
|
||||
approvals: Vec<IndirectSignedApprovalVote>,
|
||||
peer: PeerId,
|
||||
protocol_version: ValidationVersion,
|
||||
) {
|
||||
let mut batches = approvals.into_iter().peekable();
|
||||
|
||||
while batches.peek().is_some() {
|
||||
let batch: Vec<_> = batches.by_ref().take(MAX_APPROVAL_BATCH_SIZE).collect();
|
||||
let versioned = versioned_approvals_packet(protocol_version, batch);
|
||||
|
||||
sender
|
||||
.send_message(NetworkBridgeTxMessage::SendValidationMessage(
|
||||
vec![peer],
|
||||
Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution(
|
||||
protocol_v1::ApprovalDistributionMessage::Approvals(batch),
|
||||
)),
|
||||
))
|
||||
.send_message(NetworkBridgeTxMessage::SendValidationMessage(vec![peer], versioned))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +219,7 @@ async fn setup_gossip_topology(
|
||||
async fn setup_peer_with_view(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
peer_id: &PeerId,
|
||||
validation_version: ValidationVersion,
|
||||
view: View,
|
||||
) {
|
||||
overseer_send(
|
||||
@@ -226,7 +227,7 @@ async fn setup_peer_with_view(
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected(
|
||||
*peer_id,
|
||||
ObservedRole::Full,
|
||||
ValidationVersion::V1.into(),
|
||||
validation_version.into(),
|
||||
None,
|
||||
)),
|
||||
)
|
||||
@@ -243,13 +244,12 @@ async fn setup_peer_with_view(
|
||||
async fn send_message_from_peer(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
peer_id: &PeerId,
|
||||
msg: protocol_v1::ApprovalDistributionMessage,
|
||||
msg: net_protocol::ApprovalDistributionMessage,
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(
|
||||
*peer_id,
|
||||
Versioned::V1(msg),
|
||||
*peer_id, msg,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
@@ -331,9 +331,9 @@ fn try_import_the_same_assignment() {
|
||||
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
|
||||
let overseer = &mut virtual_overseer;
|
||||
// setup peers
|
||||
setup_peer_with_view(overseer, &peer_a, view![]).await;
|
||||
setup_peer_with_view(overseer, &peer_b, view![hash]).await;
|
||||
setup_peer_with_view(overseer, &peer_c, view![hash]).await;
|
||||
setup_peer_with_view(overseer, &peer_a, ValidationVersion::V1, view![]).await;
|
||||
setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await;
|
||||
setup_peer_with_view(overseer, &peer_c, ValidationVersion::V1, view![hash]).await;
|
||||
|
||||
// new block `hash_a` with 1 candidates
|
||||
let meta = BlockApprovalMeta {
|
||||
@@ -353,7 +353,7 @@ fn try_import_the_same_assignment() {
|
||||
let assignments = vec![(cert.clone(), 0u32)];
|
||||
|
||||
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;
|
||||
|
||||
@@ -386,11 +386,11 @@ fn try_import_the_same_assignment() {
|
||||
);
|
||||
|
||||
// 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
|
||||
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, BENEFIT_VALID_MESSAGE).await;
|
||||
@@ -413,7 +413,7 @@ fn delay_reputation_change() {
|
||||
let overseer = &mut virtual_overseer;
|
||||
|
||||
// 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
|
||||
let meta = BlockApprovalMeta {
|
||||
@@ -433,7 +433,7 @@ fn delay_reputation_change() {
|
||||
let assignments = vec![(cert.clone(), 0u32)];
|
||||
|
||||
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
|
||||
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 overseer = &mut virtual_overseer;
|
||||
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
|
||||
let candidates_count = 20;
|
||||
@@ -501,7 +501,7 @@ fn spam_attack_results_in_negative_reputation_change() {
|
||||
.collect();
|
||||
|
||||
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 {
|
||||
expect_reputation_change(overseer, peer, COST_UNEXPECTED_MESSAGE).await;
|
||||
@@ -533,7 +533,7 @@ fn spam_attack_results_in_negative_reputation_change() {
|
||||
.await;
|
||||
|
||||
// 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
|
||||
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 overseer = &mut virtual_overseer;
|
||||
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
|
||||
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
|
||||
let assignments = vec![(cert, candidate_index)];
|
||||
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");
|
||||
|
||||
// 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
|
||||
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 overseer = &mut virtual_overseer;
|
||||
// setup peers
|
||||
setup_peer_with_view(overseer, &peer_a, view![]).await;
|
||||
setup_peer_with_view(overseer, &peer_b, view![hash]).await;
|
||||
setup_peer_with_view(overseer, &peer_c, view![hash]).await;
|
||||
setup_peer_with_view(overseer, &peer_a, ValidationVersion::V1, view![]).await;
|
||||
setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await;
|
||||
setup_peer_with_view(overseer, &peer_c, ValidationVersion::V1, view![hash]).await;
|
||||
|
||||
// new block `hash_a` with 1 candidates
|
||||
let meta = BlockApprovalMeta {
|
||||
@@ -681,7 +681,7 @@ fn import_approval_happy_path() {
|
||||
signature: dummy_signature(),
|
||||
};
|
||||
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!(
|
||||
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 overseer = &mut virtual_overseer;
|
||||
// setup peers
|
||||
setup_peer_with_view(overseer, &peer_a, view![]).await;
|
||||
setup_peer_with_view(overseer, &peer_b, view![hash]).await;
|
||||
setup_peer_with_view(overseer, &peer_a, ValidationVersion::V1, view![]).await;
|
||||
setup_peer_with_view(overseer, &peer_b, ValidationVersion::V1, view![hash]).await;
|
||||
|
||||
// new block `hash_a` with 1 candidates
|
||||
let meta = BlockApprovalMeta {
|
||||
@@ -749,14 +749,14 @@ fn import_approval_bad() {
|
||||
signature: dummy_signature(),
|
||||
};
|
||||
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;
|
||||
|
||||
// now import an assignment from peer_b
|
||||
let assignments = vec![(cert.clone(), candidate_index)];
|
||||
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!(
|
||||
overseer_recv(overseer).await,
|
||||
@@ -775,7 +775,7 @@ fn import_approval_bad() {
|
||||
|
||||
// and try again
|
||||
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!(
|
||||
overseer_recv(overseer).await,
|
||||
@@ -916,7 +916,7 @@ fn update_peer_view() {
|
||||
overseer_send(overseer, ApprovalDistributionMessage::DistributeAssignment(cert_b, 0)).await;
|
||||
|
||||
// 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
|
||||
assert_matches!(
|
||||
@@ -934,7 +934,7 @@ fn update_peer_view() {
|
||||
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!(
|
||||
state
|
||||
.blocks
|
||||
@@ -986,7 +986,7 @@ fn update_peer_view() {
|
||||
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!(
|
||||
state
|
||||
.blocks
|
||||
@@ -1016,7 +1016,10 @@ fn update_peer_view() {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1031,7 +1034,7 @@ fn import_remotely_then_locally() {
|
||||
let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move {
|
||||
let overseer = &mut virtual_overseer;
|
||||
// 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
|
||||
let meta = BlockApprovalMeta {
|
||||
@@ -1051,7 +1054,7 @@ fn import_remotely_then_locally() {
|
||||
let cert = fake_assignment_cert(hash, validator_index);
|
||||
let assignments = vec![(cert.clone(), candidate_index)];
|
||||
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
|
||||
assert_matches!(
|
||||
@@ -1086,7 +1089,7 @@ fn import_remotely_then_locally() {
|
||||
signature: dummy_signature(),
|
||||
};
|
||||
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!(
|
||||
overseer_recv(overseer).await,
|
||||
@@ -1152,7 +1155,7 @@ fn sends_assignments_even_when_state_is_approved() {
|
||||
.await;
|
||||
|
||||
// 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 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
|
||||
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
|
||||
overseer_send(
|
||||
@@ -1237,7 +1240,7 @@ fn race_condition_in_local_vs_remote_view_update() {
|
||||
.collect();
|
||||
|
||||
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
|
||||
let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]);
|
||||
@@ -1280,7 +1283,7 @@ fn propagates_locally_generated_assignment_to_both_dimensions() {
|
||||
|
||||
// Connect all 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.
|
||||
@@ -1385,7 +1388,7 @@ fn propagates_assignments_along_unshared_dimension() {
|
||||
|
||||
// Connect all 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.
|
||||
@@ -1421,7 +1424,7 @@ fn propagates_assignments_along_unshared_dimension() {
|
||||
|
||||
// Issuer of the message is important, not the peer we receive from.
|
||||
// 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!(
|
||||
overseer_recv(overseer).await,
|
||||
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.
|
||||
// 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!(
|
||||
overseer_recv(overseer).await,
|
||||
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
|
||||
@@ -1527,7 +1530,7 @@ fn propagates_to_required_after_connect() {
|
||||
// Connect all peers except omitted.
|
||||
for (i, (peer, _)) in peers.iter().enumerate() {
|
||||
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() {
|
||||
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!(
|
||||
overseer_recv(overseer).await,
|
||||
@@ -1665,7 +1668,7 @@ fn sends_to_more_peers_after_getting_topology() {
|
||||
|
||||
// Connect all peers except omitted.
|
||||
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
|
||||
@@ -1817,7 +1820,7 @@ fn originator_aggression_l1() {
|
||||
|
||||
// Connect all peers except omitted.
|
||||
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
|
||||
@@ -1976,7 +1979,7 @@ fn non_originator_aggression_l1() {
|
||||
|
||||
// Connect all peers except omitted.
|
||||
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
|
||||
@@ -2010,7 +2013,7 @@ fn non_originator_aggression_l1() {
|
||||
|
||||
// Issuer of the message is important, not the peer we receive from.
|
||||
// 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!(
|
||||
overseer_recv(overseer).await,
|
||||
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
|
||||
@@ -2081,7 +2084,7 @@ fn non_originator_aggression_l2() {
|
||||
|
||||
// Connect all peers except omitted.
|
||||
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
|
||||
@@ -2115,7 +2118,7 @@ fn non_originator_aggression_l2() {
|
||||
|
||||
// Issuer of the message is important, not the peer we receive from.
|
||||
// 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!(
|
||||
overseer_recv(overseer).await,
|
||||
AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment(
|
||||
@@ -2246,7 +2249,7 @@ fn resends_messages_periodically() {
|
||||
|
||||
// Connect all 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.
|
||||
@@ -2281,7 +2284,7 @@ fn resends_messages_periodically() {
|
||||
|
||||
// Issuer of the message is important, not the peer we receive from.
|
||||
// 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!(
|
||||
overseer_recv(overseer).await,
|
||||
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) {
|
||||
use polkadot_node_subsystem::SubsystemContext;
|
||||
let pool = sp_core::testing::TaskExecutor::new();
|
||||
@@ -2402,8 +2525,9 @@ fn batch_test_round(message_count: usize) {
|
||||
.collect();
|
||||
|
||||
let peer = PeerId::random();
|
||||
send_assignments_batched(&mut sender, assignments.clone(), peer).await;
|
||||
send_approvals_batched(&mut sender, approvals.clone(), peer).await;
|
||||
send_assignments_batched(&mut sender, assignments.clone(), peer, ValidationVersion::V1)
|
||||
.await;
|
||||
send_approvals_batched(&mut sender, approvals.clone(), peer, ValidationVersion::V1).await;
|
||||
|
||||
// Check expected assignments batches.
|
||||
for assignment_index in (0..assignments.len()).step_by(super::MAX_ASSIGNMENT_BATCH_SIZE) {
|
||||
|
||||
@@ -6,6 +6,7 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
always-assert = "0.1"
|
||||
futures = "0.3.21"
|
||||
futures-timer = "3.0.2"
|
||||
gum = { package = "tracing-gum", path = "../../gum" }
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
|
||||
use always_assert::never;
|
||||
use futures::{channel::oneshot, FutureExt};
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
@@ -29,7 +30,9 @@ use polkadot_node_network_protocol::{
|
||||
grid_topology::{
|
||||
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::{
|
||||
jaeger, messages::*, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, PerLeafSpan,
|
||||
@@ -76,25 +79,63 @@ struct BitfieldGossipMessage {
|
||||
}
|
||||
|
||||
impl BitfieldGossipMessage {
|
||||
fn into_validation_protocol(self) -> net_protocol::VersionedValidationProtocol {
|
||||
self.into_network_message().into()
|
||||
fn into_validation_protocol(
|
||||
self,
|
||||
recipient_version: ProtocolVersion,
|
||||
) -> net_protocol::VersionedValidationProtocol {
|
||||
self.into_network_message(recipient_version).into()
|
||||
}
|
||||
|
||||
fn into_network_message(self) -> net_protocol::BitfieldDistributionMessage {
|
||||
Versioned::V1(protocol_v1::BitfieldDistributionMessage::Bitfield(
|
||||
self.relay_parent,
|
||||
self.signed_availability.into(),
|
||||
))
|
||||
fn into_network_message(
|
||||
self,
|
||||
recipient_version: ProtocolVersion,
|
||||
) -> 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
|
||||
/// overseer ordered us to work on.
|
||||
#[derive(Default, Debug)]
|
||||
struct ProtocolState {
|
||||
/// Track all active peers and their views
|
||||
/// to determine what is relevant to them.
|
||||
peer_views: HashMap<PeerId, View>,
|
||||
peer_data: HashMap<PeerId, PeerData>,
|
||||
|
||||
/// The current and previous gossip topologies
|
||||
topologies: SessionBoundGridTopologyStorage,
|
||||
@@ -357,7 +398,7 @@ async fn handle_bitfield_distribution<Context>(
|
||||
ctx,
|
||||
job_data,
|
||||
topology,
|
||||
&mut state.peer_views,
|
||||
&mut state.peer_data,
|
||||
validator,
|
||||
msg,
|
||||
required_routing,
|
||||
@@ -376,7 +417,7 @@ async fn relay_message<Context>(
|
||||
ctx: &mut Context,
|
||||
job_data: &mut PerRelayParentData,
|
||||
topology_neighbors: &GridNeighbors,
|
||||
peer_views: &mut HashMap<PeerId, View>,
|
||||
peers: &mut HashMap<PeerId, PeerData>,
|
||||
validator: ValidatorId,
|
||||
message: BitfieldGossipMessage,
|
||||
required_routing: RequiredRouting,
|
||||
@@ -394,16 +435,16 @@ async fn relay_message<Context>(
|
||||
.await;
|
||||
|
||||
drop(_span);
|
||||
let total_peers = peer_views.len();
|
||||
let total_peers = peers.len();
|
||||
let mut random_routing: RandomRouting = Default::default();
|
||||
|
||||
let _span = span.child("interested-peers");
|
||||
// pass on the bitfield distribution to all interested peers
|
||||
let interested_peers = peer_views
|
||||
let interested_peers = peers
|
||||
.iter()
|
||||
.filter_map(|(peer, view)| {
|
||||
.filter_map(|(peer, data)| {
|
||||
// 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 =
|
||||
job_data.message_from_validator_needed_by_peer(&peer, &validator);
|
||||
if message_needed {
|
||||
@@ -418,7 +459,7 @@ async fn relay_message<Context>(
|
||||
};
|
||||
|
||||
if need_routing {
|
||||
Some(*peer)
|
||||
Some((*peer, data.version))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -429,9 +470,9 @@ async fn relay_message<Context>(
|
||||
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
|
||||
job_data
|
||||
.message_sent_to_peer
|
||||
@@ -450,11 +491,35 @@ async fn relay_message<Context>(
|
||||
);
|
||||
} else {
|
||||
let _span = span.child("gossip");
|
||||
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
|
||||
interested_peers,
|
||||
message.into_validation_protocol(),
|
||||
))
|
||||
.await;
|
||||
|
||||
let filter_by_version = |peers: &[(PeerId, ProtocolVersion)],
|
||||
version: ValidationVersion| {
|
||||
peers
|
||||
.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,
|
||||
metrics: &Metrics,
|
||||
origin: PeerId,
|
||||
message: protocol_v1::BitfieldDistributionMessage,
|
||||
message: net_protocol::BitfieldDistributionMessage,
|
||||
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!(
|
||||
target: LOG_TARGET,
|
||||
peer = %origin,
|
||||
@@ -616,7 +691,7 @@ async fn process_incoming_peer_message<Context>(
|
||||
ctx,
|
||||
job_data,
|
||||
topology,
|
||||
&mut state.peer_views,
|
||||
&mut state.peer_data,
|
||||
validator,
|
||||
message,
|
||||
required_routing,
|
||||
@@ -647,15 +722,18 @@ async fn handle_network_msg<Context>(
|
||||
let _timer = metrics.time_handle_network_msg();
|
||||
|
||||
match bridge_message {
|
||||
NetworkBridgeEvent::PeerConnected(peer, role, _, _) => {
|
||||
NetworkBridgeEvent::PeerConnected(peer, role, version, _) => {
|
||||
gum::trace!(target: LOG_TARGET, ?peer, ?role, "Peer connected");
|
||||
// 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) => {
|
||||
gum::trace!(target: LOG_TARGET, ?peer, "Peer disconnected");
|
||||
// get rid of superfluous data
|
||||
state.peer_views.remove(&peer);
|
||||
state.peer_data.remove(&peer);
|
||||
},
|
||||
NetworkBridgeEvent::NewGossipTopology(gossip_topology) => {
|
||||
let session_index = gossip_topology.session;
|
||||
@@ -680,12 +758,21 @@ async fn handle_network_msg<Context>(
|
||||
);
|
||||
|
||||
for new_peer in newly_added {
|
||||
// in case we already knew that peer in the past
|
||||
// it might have had an existing view, we use to initialize
|
||||
// and minimize the delta on `PeerViewChange` to be sent
|
||||
if let Some(old_view) = state.peer_views.remove(&new_peer) {
|
||||
handle_peer_view_change(ctx, state, new_peer, old_view, rng).await;
|
||||
}
|
||||
let old_view = match state.peer_data.get_mut(&new_peer) {
|
||||
Some(d) => {
|
||||
// in case we already knew that peer in the past
|
||||
// it might have had an existing view, we use to initialize
|
||||
// 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) => {
|
||||
@@ -696,7 +783,7 @@ async fn handle_network_msg<Context>(
|
||||
gum::trace!(target: LOG_TARGET, ?new_view, "Our view change");
|
||||
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,
|
||||
NetworkBridgeEvent::UpdatedAuthorityIds { .. } => {
|
||||
// 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
|
||||
// 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)]
|
||||
async fn handle_peer_view_change<Context>(
|
||||
ctx: &mut Context,
|
||||
@@ -736,13 +826,20 @@ async fn handle_peer_view_change<Context>(
|
||||
view: View,
|
||||
rng: &mut (impl CryptoRng + Rng),
|
||||
) {
|
||||
let added = state
|
||||
.peer_views
|
||||
.entry(origin)
|
||||
.or_default()
|
||||
.replace_difference(view)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let peer_data = match state.peer_data.get_mut(&origin) {
|
||||
None => {
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
peer = ?origin,
|
||||
"Attempted to update peer view for unknown peer."
|
||||
);
|
||||
|
||||
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 is_gossip_peer = topology.route_to_peer(RequiredRouting::GridXY, &origin);
|
||||
@@ -808,11 +905,14 @@ async fn send_tracked_gossip_message<Context>(
|
||||
"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());
|
||||
|
||||
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
|
||||
vec![dest],
|
||||
message.into_validation_protocol(),
|
||||
message.into_validation_protocol(version),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ fn dummy_rng() -> ChaCha12Rng {
|
||||
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
|
||||
/// given message, which must be signed by `validator` and a set of peers
|
||||
/// 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"),
|
||||
},
|
||||
},
|
||||
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,
|
||||
view: our_view!(relay_parent),
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
@@ -211,7 +219,10 @@ fn receive_invalid_signature() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -222,7 +233,10 @@ fn receive_invalid_signature() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
// reputation change due to invalid signature
|
||||
@@ -256,7 +270,7 @@ fn receive_invalid_validator_index() {
|
||||
let (mut state, signing_context, keystore, validator) =
|
||||
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 signed = Signed::<AvailabilityBitfield>::sign(
|
||||
@@ -281,7 +295,10 @@ fn receive_invalid_validator_index() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&Default::default(),
|
||||
NetworkBridgeEvent::PeerMessage(peer_b, msg.into_network_message()),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer_b,
|
||||
msg.into_network_message(ValidationVersion::V1.into())
|
||||
),
|
||||
&mut rng,
|
||||
));
|
||||
|
||||
@@ -344,7 +361,10 @@ fn receive_duplicate_messages() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -377,7 +397,10 @@ fn receive_duplicate_messages() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -396,7 +419,10 @@ fn receive_duplicate_messages() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -463,7 +489,10 @@ fn delay_reputation_change() {
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: BitfieldDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerMessage(peer, msg.clone().into_network_message()),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer,
|
||||
msg.clone().into_network_message(ValidationVersion::V1.into()),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
@@ -486,7 +515,10 @@ fn delay_reputation_change() {
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: BitfieldDistributionMessage::NetworkBridgeUpdate(
|
||||
NetworkBridgeEvent::PeerMessage(peer, msg.clone().into_network_message()),
|
||||
NetworkBridgeEvent::PeerMessage(
|
||||
peer,
|
||||
msg.clone().into_network_message(ValidationVersion::V1.into()),
|
||||
),
|
||||
),
|
||||
})
|
||||
.await;
|
||||
@@ -546,8 +578,8 @@ fn do_not_relay_message_twice() {
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b, view![hash]);
|
||||
state.peer_views.insert(peer_a, view![hash]);
|
||||
state.peer_data.insert(peer_b, peer_data_v1(view![hash]));
|
||||
state.peer_data.insert(peer_a, peer_data_v1(view![hash]));
|
||||
|
||||
let msg =
|
||||
BitfieldGossipMessage { relay_parent: hash, signed_availability: signed_bitfield.clone() };
|
||||
@@ -564,7 +596,7 @@ fn do_not_relay_message_twice() {
|
||||
&mut ctx,
|
||||
state.per_relay_parent.get_mut(&hash).unwrap(),
|
||||
&gossip_peers,
|
||||
&mut state.peer_views,
|
||||
&mut state.peer_data,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
RequiredRouting::GridXY,
|
||||
@@ -591,7 +623,7 @@ fn do_not_relay_message_twice() {
|
||||
assert_eq!(2, peers.len());
|
||||
assert!(peers.contains(&peer_a));
|
||||
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,
|
||||
state.per_relay_parent.get_mut(&hash).unwrap(),
|
||||
&gossip_peers,
|
||||
&mut state.peer_views,
|
||||
&mut state.peer_data,
|
||||
validator.clone(),
|
||||
msg.clone(),
|
||||
RequiredRouting::GridXY,
|
||||
@@ -687,14 +719,17 @@ fn changing_view() {
|
||||
&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
|
||||
launch!(handle_network_msg(
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -729,8 +764,11 @@ fn changing_view() {
|
||||
&mut rng,
|
||||
));
|
||||
|
||||
assert!(state.peer_views.contains_key(&peer_b));
|
||||
assert_eq!(state.peer_views.get(&peer_b).expect("Must contain value for peer B"), &view![]);
|
||||
assert!(state.peer_data.contains_key(&peer_b));
|
||||
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,
|
||||
// should give penalty
|
||||
@@ -738,7 +776,10 @@ fn changing_view() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -770,7 +811,10 @@ fn changing_view() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -817,8 +861,8 @@ fn do_not_send_message_back_to_origin() {
|
||||
.flatten()
|
||||
.expect("should be signed");
|
||||
|
||||
state.peer_views.insert(peer_b, view![hash]);
|
||||
state.peer_views.insert(peer_a, view![hash]);
|
||||
state.peer_data.insert(peer_b, peer_data_v1(view![hash]));
|
||||
state.peer_data.insert(peer_a, peer_data_v1(view![hash]));
|
||||
|
||||
let msg =
|
||||
BitfieldGossipMessage { relay_parent: hash, signed_availability: signed_bitfield.clone() };
|
||||
@@ -833,7 +877,10 @@ fn do_not_send_message_back_to_origin() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -855,7 +902,7 @@ fn do_not_send_message_back_to_origin() {
|
||||
) => {
|
||||
assert_eq!(1, peers.len());
|
||||
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");
|
||||
|
||||
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 =
|
||||
@@ -948,7 +995,10 @@ fn topology_test() {
|
||||
&mut ctx,
|
||||
&mut state,
|
||||
&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,
|
||||
));
|
||||
|
||||
@@ -975,7 +1025,7 @@ fn topology_test() {
|
||||
assert!(topology.peers_x.iter().filter(|peer| peers.contains(&peer)).count() == 4);
|
||||
// Must never include originator
|
||||
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
|
||||
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())
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ pub(crate) fn send_message<M>(
|
||||
) where
|
||||
M: Encode + Clone,
|
||||
{
|
||||
if peers.is_empty() {
|
||||
return
|
||||
}
|
||||
let message = {
|
||||
let encoded = message.encode();
|
||||
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
|
||||
// network used `Bytes` this would not be necessary.
|
||||
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| {
|
||||
net.write_notification(peer, protocol_name.clone(), message.clone());
|
||||
});
|
||||
|
||||
@@ -33,7 +33,8 @@ use polkadot_node_network_protocol::{
|
||||
CollationVersion, PeerSet, PeerSetProtocolNames, PerPeerSet, ProtocolVersion,
|
||||
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::{
|
||||
@@ -246,15 +247,32 @@ where
|
||||
)
|
||||
.await;
|
||||
|
||||
send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Validation,
|
||||
version,
|
||||
&peerset_protocol_names,
|
||||
WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(local_view),
|
||||
&metrics,
|
||||
);
|
||||
match ValidationVersion::try_from(version)
|
||||
.expect("try_get_protocol has already checked version is known; qed")
|
||||
{
|
||||
ValidationVersion::V1 => send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Validation,
|
||||
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 => {
|
||||
dispatch_collation_events_to_all(
|
||||
@@ -271,15 +289,32 @@ where
|
||||
)
|
||||
.await;
|
||||
|
||||
send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Collation,
|
||||
version,
|
||||
&peerset_protocol_names,
|
||||
WireMessage::<protocol_v1::CollationProtocol>::ViewUpdate(local_view),
|
||||
&metrics,
|
||||
);
|
||||
match CollationVersion::try_from(version)
|
||||
.expect("try_get_protocol has already checked version is known; qed")
|
||||
{
|
||||
CollationVersion::V1 => send_message(
|
||||
&mut network_service,
|
||||
vec![peer],
|
||||
PeerSet::Collation,
|
||||
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() {
|
||||
let (events, reports) =
|
||||
if expected_versions[PeerSet::Validation] ==
|
||||
Some(ValidationVersion::V1.into())
|
||||
{
|
||||
handle_v1_peer_messages::<protocol_v1::ValidationProtocol, _>(
|
||||
remote,
|
||||
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."
|
||||
);
|
||||
let (events, reports) = if expected_versions[PeerSet::Validation] ==
|
||||
Some(ValidationVersion::V1.into())
|
||||
{
|
||||
handle_peer_messages::<protocol_v1::ValidationProtocol, _>(
|
||||
remote,
|
||||
PeerSet::Validation,
|
||||
&mut shared.0.lock().validation_peers,
|
||||
v_messages,
|
||||
&metrics,
|
||||
)
|
||||
} else if expected_versions[PeerSet::Validation] ==
|
||||
Some(ValidationVersion::VStaging.into())
|
||||
{
|
||||
handle_peer_messages::<protocol_vstaging::ValidationProtocol, _>(
|
||||
remote,
|
||||
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
|
||||
// eventually.
|
||||
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
|
||||
};
|
||||
// If a peer somehow triggers this, we'll disconnect them
|
||||
// eventually.
|
||||
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
|
||||
};
|
||||
|
||||
for report in reports {
|
||||
network_service.report_peer(remote, report.into());
|
||||
@@ -450,30 +494,39 @@ where
|
||||
}
|
||||
|
||||
if !c_messages.is_empty() {
|
||||
let (events, reports) =
|
||||
if expected_versions[PeerSet::Collation] ==
|
||||
Some(CollationVersion::V1.into())
|
||||
{
|
||||
handle_v1_peer_messages::<protocol_v1::CollationProtocol, _>(
|
||||
remote,
|
||||
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."
|
||||
);
|
||||
let (events, reports) = if expected_versions[PeerSet::Collation] ==
|
||||
Some(CollationVersion::V1.into())
|
||||
{
|
||||
handle_peer_messages::<protocol_v1::CollationProtocol, _>(
|
||||
remote,
|
||||
PeerSet::Collation,
|
||||
&mut shared.0.lock().collation_peers,
|
||||
c_messages,
|
||||
&metrics,
|
||||
)
|
||||
} else if expected_versions[PeerSet::Collation] ==
|
||||
Some(CollationVersion::VStaging.into())
|
||||
{
|
||||
handle_peer_messages::<protocol_vstaging::CollationProtocol, _>(
|
||||
remote,
|
||||
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
|
||||
// eventually.
|
||||
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
|
||||
};
|
||||
// If a peer somehow triggers this, we'll disconnect them
|
||||
// eventually.
|
||||
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
|
||||
};
|
||||
|
||||
for report in reports {
|
||||
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.collation_peers.keys().cloned().collect::<Vec<_>>(),
|
||||
shared
|
||||
.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(
|
||||
net,
|
||||
validation_peers,
|
||||
v1_validation_peers,
|
||||
peerset_protocol_names,
|
||||
WireMessage::ViewUpdate(new_view.clone()),
|
||||
metrics,
|
||||
@@ -753,7 +826,23 @@ fn update_our_view<Net, Context>(
|
||||
|
||||
send_collation_message_v1(
|
||||
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,
|
||||
WireMessage::ViewUpdate(new_view),
|
||||
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
|
||||
// peer-set.
|
||||
fn handle_v1_peer_messages<RawMessage: Decode, OutMessage: From<RawMessage>>(
|
||||
fn handle_peer_messages<RawMessage: Decode, OutMessage: From<RawMessage>>(
|
||||
peer: PeerId,
|
||||
peer_set: PeerSet,
|
||||
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(
|
||||
event: NetworkBridgeEvent<net_protocol::VersionedValidationProtocol>,
|
||||
ctx: &mut impl overseer::NetworkBridgeRxSenderTrait,
|
||||
|
||||
@@ -25,6 +25,7 @@ use parking_lot::Mutex;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
task::Poll,
|
||||
};
|
||||
|
||||
use sc_network::{Event as NetworkEvent, IfDisconnected, ProtocolName, ReputationChange};
|
||||
@@ -46,7 +47,7 @@ use polkadot_node_subsystem_test_helpers::{
|
||||
SingleItemSink, SingleItemStream, TestSubsystemContextHandle,
|
||||
};
|
||||
use polkadot_node_subsystem_util::metered;
|
||||
use polkadot_primitives::{AuthorityDiscoveryId, Hash};
|
||||
use polkadot_primitives::{AuthorityDiscoveryId, CandidateHash, Hash};
|
||||
|
||||
use sc_network::Multiaddr;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
@@ -142,8 +143,7 @@ impl Network for TestNetwork {
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) {
|
||||
let (peer_set, version) = self.protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
assert_eq!(version, peer_set.get_main_version());
|
||||
let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
|
||||
self.action_tx
|
||||
.lock()
|
||||
@@ -152,8 +152,7 @@ impl Network for TestNetwork {
|
||||
}
|
||||
|
||||
fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec<u8>) {
|
||||
let (peer_set, version) = self.protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
assert_eq!(version, peer_set.get_main_version());
|
||||
let (peer_set, _) = self.protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
|
||||
self.action_tx
|
||||
.lock()
|
||||
@@ -195,10 +194,17 @@ impl TestNetworkHandle {
|
||||
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 {
|
||||
remote: peer,
|
||||
protocol: self.protocol_names.get_main_name(peer_set),
|
||||
protocol: self.protocol_names.get_name(peer_set, protocol_version),
|
||||
negotiated_fallback: None,
|
||||
role: role.into(),
|
||||
received_handshake: vec![],
|
||||
@@ -432,8 +438,12 @@ fn send_our_view_upon_connection() {
|
||||
|
||||
handle.await_mode_switch().await;
|
||||
|
||||
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await;
|
||||
network_handle.connect_peer(peer, PeerSet::Collation, ObservedRole::Full).await;
|
||||
network_handle
|
||||
.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;
|
||||
|
||||
@@ -478,10 +488,10 @@ fn sends_view_updates_to_peers() {
|
||||
handle.await_mode_switch().await;
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
network_handle
|
||||
.connect_peer(peer_b, PeerSet::Collation, ObservedRole::Full)
|
||||
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
|
||||
.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);
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
network_handle
|
||||
.connect_peer(peer_b, PeerSet::Collation, ObservedRole::Full)
|
||||
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
|
||||
.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();
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
network_handle
|
||||
.connect_peer(peer_b, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
|
||||
await_peer_connections(&shared, 2, 0).await;
|
||||
@@ -696,7 +706,9 @@ fn peer_view_updates_sent_via_overseer() {
|
||||
|
||||
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;
|
||||
|
||||
@@ -746,7 +758,9 @@ fn peer_messages_sent_via_overseer() {
|
||||
|
||||
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;
|
||||
|
||||
@@ -819,8 +833,12 @@ fn peer_disconnect_from_just_one_peerset() {
|
||||
|
||||
let peer = PeerId::random();
|
||||
|
||||
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await;
|
||||
network_handle.connect_peer(peer, PeerSet::Collation, ObservedRole::Full).await;
|
||||
network_handle
|
||||
.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;
|
||||
|
||||
@@ -907,10 +925,10 @@ fn relays_collation_protocol_messages() {
|
||||
let peer_b = PeerId::random();
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
network_handle
|
||||
.connect_peer(peer_b, PeerSet::Collation, ObservedRole::Full)
|
||||
.connect_peer(peer_b, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
|
||||
.await;
|
||||
|
||||
await_peer_connections(&shared, 1, 1).await;
|
||||
@@ -1011,8 +1029,12 @@ fn different_views_on_different_peer_sets() {
|
||||
|
||||
let peer = PeerId::random();
|
||||
|
||||
network_handle.connect_peer(peer, PeerSet::Validation, ObservedRole::Full).await;
|
||||
network_handle.connect_peer(peer, PeerSet::Collation, ObservedRole::Full).await;
|
||||
network_handle
|
||||
.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;
|
||||
|
||||
@@ -1097,7 +1119,7 @@ fn sent_views_include_finalized_number_update() {
|
||||
let peer_a = PeerId::random();
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.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();
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer_a, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer_a, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.await;
|
||||
|
||||
await_peer_connections(&shared, 1, 0).await;
|
||||
@@ -1225,3 +1247,164 @@ fn our_view_updates_decreasing_order_and_limited_to_max() {
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use super::*;
|
||||
use polkadot_node_network_protocol::{
|
||||
peer_set::{CollationVersion, PeerSet, PeerSetProtocolNames, ValidationVersion},
|
||||
request_response::ReqProtocolNames,
|
||||
v1 as protocol_v1, PeerId, Versioned,
|
||||
v1 as protocol_v1, vstaging as protocol_vstaging, PeerId, Versioned,
|
||||
};
|
||||
|
||||
use polkadot_node_subsystem::{
|
||||
@@ -197,6 +197,13 @@ where
|
||||
WireMessage::ProtocolMessage(msg),
|
||||
&metrics,
|
||||
),
|
||||
Versioned::VStaging(msg) => send_validation_message_vstaging(
|
||||
&mut network_service,
|
||||
peers,
|
||||
peerset_protocol_names,
|
||||
WireMessage::ProtocolMessage(msg),
|
||||
&metrics,
|
||||
),
|
||||
}
|
||||
},
|
||||
NetworkBridgeTxMessage::SendValidationMessages(msgs) => {
|
||||
@@ -215,6 +222,13 @@ where
|
||||
WireMessage::ProtocolMessage(msg),
|
||||
&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),
|
||||
&metrics,
|
||||
),
|
||||
Versioned::VStaging(msg) => send_collation_message_vstaging(
|
||||
&mut network_service,
|
||||
peers,
|
||||
peerset_protocol_names,
|
||||
WireMessage::ProtocolMessage(msg),
|
||||
&metrics,
|
||||
),
|
||||
}
|
||||
},
|
||||
NetworkBridgeTxMessage::SendCollationMessages(msgs) => {
|
||||
@@ -251,6 +272,13 @@ where
|
||||
WireMessage::ProtocolMessage(msg),
|
||||
&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,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,8 +130,7 @@ impl Network for TestNetwork {
|
||||
}
|
||||
|
||||
fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) {
|
||||
let (peer_set, version) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
assert_eq!(version, peer_set.get_main_version());
|
||||
let (peer_set, _) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
|
||||
self.action_tx
|
||||
.lock()
|
||||
@@ -140,8 +139,7 @@ impl Network for TestNetwork {
|
||||
}
|
||||
|
||||
fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec<u8>) {
|
||||
let (peer_set, version) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
assert_eq!(version, peer_set.get_main_version());
|
||||
let (peer_set, _) = self.peerset_protocol_names.try_get_protocol(&protocol).unwrap();
|
||||
|
||||
self.action_tx
|
||||
.lock()
|
||||
@@ -173,10 +171,17 @@ impl TestNetworkHandle {
|
||||
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 {
|
||||
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,
|
||||
role: role.into(),
|
||||
received_handshake: vec![],
|
||||
@@ -242,7 +247,7 @@ fn send_messages_to_peers() {
|
||||
let peer = PeerId::random();
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer, PeerSet::Validation, ObservedRole::Full)
|
||||
.connect_peer(peer, ValidationVersion::V1, PeerSet::Validation, ObservedRole::Full)
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("Timeout does not occur");
|
||||
@@ -251,7 +256,7 @@ fn send_messages_to_peers() {
|
||||
// so the single item sink has to be free explicitly
|
||||
|
||||
network_handle
|
||||
.connect_peer(peer, PeerSet::Collation, ObservedRole::Full)
|
||||
.connect_peer(peer, ValidationVersion::V1, PeerSet::Collation, ObservedRole::Full)
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("Timeout does not occur");
|
||||
@@ -328,3 +333,107 @@ fn send_messages_to_peers() {
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
always-assert = "0.1.2"
|
||||
bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] }
|
||||
futures = "0.3.21"
|
||||
futures-timer = "3"
|
||||
@@ -23,6 +22,7 @@ polkadot-node-subsystem-util = { path = "../../subsystem-util" }
|
||||
polkadot-node-subsystem = {path = "../../subsystem" }
|
||||
fatality = "0.0.6"
|
||||
thiserror = "1.0.31"
|
||||
tokio-util = "0.7.1"
|
||||
|
||||
[dev-dependencies]
|
||||
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-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" }
|
||||
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>);
|
||||
|
||||
impl Metrics {
|
||||
pub fn on_advertisment_made(&self) {
|
||||
pub fn on_advertisement_made(&self) {
|
||||
if let Some(metrics) = &self.0 {
|
||||
metrics.advertisements_made.inc();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+366
-113
@@ -37,6 +37,7 @@ use polkadot_node_network_protocol::{
|
||||
};
|
||||
use polkadot_node_primitives::BlockData;
|
||||
use polkadot_node_subsystem::{
|
||||
errors::RuntimeApiError,
|
||||
jaeger,
|
||||
messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest},
|
||||
ActivatedLeaf, ActiveLeavesUpdate, LeafStatus,
|
||||
@@ -49,8 +50,13 @@ use polkadot_primitives::{
|
||||
};
|
||||
use polkadot_primitives_test_helpers::TestCandidateBuilder;
|
||||
|
||||
mod prospective_parachains;
|
||||
|
||||
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)]
|
||||
struct TestState {
|
||||
para_id: ParaId,
|
||||
@@ -186,6 +192,17 @@ impl TestState {
|
||||
)),
|
||||
)
|
||||
.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 {
|
||||
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>>(
|
||||
@@ -215,7 +233,9 @@ fn test_harness<T: Future<Output = TestHarness>>(
|
||||
let genesis_hash = Hash::repeat_byte(0xff);
|
||||
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);
|
||||
let subsystem = async {
|
||||
run_inner(
|
||||
@@ -223,6 +243,7 @@ fn test_harness<T: Future<Output = TestHarness>>(
|
||||
local_peer_id,
|
||||
collator_pair,
|
||||
collation_req_receiver,
|
||||
collation_req_vstaging_receiver,
|
||||
Default::default(),
|
||||
reputation,
|
||||
REPUTATION_CHANGE_TEST_INTERVAL,
|
||||
@@ -231,7 +252,7 @@ fn test_harness<T: Future<Output = TestHarness>>(
|
||||
.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!(subsystem);
|
||||
@@ -305,6 +326,17 @@ async fn setup_system(virtual_overseer: &mut VirtualOverseer, test_state: &TestS
|
||||
])),
|
||||
)
|
||||
.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`]
|
||||
@@ -313,29 +345,23 @@ struct DistributeCollation {
|
||||
pov_block: PoV,
|
||||
}
|
||||
|
||||
/// Create some PoV and distribute it.
|
||||
async fn distribute_collation(
|
||||
async fn distribute_collation_with_receipt(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
test_state: &TestState,
|
||||
// whether or not we expect a connection request or not.
|
||||
relay_parent: Hash,
|
||||
should_connect: bool,
|
||||
candidate: CandidateReceipt,
|
||||
pov: PoV,
|
||||
parent_head_data_hash: Hash,
|
||||
) -> 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(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::DistributeCollation(candidate.clone(), pov_block.clone(), None),
|
||||
CollatorProtocolMessage::DistributeCollation(
|
||||
candidate.clone(),
|
||||
parent_head_data_hash,
|
||||
pov.clone(),
|
||||
None,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -343,10 +369,10 @@ async fn distribute_collation(
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
_relay_parent,
|
||||
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();
|
||||
}
|
||||
);
|
||||
@@ -358,7 +384,7 @@ async fn distribute_collation(
|
||||
relay_parent,
|
||||
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();
|
||||
},
|
||||
|
||||
@@ -366,17 +392,17 @@ async fn distribute_collation(
|
||||
relay_parent,
|
||||
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());
|
||||
|
||||
tx.send(Ok(Some(test_state.session_info.clone()))).unwrap();
|
||||
},
|
||||
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
relay_parent,
|
||||
_relay_parent,
|
||||
RuntimeApiRequest::ValidatorGroups(tx),
|
||||
)) => {
|
||||
assert_eq!(relay_parent, test_state.relay_parent);
|
||||
assert_eq!(_relay_parent, relay_parent);
|
||||
tx.send(Ok((
|
||||
test_state.session_info.validator_groups.to_vec(),
|
||||
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
|
||||
async fn connect_peer(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
peer: PeerId,
|
||||
version: CollationVersion,
|
||||
authority_id: Option<AuthorityDiscoveryId>,
|
||||
) {
|
||||
overseer_send(
|
||||
@@ -414,7 +475,7 @@ async fn connect_peer(
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected(
|
||||
peer,
|
||||
polkadot_node_network_protocol::ObservedRole::Authority,
|
||||
CollationVersion::V1.into(),
|
||||
version.into(),
|
||||
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.
|
||||
///
|
||||
/// Expects vstaging message if `expected_candidate_hashes` is `Some`, v1 otherwise.
|
||||
async fn expect_advertise_collation_msg(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
peer: &PeerId,
|
||||
expected_relay_parent: Hash,
|
||||
expected_candidate_hashes: Option<Vec<CandidateHash>>,
|
||||
) {
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendCollationMessage(
|
||||
to,
|
||||
Versioned::V1(protocol_v1::CollationProtocol::CollatorProtocol(wire_message)),
|
||||
)
|
||||
) => {
|
||||
assert_eq!(to[0], *peer);
|
||||
assert_matches!(
|
||||
wire_message,
|
||||
protocol_v1::CollatorProtocolMessage::AdvertiseCollation(
|
||||
relay_parent,
|
||||
) => {
|
||||
assert_eq!(relay_parent, expected_relay_parent);
|
||||
let mut candidate_hashes: Option<HashSet<_>> =
|
||||
expected_candidate_hashes.map(|hashes| hashes.into_iter().collect());
|
||||
let iter_num = candidate_hashes.as_ref().map(|hashes| hashes.len()).unwrap_or(1);
|
||||
|
||||
for _ in 0..iter_num {
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::SendCollationMessage(
|
||||
to,
|
||||
wire_message,
|
||||
)
|
||||
) => {
|
||||
assert_eq!(to[0], *peer);
|
||||
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.
|
||||
@@ -528,19 +624,26 @@ fn advertise_and_send_collation() {
|
||||
ReputationAggregator::new(|_| true),
|
||||
|test_harness| async move {
|
||||
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;
|
||||
|
||||
let DistributeCollation { candidate, pov_block } =
|
||||
distribute_collation(&mut virtual_overseer, &test_state, true).await;
|
||||
let DistributeCollation { candidate, pov_block } = distribute_collation(
|
||||
&mut virtual_overseer,
|
||||
&test_state,
|
||||
test_state.relay_parent,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
for (val, peer) in test_state
|
||||
.current_group_validator_authority_ids()
|
||||
.into_iter()
|
||||
.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.
|
||||
@@ -558,18 +661,23 @@ fn advertise_and_send_collation() {
|
||||
|
||||
// The peer is interested in a leaf that we have a collation for;
|
||||
// advertise it.
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent)
|
||||
.await;
|
||||
expect_advertise_collation_msg(
|
||||
&mut virtual_overseer,
|
||||
&peer,
|
||||
test_state.relay_parent,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Request a collation.
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
req_cfg
|
||||
req_v1_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
}
|
||||
@@ -582,13 +690,13 @@ fn advertise_and_send_collation() {
|
||||
{
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
|
||||
req_cfg
|
||||
req_v1_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
}
|
||||
@@ -613,8 +721,8 @@ fn advertise_and_send_collation() {
|
||||
assert_matches!(
|
||||
rx.await,
|
||||
Ok(full_response) => {
|
||||
let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse
|
||||
= CollationFetchingResponse::decode(
|
||||
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()
|
||||
)
|
||||
@@ -632,13 +740,13 @@ fn advertise_and_send_collation() {
|
||||
// Re-request a collation.
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
|
||||
req_cfg
|
||||
req_v1_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: old_relay_parent,
|
||||
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());
|
||||
|
||||
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.
|
||||
overseer_send(
|
||||
@@ -664,9 +773,14 @@ fn advertise_and_send_collation() {
|
||||
)
|
||||
.await;
|
||||
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent)
|
||||
.await;
|
||||
TestHarness { virtual_overseer, req_cfg }
|
||||
expect_advertise_collation_msg(
|
||||
&mut virtual_overseer,
|
||||
&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),
|
||||
|test_harness| async move {
|
||||
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;
|
||||
|
||||
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
|
||||
.current_group_validator_authority_ids()
|
||||
.into_iter()
|
||||
.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.
|
||||
@@ -712,18 +834,23 @@ fn delay_reputation_change() {
|
||||
|
||||
// The peer is interested in a leaf that we have a collation for;
|
||||
// advertise it.
|
||||
expect_advertise_collation_msg(&mut virtual_overseer, &peer, test_state.relay_parent)
|
||||
.await;
|
||||
expect_advertise_collation_msg(
|
||||
&mut virtual_overseer,
|
||||
&peer,
|
||||
test_state.relay_parent,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Request a collation.
|
||||
let (pending_response, _rx) = oneshot::channel();
|
||||
req_cfg
|
||||
req_v1_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
}
|
||||
@@ -736,13 +863,13 @@ fn delay_reputation_change() {
|
||||
{
|
||||
let (pending_response, _rx) = oneshot::channel();
|
||||
|
||||
req_cfg
|
||||
req_v1_cfg
|
||||
.inbound_queue
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
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;
|
||||
|
||||
// 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;
|
||||
test_harness
|
||||
},
|
||||
@@ -843,10 +1059,10 @@ fn collations_are_only_advertised_to_validators_with_correct_view() {
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
// 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_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, &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.
|
||||
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.
|
||||
send_peer_view_change(virtual_overseer, &peer, vec![test_state.relay_parent]).await;
|
||||
|
||||
// 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
|
||||
},
|
||||
)
|
||||
@@ -890,15 +1109,16 @@ fn collate_on_two_different_relay_chain_blocks() {
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
// 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_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, &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;
|
||||
|
||||
@@ -906,14 +1126,16 @@ fn collate_on_two_different_relay_chain_blocks() {
|
||||
// parent are active.
|
||||
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;
|
||||
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;
|
||||
|
||||
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
|
||||
},
|
||||
)
|
||||
@@ -938,17 +1160,20 @@ fn validator_reconnect_does_not_advertise_a_second_time() {
|
||||
setup_system(virtual_overseer, &test_state).await;
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
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_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;
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
overseer_send(
|
||||
@@ -1031,19 +1256,20 @@ where
|
||||
ReputationAggregator::new(|_| true),
|
||||
|mut test_harness| async move {
|
||||
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;
|
||||
|
||||
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
|
||||
.current_group_validator_authority_ids()
|
||||
.into_iter()
|
||||
.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.
|
||||
@@ -1064,10 +1290,20 @@ where
|
||||
|
||||
// The peer is interested in a leaf that we have a collation for;
|
||||
// advertise it.
|
||||
expect_advertise_collation_msg(virtual_overseer, &validator_0, test_state.relay_parent)
|
||||
.await;
|
||||
expect_advertise_collation_msg(virtual_overseer, &validator_1, test_state.relay_parent)
|
||||
.await;
|
||||
expect_advertise_collation_msg(
|
||||
virtual_overseer,
|
||||
&validator_0,
|
||||
test_state.relay_parent,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
expect_advertise_collation_msg(
|
||||
virtual_overseer,
|
||||
&validator_1,
|
||||
test_state.relay_parent,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Request a collation.
|
||||
let (pending_response, rx) = oneshot::channel();
|
||||
@@ -1077,7 +1313,7 @@ where
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer: validator_0,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
}
|
||||
@@ -1092,8 +1328,8 @@ where
|
||||
let feedback_tx = assert_matches!(
|
||||
rx.await,
|
||||
Ok(full_response) => {
|
||||
let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse
|
||||
= CollationFetchingResponse::decode(
|
||||
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()
|
||||
)
|
||||
@@ -1113,7 +1349,7 @@ where
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer: validator_1,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: test_state.relay_parent,
|
||||
para_id: test_state.para_id,
|
||||
}
|
||||
@@ -1129,8 +1365,8 @@ where
|
||||
assert_matches!(
|
||||
rx.await,
|
||||
Ok(full_response) => {
|
||||
let CollationFetchingResponse::Collation(receipt, pov): CollationFetchingResponse
|
||||
= CollationFetchingResponse::decode(
|
||||
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()
|
||||
)
|
||||
@@ -1159,7 +1395,8 @@ fn connect_to_buffered_groups() {
|
||||
ReputationAggregator::new(|_| true),
|
||||
|test_harness| async move {
|
||||
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;
|
||||
|
||||
@@ -1167,7 +1404,13 @@ fn connect_to_buffered_groups() {
|
||||
let peers_a = test_state.current_group_validator_peer_ids();
|
||||
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!(
|
||||
overseer_recv(&mut virtual_overseer).await,
|
||||
@@ -1181,7 +1424,8 @@ fn connect_to_buffered_groups() {
|
||||
let head_a = test_state.relay_parent;
|
||||
|
||||
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 {
|
||||
@@ -1191,7 +1435,7 @@ fn connect_to_buffered_groups() {
|
||||
// Update views.
|
||||
for peed_id in &peers_a {
|
||||
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];
|
||||
@@ -1203,7 +1447,7 @@ fn connect_to_buffered_groups() {
|
||||
.unwrap()
|
||||
.send(RawIncomingRequest {
|
||||
peer,
|
||||
payload: CollationFetchingRequest {
|
||||
payload: request_v1::CollationFetchingRequest {
|
||||
relay_parent: head_a,
|
||||
para_id: test_state.para_id,
|
||||
}
|
||||
@@ -1215,14 +1459,17 @@ fn connect_to_buffered_groups() {
|
||||
assert_matches!(
|
||||
rx.await,
|
||||
Ok(full_response) => {
|
||||
let CollationFetchingResponse::Collation(..): CollationFetchingResponse =
|
||||
CollationFetchingResponse::decode(
|
||||
let request_v1::CollationFetchingResponse::Collation(..) =
|
||||
request_v1::CollationFetchingResponse::decode(
|
||||
&mut full_response.result.expect("We should have a proper answer").as_ref(),
|
||||
)
|
||||
.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.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!(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
|
||||
// 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 }
|
||||
},
|
||||
);
|
||||
}
|
||||
+575
@@ -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::{
|
||||
collections::{HashMap, VecDeque},
|
||||
future::Future,
|
||||
num::NonZeroUsize,
|
||||
ops::Range,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
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.
|
||||
///
|
||||
@@ -66,9 +72,9 @@ pub struct ValidatorGroupsBuffer {
|
||||
group_infos: VecDeque<ValidatorsGroupInfo>,
|
||||
/// Continuous buffer of validators discovery keys.
|
||||
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.
|
||||
should_be_connected: HashMap<Hash, BitVec>,
|
||||
should_be_connected: HashMap<CandidateHash, BitVec>,
|
||||
/// Buffer capacity, limits the number of **groups** tracked.
|
||||
cap: NonZeroUsize,
|
||||
}
|
||||
@@ -107,7 +113,7 @@ impl ValidatorGroupsBuffer {
|
||||
/// of the buffer.
|
||||
pub fn note_collation_advertised(
|
||||
&mut self,
|
||||
relay_parent: Hash,
|
||||
candidate_hash: CandidateHash,
|
||||
session_index: SessionIndex,
|
||||
group_index: GroupIndex,
|
||||
validators: &[AuthorityDiscoveryId],
|
||||
@@ -121,19 +127,19 @@ impl ValidatorGroupsBuffer {
|
||||
}) {
|
||||
Some((idx, group)) => {
|
||||
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.
|
||||
pub fn reset_validator_interest(
|
||||
&mut self,
|
||||
relay_parent: Hash,
|
||||
candidate_hash: CandidateHash,
|
||||
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,
|
||||
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
|
||||
/// advertisement.
|
||||
pub fn remove_relay_parent(&mut self, relay_parent: &Hash) {
|
||||
self.should_be_connected.remove(relay_parent);
|
||||
}
|
||||
|
||||
/// Removes all advertisements from the buffer.
|
||||
pub fn clear_advertisements(&mut self) {
|
||||
self.should_be_connected.clear();
|
||||
pub fn remove_candidate(&mut self, candidate_hash: &CandidateHash) {
|
||||
self.should_be_connected.remove(candidate_hash);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn push(
|
||||
&mut self,
|
||||
relay_parent: Hash,
|
||||
candidate_hash: CandidateHash,
|
||||
session_index: SessionIndex,
|
||||
group_index: GroupIndex,
|
||||
validators: &[AuthorityDiscoveryId],
|
||||
@@ -193,17 +194,17 @@ impl ValidatorGroupsBuffer {
|
||||
self.should_be_connected
|
||||
.values_mut()
|
||||
.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).
|
||||
/// If the relay parent is unknown, inserts 0-initialized bitvec first.
|
||||
///
|
||||
/// 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
|
||||
.should_be_connected
|
||||
.entry(relay_parent)
|
||||
.entry(candidate_hash)
|
||||
.or_insert_with(|| bitvec![0; self.validators.len()]);
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use polkadot_primitives::Hash;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
|
||||
#[test]
|
||||
@@ -227,8 +259,8 @@ mod tests {
|
||||
let cap = NonZeroUsize::new(1).unwrap();
|
||||
let mut buf = ValidatorGroupsBuffer::with_capacity(cap);
|
||||
|
||||
let hash_a = Hash::repeat_byte(0x1);
|
||||
let hash_b = Hash::repeat_byte(0x2);
|
||||
let hash_a = CandidateHash(Hash::repeat_byte(0x1));
|
||||
let hash_b = CandidateHash(Hash::repeat_byte(0x2));
|
||||
|
||||
let validators: Vec<_> = [
|
||||
Sr25519Keyring::Alice,
|
||||
@@ -263,7 +295,7 @@ mod tests {
|
||||
let cap = NonZeroUsize::new(3).unwrap();
|
||||
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<_> = [
|
||||
Sr25519Keyring::Alice,
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
//! Error handling related code and Error/Result definitions.
|
||||
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use polkadot_node_network_protocol::request_response::incoming;
|
||||
use polkadot_node_primitives::UncheckedSignedFullStatement;
|
||||
use polkadot_node_subsystem::errors::SubsystemError;
|
||||
use polkadot_node_subsystem_util::runtime;
|
||||
use polkadot_node_subsystem::{errors::SubsystemError, RuntimeApiError};
|
||||
use polkadot_node_subsystem_util::{backing_implicit_view, runtime};
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
@@ -44,10 +46,78 @@ pub enum Error {
|
||||
#[error("Error while accessing runtime information")]
|
||||
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")]
|
||||
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.
|
||||
///
|
||||
/// 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 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,
|
||||
};
|
||||
use polkadot_primitives::CollatorPair;
|
||||
@@ -76,12 +76,19 @@ pub enum ProtocolSide {
|
||||
metrics: validator_side::Metrics,
|
||||
},
|
||||
/// Collators operate on a parachain.
|
||||
Collator(
|
||||
PeerId,
|
||||
CollatorPair,
|
||||
IncomingRequestReceiver<request_v1::CollationFetchingRequest>,
|
||||
collator_side::Metrics,
|
||||
),
|
||||
Collator {
|
||||
/// Local peer id.
|
||||
peer_id: PeerId,
|
||||
/// Parachain collator pair.
|
||||
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.
|
||||
None,
|
||||
}
|
||||
@@ -110,10 +117,22 @@ impl<Context> CollatorProtocolSubsystem {
|
||||
validator_side::run(ctx, keystore, eviction_policy, metrics)
|
||||
.map_err(|e| SubsystemError::with_origin("collator-protocol", e))
|
||||
.boxed(),
|
||||
ProtocolSide::Collator(local_peer_id, collator_pair, req_receiver, metrics) =>
|
||||
collator_side::run(ctx, local_peer_id, collator_pair, req_receiver, metrics)
|
||||
.map_err(|e| SubsystemError::with_origin("collator-protocol", e))
|
||||
.boxed(),
|
||||
ProtocolSide::Collator {
|
||||
peer_id,
|
||||
collator_pair,
|
||||
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),
|
||||
};
|
||||
|
||||
|
||||
@@ -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
+348
-105
@@ -19,8 +19,8 @@ use assert_matches::assert_matches;
|
||||
use futures::{executor, future, Future};
|
||||
use sp_core::{crypto::Pair, Encode};
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_keystore::{testing::MemoryKeystore, Keystore};
|
||||
use std::{iter, sync::Arc, task::Poll, time::Duration};
|
||||
use sp_keystore::Keystore;
|
||||
use std::{iter, sync::Arc, time::Duration};
|
||||
|
||||
use polkadot_node_network_protocol::{
|
||||
our_view,
|
||||
@@ -28,24 +28,39 @@ use polkadot_node_network_protocol::{
|
||||
request_response::{Requests, ResponseSender},
|
||||
ObservedRole,
|
||||
};
|
||||
use polkadot_node_primitives::BlockData;
|
||||
use polkadot_node_subsystem::messages::{
|
||||
AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
use polkadot_node_primitives::{BlockData, PoV};
|
||||
use polkadot_node_subsystem::{
|
||||
errors::RuntimeApiError,
|
||||
messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest},
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers as test_helpers;
|
||||
use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt};
|
||||
use polkadot_primitives::{
|
||||
CollatorPair, CoreState, GroupIndex, GroupRotationInfo, OccupiedCore, ScheduledCore,
|
||||
ValidatorId, ValidatorIndex,
|
||||
CandidateReceipt, CollatorPair, CoreState, GroupIndex, GroupRotationInfo, HeadData,
|
||||
OccupiedCore, PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex,
|
||||
};
|
||||
use polkadot_primitives_test_helpers::{
|
||||
dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash,
|
||||
};
|
||||
|
||||
mod prospective_parachains;
|
||||
|
||||
const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500);
|
||||
const DECLARE_TIMEOUT: Duration = Duration::from_millis(25);
|
||||
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)]
|
||||
struct TestState {
|
||||
chain_ids: Vec<ParaId>,
|
||||
@@ -120,6 +135,7 @@ type VirtualOverseer = test_helpers::TestSubsystemContextHandle<CollatorProtocol
|
||||
|
||||
struct TestHarness {
|
||||
virtual_overseer: VirtualOverseer,
|
||||
keystore: KeystorePtr,
|
||||
}
|
||||
|
||||
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 keystore = MemoryKeystore::new();
|
||||
keystore
|
||||
.sr25519_generate_new(
|
||||
polkadot_primitives::PARACHAIN_KEY_TYPE_ID,
|
||||
Some(&Sr25519Keyring::Alice.to_seed()),
|
||||
)
|
||||
.unwrap();
|
||||
let keystore = Arc::new(sc_keystore::LocalKeystore::in_memory());
|
||||
Keystore::sr25519_generate_new(
|
||||
&*keystore,
|
||||
polkadot_primitives::PARACHAIN_KEY_TYPE_ID,
|
||||
Some(&Sr25519Keyring::Alice.to_seed()),
|
||||
)
|
||||
.expect("Insert key into keystore");
|
||||
|
||||
let subsystem = run_inner(
|
||||
context,
|
||||
Arc::new(keystore),
|
||||
keystore.clone(),
|
||||
crate::CollatorEvictionPolicy {
|
||||
inactive_collator: ACTIVITY_TIMEOUT,
|
||||
undeclared: DECLARE_TIMEOUT,
|
||||
@@ -156,7 +172,7 @@ fn test_harness<T: Future<Output = VirtualOverseer>>(
|
||||
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!(subsystem);
|
||||
@@ -253,16 +269,53 @@ async fn assert_candidate_backing_second(
|
||||
expected_relay_parent: Hash,
|
||||
expected_para_id: ParaId,
|
||||
expected_pov: &PoV,
|
||||
mode: ProspectiveParachainsMode,
|
||||
) -> 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!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
AllMessages::CandidateBacking(CandidateBackingMessage::Second(relay_parent, candidate_receipt, incoming_pov)
|
||||
) => {
|
||||
assert_eq!(expected_relay_parent, relay_parent);
|
||||
assert_eq!(expected_para_id, candidate_receipt.descriptor.para_id);
|
||||
assert_eq!(*expected_pov, incoming_pov);
|
||||
candidate_receipt
|
||||
})
|
||||
AllMessages::CandidateBacking(CandidateBackingMessage::Second(
|
||||
relay_parent,
|
||||
candidate_receipt,
|
||||
received_pvd,
|
||||
incoming_pov,
|
||||
)) => {
|
||||
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.
|
||||
@@ -284,6 +337,7 @@ async fn assert_fetch_collation_request(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
relay_parent: Hash,
|
||||
para_id: ParaId,
|
||||
candidate_hash: Option<CandidateHash>,
|
||||
) -> ResponseSender {
|
||||
assert_matches!(
|
||||
overseer_recv(virtual_overseer).await,
|
||||
@@ -291,14 +345,26 @@ async fn assert_fetch_collation_request(
|
||||
) => {
|
||||
let req = reqs.into_iter().next()
|
||||
.expect("There should be exactly one request");
|
||||
match req {
|
||||
Requests::CollationFetchingV1(req) => {
|
||||
let payload = req.payload;
|
||||
assert_eq!(payload.relay_parent, relay_parent);
|
||||
assert_eq!(payload.para_id, para_id);
|
||||
req.pending_response
|
||||
}
|
||||
_ => panic!("Unexpected request"),
|
||||
match candidate_hash {
|
||||
None => assert_matches!(
|
||||
req,
|
||||
Requests::CollationFetchingV1(req) => {
|
||||
let payload = req.payload;
|
||||
assert_eq!(payload.relay_parent, relay_parent);
|
||||
assert_eq!(payload.para_id, para_id);
|
||||
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,
|
||||
collator: CollatorPair,
|
||||
para_id: ParaId,
|
||||
version: CollationVersion,
|
||||
) {
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected(
|
||||
peer,
|
||||
ObservedRole::Full,
|
||||
CollationVersion::V1.into(),
|
||||
version.into(),
|
||||
None,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(
|
||||
peer,
|
||||
Versioned::V1(protocol_v1::CollatorProtocolMessage::Declare(
|
||||
let wire_message = match version {
|
||||
CollationVersion::V1 => Versioned::V1(protocol_v1::CollatorProtocolMessage::Declare(
|
||||
collator.public(),
|
||||
para_id,
|
||||
collator.sign(&protocol_v1::declare_signature_payload(&peer)),
|
||||
)),
|
||||
CollationVersion::VStaging =>
|
||||
Versioned::VStaging(protocol_vstaging::CollatorProtocolMessage::Declare(
|
||||
collator.public(),
|
||||
para_id,
|
||||
collator.sign(&protocol_v1::declare_signature_payload(&peer)),
|
||||
)),
|
||||
};
|
||||
|
||||
overseer_send(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(
|
||||
peer,
|
||||
wire_message,
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
@@ -340,24 +417,48 @@ async fn advertise_collation(
|
||||
virtual_overseer: &mut VirtualOverseer,
|
||||
peer: PeerId,
|
||||
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(
|
||||
virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(
|
||||
peer,
|
||||
Versioned::V1(protocol_v1::CollatorProtocolMessage::AdvertiseCollation(relay_parent)),
|
||||
wire_message,
|
||||
)),
|
||||
)
|
||||
.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.
|
||||
#[test]
|
||||
fn act_on_advertisement() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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;
|
||||
gum::trace!("activating");
|
||||
@@ -370,6 +471,7 @@ fn act_on_advertisement() {
|
||||
)
|
||||
.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();
|
||||
@@ -379,15 +481,74 @@ fn act_on_advertisement() {
|
||||
peer_b,
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.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(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
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;
|
||||
|
||||
@@ -401,7 +562,7 @@ fn collator_reporting_works() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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(
|
||||
&mut virtual_overseer,
|
||||
@@ -411,6 +572,8 @@ fn collator_reporting_works() {
|
||||
)
|
||||
.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();
|
||||
@@ -421,6 +584,7 @@ fn collator_reporting_works() {
|
||||
peer_b,
|
||||
test_state.collators[0].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -429,6 +593,7 @@ fn collator_reporting_works() {
|
||||
peer_c,
|
||||
test_state.collators[1].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -458,7 +623,7 @@ fn collator_authentication_verification_works() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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();
|
||||
|
||||
@@ -509,20 +674,24 @@ fn fetch_one_collation_at_a_time() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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 our_view = our_view![test_state.relay_parent, second];
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent, second],
|
||||
our_view.clone(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
// Iter over view since the order may change due to sorted invariant.
|
||||
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_c = PeerId::random();
|
||||
@@ -532,6 +701,7 @@ fn fetch_one_collation_at_a_time() {
|
||||
peer_b,
|
||||
test_state.collators[0].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -540,16 +710,18 @@ fn fetch_one_collation_at_a_time() {
|
||||
peer_c,
|
||||
test_state.collators[1].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_c, 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, None).await;
|
||||
|
||||
let response_channel = assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -563,10 +735,13 @@ fn fetch_one_collation_at_a_time() {
|
||||
dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default()));
|
||||
candidate_a.descriptor.para_id = test_state.chain_ids[0];
|
||||
candidate_a.descriptor.relay_parent = test_state.relay_parent;
|
||||
candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
|
||||
response_channel
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.send(Ok(request_v1::CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
)
|
||||
.encode()))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
assert_candidate_backing_second(
|
||||
@@ -574,6 +749,7 @@ fn fetch_one_collation_at_a_time() {
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
&pov,
|
||||
ProspectiveParachainsMode::Disabled,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -581,7 +757,7 @@ fn fetch_one_collation_at_a_time() {
|
||||
test_helpers::Yield::new().await;
|
||||
|
||||
// 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
|
||||
})
|
||||
@@ -594,20 +770,24 @@ fn fetches_next_collation() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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 our_view = our_view![test_state.relay_parent, second];
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent, second],
|
||||
our_view.clone(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
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_c = PeerId::random();
|
||||
@@ -618,6 +798,7 @@ fn fetches_next_collation() {
|
||||
peer_b,
|
||||
test_state.collators[2].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -626,6 +807,7 @@ fn fetches_next_collation() {
|
||||
peer_c,
|
||||
test_state.collators[3].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -634,45 +816,64 @@ fn fetches_next_collation() {
|
||||
peer_d,
|
||||
test_state.collators[4].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b, second).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_c, second).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_d, second).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_b, second, None).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_c, second, None).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_d, second, None).await;
|
||||
|
||||
// Dropping the response channel should lead to fetching the second collation.
|
||||
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0])
|
||||
.await;
|
||||
assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let response_channel_non_exclusive =
|
||||
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0])
|
||||
.await;
|
||||
let response_channel_non_exclusive = assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Third collator should receive response after that timeout:
|
||||
Delay::new(MAX_UNSHARED_DOWNLOAD_TIME + Duration::from_millis(50)).await;
|
||||
|
||||
let response_channel =
|
||||
assert_fetch_collation_request(&mut virtual_overseer, second, test_state.chain_ids[0])
|
||||
.await;
|
||||
let response_channel = assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let pov = PoV { block_data: BlockData(vec![1]) };
|
||||
let mut candidate_a =
|
||||
dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default()));
|
||||
candidate_a.descriptor.para_id = test_state.chain_ids[0];
|
||||
candidate_a.descriptor.relay_parent = second;
|
||||
candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
|
||||
|
||||
// First request finishes now:
|
||||
response_channel_non_exclusive
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.send(Ok(request_v1::CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
)
|
||||
.encode()))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
response_channel
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.send(Ok(request_v1::CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
)
|
||||
.encode()))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
assert_candidate_backing_second(
|
||||
@@ -680,6 +881,7 @@ fn fetches_next_collation() {
|
||||
second,
|
||||
test_state.chain_ids[0],
|
||||
&pov,
|
||||
ProspectiveParachainsMode::Disabled,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -692,7 +894,7 @@ fn reject_connection_to_next_group() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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(
|
||||
&mut virtual_overseer,
|
||||
@@ -702,6 +904,7 @@ fn reject_connection_to_next_group() {
|
||||
)
|
||||
.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();
|
||||
@@ -711,6 +914,7 @@ fn reject_connection_to_next_group() {
|
||||
peer_b,
|
||||
test_state.collators[0].clone(),
|
||||
test_state.chain_ids[1], // next, not current `para_id`
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -737,20 +941,24 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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 our_view = our_view![test_state.relay_parent, second];
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![test_state.relay_parent, second],
|
||||
our_view.clone(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
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_c = PeerId::random();
|
||||
@@ -760,6 +968,7 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
peer_b,
|
||||
test_state.collators[0].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -768,16 +977,18 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
peer_c,
|
||||
test_state.collators[1].clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent).await;
|
||||
advertise_collation(&mut virtual_overseer, peer_c, 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, None).await;
|
||||
|
||||
let response_channel = assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -786,10 +997,13 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default()));
|
||||
candidate_a.descriptor.para_id = test_state.chain_ids[0];
|
||||
candidate_a.descriptor.relay_parent = test_state.relay_parent;
|
||||
candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash();
|
||||
response_channel
|
||||
.send(Ok(
|
||||
CollationFetchingResponse::Collation(candidate_a.clone(), pov.clone()).encode()
|
||||
))
|
||||
.send(Ok(request_v1::CollationFetchingResponse::Collation(
|
||||
candidate_a.clone(),
|
||||
pov.clone(),
|
||||
)
|
||||
.encode()))
|
||||
.expect("Sending response should succeed");
|
||||
|
||||
let receipt = assert_candidate_backing_second(
|
||||
@@ -797,6 +1011,7 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
&pov,
|
||||
ProspectiveParachainsMode::Disabled,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -822,6 +1037,7 @@ fn fetch_next_collation_on_invalid_collation() {
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -834,7 +1050,7 @@ fn inactive_disconnected() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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;
|
||||
|
||||
@@ -848,6 +1064,7 @@ fn inactive_disconnected() {
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_async_backing_params_request(&mut virtual_overseer, hash_a).await;
|
||||
respond_to_core_info_queries(&mut virtual_overseer, &test_state).await;
|
||||
|
||||
let peer_b = PeerId::random();
|
||||
@@ -857,14 +1074,16 @@ fn inactive_disconnected() {
|
||||
peer_b,
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.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(
|
||||
&mut virtual_overseer,
|
||||
test_state.relay_parent,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -880,7 +1099,7 @@ fn activity_extends_life() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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;
|
||||
|
||||
@@ -888,18 +1107,20 @@ fn activity_extends_life() {
|
||||
let hash_b = Hash::repeat_byte(1);
|
||||
let hash_c = Hash::repeat_byte(2);
|
||||
|
||||
let our_view = our_view![hash_a, hash_b, hash_c];
|
||||
|
||||
overseer_send(
|
||||
&mut virtual_overseer,
|
||||
CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange(
|
||||
our_view![hash_a, hash_b, hash_c],
|
||||
our_view.clone(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
|
||||
// 3 heads, 3 times.
|
||||
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;
|
||||
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();
|
||||
|
||||
@@ -908,29 +1129,45 @@ fn activity_extends_life() {
|
||||
peer_b,
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.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])
|
||||
.await;
|
||||
assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
hash_a,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.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])
|
||||
.await;
|
||||
assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
hash_b,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.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])
|
||||
.await;
|
||||
assert_fetch_collation_request(
|
||||
&mut virtual_overseer,
|
||||
hash_c,
|
||||
test_state.chain_ids[0],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
Delay::new(ACTIVITY_TIMEOUT * 3 / 2).await;
|
||||
|
||||
@@ -945,7 +1182,7 @@ fn disconnect_if_no_declare() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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(
|
||||
&mut virtual_overseer,
|
||||
@@ -955,6 +1192,7 @@ fn disconnect_if_no_declare() {
|
||||
)
|
||||
.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();
|
||||
@@ -981,7 +1219,7 @@ fn disconnect_if_wrong_declare() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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;
|
||||
|
||||
@@ -993,6 +1231,7 @@ fn disconnect_if_wrong_declare() {
|
||||
)
|
||||
.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();
|
||||
@@ -1042,7 +1281,7 @@ fn delay_reputation_change() {
|
||||
let test_state = TestState::default();
|
||||
|
||||
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;
|
||||
|
||||
@@ -1054,6 +1293,7 @@ fn delay_reputation_change() {
|
||||
)
|
||||
.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();
|
||||
@@ -1127,7 +1367,7 @@ fn view_change_clears_old_collators() {
|
||||
let mut test_state = TestState::default();
|
||||
|
||||
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;
|
||||
|
||||
@@ -1139,6 +1379,7 @@ fn view_change_clears_old_collators() {
|
||||
)
|
||||
.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();
|
||||
@@ -1148,6 +1389,7 @@ fn view_change_clears_old_collators() {
|
||||
peer_b,
|
||||
pair.clone(),
|
||||
test_state.chain_ids[0],
|
||||
CollationVersion::V1,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1162,6 +1404,7 @@ fn view_change_clears_old_collators() {
|
||||
.await;
|
||||
|
||||
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;
|
||||
|
||||
assert_collator_disconnect(&mut virtual_overseer, peer_b).await;
|
||||
+988
@@ -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::PeerViewChange(_, _) => {},
|
||||
NetworkBridgeEvent::NewGossipTopology { .. } => {},
|
||||
NetworkBridgeEvent::PeerMessage(_, Versioned::V1(v)) => {
|
||||
match v {};
|
||||
NetworkBridgeEvent::PeerMessage(_, message) => {
|
||||
// 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"
|
||||
derive_more = "0.99"
|
||||
gum = { package = "tracing-gum", path = "../../gum" }
|
||||
bitvec = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.3.1"
|
||||
|
||||
[features]
|
||||
network-protocol-staging = []
|
||||
|
||||
@@ -253,22 +253,26 @@ impl View {
|
||||
|
||||
/// A protocol-versioned type.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Versioned<V1> {
|
||||
pub enum Versioned<V1, VStaging> {
|
||||
/// V1 type.
|
||||
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.
|
||||
pub fn clone_inner(&self) -> Versioned<V1> {
|
||||
pub fn clone_inner(&self) -> Versioned<V1, VStaging> {
|
||||
match *self {
|
||||
Versioned::V1(inner) => Versioned::V1(inner.clone()),
|
||||
Versioned::VStaging(inner) => Versioned::VStaging(inner.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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.
|
||||
pub type VersionedCollationProtocol = Versioned<v1::CollationProtocol>;
|
||||
pub type VersionedCollationProtocol = Versioned<v1::CollationProtocol, vstaging::CollationProtocol>;
|
||||
|
||||
impl From<v1::CollationProtocol> for VersionedCollationProtocol {
|
||||
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 {
|
||||
($from:ty, $out:ty, $variant:ident) => {
|
||||
impl From<$from> for $out {
|
||||
fn from(versioned_from: $from) -> $out {
|
||||
match versioned_from {
|
||||
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.
|
||||
/// `$m_ty::$variant(inner) -> Ok(inner)`
|
||||
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 {
|
||||
type Error = crate::WrongVariant;
|
||||
|
||||
@@ -308,6 +330,7 @@ macro_rules! impl_versioned_try_from {
|
||||
#[allow(unreachable_patterns)] // when there is only one variant
|
||||
match x {
|
||||
Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out)),
|
||||
Versioned::VStaging($vstaging_pat) => Ok(Versioned::VStaging($vstaging_out)),
|
||||
_ => Err(crate::WrongVariant),
|
||||
}
|
||||
}
|
||||
@@ -320,6 +343,8 @@ macro_rules! impl_versioned_try_from {
|
||||
#[allow(unreachable_patterns)] // when there is only one variant
|
||||
match x {
|
||||
Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out.clone())),
|
||||
Versioned::VStaging($vstaging_pat) =>
|
||||
Ok(Versioned::VStaging($vstaging_out.clone())),
|
||||
_ => Err(crate::WrongVariant),
|
||||
}
|
||||
}
|
||||
@@ -328,7 +353,8 @@ macro_rules! impl_versioned_try_from {
|
||||
}
|
||||
|
||||
/// 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!(
|
||||
BitfieldDistributionMessage,
|
||||
VersionedValidationProtocol,
|
||||
@@ -337,11 +363,13 @@ impl_versioned_full_protocol_from!(
|
||||
impl_versioned_try_from!(
|
||||
VersionedValidationProtocol,
|
||||
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.
|
||||
pub type StatementDistributionMessage = Versioned<v1::StatementDistributionMessage>;
|
||||
pub type StatementDistributionMessage =
|
||||
Versioned<v1::StatementDistributionMessage, vstaging::StatementDistributionMessage>;
|
||||
impl_versioned_full_protocol_from!(
|
||||
StatementDistributionMessage,
|
||||
VersionedValidationProtocol,
|
||||
@@ -350,11 +378,13 @@ impl_versioned_full_protocol_from!(
|
||||
impl_versioned_try_from!(
|
||||
VersionedValidationProtocol,
|
||||
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.
|
||||
pub type ApprovalDistributionMessage = Versioned<v1::ApprovalDistributionMessage>;
|
||||
pub type ApprovalDistributionMessage =
|
||||
Versioned<v1::ApprovalDistributionMessage, vstaging::ApprovalDistributionMessage>;
|
||||
impl_versioned_full_protocol_from!(
|
||||
ApprovalDistributionMessage,
|
||||
VersionedValidationProtocol,
|
||||
@@ -363,11 +393,14 @@ impl_versioned_full_protocol_from!(
|
||||
impl_versioned_try_from!(
|
||||
VersionedValidationProtocol,
|
||||
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).
|
||||
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.
|
||||
impl TryFrom<VersionedValidationProtocol> for GossipSupportNetworkMessage {
|
||||
type Error = WrongVariant;
|
||||
@@ -384,7 +417,8 @@ impl<'a> TryFrom<&'a VersionedValidationProtocol> for GossipSupportNetworkMessag
|
||||
}
|
||||
|
||||
/// 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!(
|
||||
CollatorProtocolMessage,
|
||||
VersionedCollationProtocol,
|
||||
@@ -393,7 +427,8 @@ impl_versioned_full_protocol_from!(
|
||||
impl_versioned_try_from!(
|
||||
VersionedCollationProtocol,
|
||||
CollatorProtocolMessage,
|
||||
v1::CollationProtocol::CollatorProtocol(x) => x
|
||||
v1::CollationProtocol::CollatorProtocol(x) => x,
|
||||
vstaging::CollationProtocol::CollatorProtocol(x) => x
|
||||
);
|
||||
|
||||
/// v1 notification protocol types.
|
||||
@@ -553,3 +588,256 @@ pub mod v1 {
|
||||
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
|
||||
/// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`].
|
||||
pub fn get_main_version(self) -> ProtocolVersion {
|
||||
#[cfg(not(feature = "network-protocol-staging"))]
|
||||
match self {
|
||||
PeerSet::Validation => ValidationVersion::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.
|
||||
@@ -145,12 +152,16 @@ impl PeerSet {
|
||||
PeerSet::Validation =>
|
||||
if version == ValidationVersion::V1.into() {
|
||||
Some("validation/1")
|
||||
} else if version == ValidationVersion::VStaging.into() {
|
||||
Some("validation/2")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
PeerSet::Collation =>
|
||||
if version == CollationVersion::V1.into() {
|
||||
Some("collation/1")
|
||||
} else if version == CollationVersion::VStaging.into() {
|
||||
Some("collation/2")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -212,6 +223,8 @@ impl From<ProtocolVersion> for u32 {
|
||||
pub enum ValidationVersion {
|
||||
/// The first version.
|
||||
V1 = 1,
|
||||
/// The staging version.
|
||||
VStaging = 2,
|
||||
}
|
||||
|
||||
/// Supported collation protocol versions. Only versions defined here must be used in the codebase.
|
||||
@@ -219,6 +232,40 @@ pub enum ValidationVersion {
|
||||
pub enum CollationVersion {
|
||||
/// The first version.
|
||||
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 {
|
||||
|
||||
@@ -51,9 +51,12 @@ pub use outgoing::{OutgoingRequest, OutgoingResult, Recipient, Requests, Respons
|
||||
///// Multiplexer for incoming requests.
|
||||
// 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;
|
||||
|
||||
/// 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
|
||||
/// within protocols.
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumIter)]
|
||||
@@ -62,6 +65,8 @@ pub enum Protocol {
|
||||
ChunkFetchingV1,
|
||||
/// Protocol for fetching collations from collators.
|
||||
CollationFetchingV1,
|
||||
/// Protocol for fetching collations from collators when async backing is enabled.
|
||||
CollationFetchingVStaging,
|
||||
/// Protocol for fetching seconded PoVs from validators of the same group.
|
||||
PoVFetchingV1,
|
||||
/// Protocol for fetching available data.
|
||||
@@ -70,6 +75,10 @@ pub enum Protocol {
|
||||
StatementFetchingV1,
|
||||
/// Sending of dispute statements with application level confirmations.
|
||||
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
|
||||
@@ -101,12 +110,30 @@ const POV_REQUEST_TIMEOUT_CONNECTED: Duration = Duration::from_millis(1200);
|
||||
/// fit statement distribution within a block of 6 seconds.)
|
||||
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
|
||||
/// 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
|
||||
/// to have 3 slow nodes connected, to delay transfer for others by `STATEMENTS_TIMEOUT`.
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
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
|
||||
/// timeout as we want to get statements through to each node in any case.
|
||||
pub const DISPUTE_REQUEST_TIMEOUT: Duration = Duration::from_secs(12);
|
||||
@@ -166,15 +199,16 @@ impl Protocol {
|
||||
request_timeout: CHUNK_REQUEST_TIMEOUT,
|
||||
inbound_queue: tx,
|
||||
},
|
||||
Protocol::CollationFetchingV1 => RequestResponseConfig {
|
||||
name,
|
||||
fallback_names,
|
||||
max_request_size: 1_000,
|
||||
max_response_size: POV_RESPONSE_SIZE,
|
||||
// Taken from initial implementation in collator protocol:
|
||||
request_timeout: POV_REQUEST_TIMEOUT_CONNECTED,
|
||||
inbound_queue: tx,
|
||||
},
|
||||
Protocol::CollationFetchingV1 | Protocol::CollationFetchingVStaging =>
|
||||
RequestResponseConfig {
|
||||
name,
|
||||
fallback_names,
|
||||
max_request_size: 1_000,
|
||||
max_response_size: POV_RESPONSE_SIZE,
|
||||
// Taken from initial implementation in collator protocol:
|
||||
request_timeout: POV_REQUEST_TIMEOUT_CONNECTED,
|
||||
inbound_queue: tx,
|
||||
},
|
||||
Protocol::PoVFetchingV1 => RequestResponseConfig {
|
||||
name,
|
||||
fallback_names,
|
||||
@@ -220,6 +254,14 @@ impl Protocol {
|
||||
request_timeout: DISPUTE_REQUEST_TIMEOUT,
|
||||
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.
|
||||
Protocol::ChunkFetchingV1 => 100,
|
||||
// 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.
|
||||
Protocol::PoVFetchingV1 => 10,
|
||||
// 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
|
||||
// failure, so having a good value here is mostly about performance tuning.
|
||||
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.
|
||||
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.
|
||||
const fn get_legacy_name(self) -> &'static str {
|
||||
/// Legacy protocol name associated with each peer set, if any.
|
||||
const fn get_legacy_name(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Protocol::ChunkFetchingV1 => "/polkadot/req_chunk/1",
|
||||
Protocol::CollationFetchingV1 => "/polkadot/req_collation/1",
|
||||
Protocol::PoVFetchingV1 => "/polkadot/req_pov/1",
|
||||
Protocol::AvailableDataFetchingV1 => "/polkadot/req_available_data/1",
|
||||
Protocol::StatementFetchingV1 => "/polkadot/req_statement/1",
|
||||
Protocol::DisputeSendingV1 => "/polkadot/send_dispute/1",
|
||||
Protocol::ChunkFetchingV1 => Some("/polkadot/req_chunk/1"),
|
||||
Protocol::CollationFetchingV1 => Some("/polkadot/req_collation/1"),
|
||||
Protocol::PoVFetchingV1 => Some("/polkadot/req_pov/1"),
|
||||
Protocol::AvailableDataFetchingV1 => Some("/polkadot/req_available_data/1"),
|
||||
Protocol::StatementFetchingV1 => Some("/polkadot/req_statement/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::StatementFetchingV1 => "/req_statement/1",
|
||||
Protocol::DisputeSendingV1 => "/send_dispute/1",
|
||||
|
||||
Protocol::CollationFetchingVStaging => "/req_collation/2",
|
||||
Protocol::AttestedCandidateVStaging => "/req_attested_candidate/2",
|
||||
};
|
||||
|
||||
format!("{}{}", prefix, short_name).into()
|
||||
|
||||
@@ -23,7 +23,7 @@ use sc_network::PeerId;
|
||||
|
||||
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`.
|
||||
#[derive(Debug)]
|
||||
@@ -40,6 +40,12 @@ pub enum Requests {
|
||||
StatementFetchingV1(OutgoingRequest<v1::StatementFetchingRequest>),
|
||||
/// Requests for notifying about an ongoing dispute.
|
||||
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 {
|
||||
@@ -48,10 +54,12 @@ impl Requests {
|
||||
match self {
|
||||
Self::ChunkFetchingV1(_) => Protocol::ChunkFetchingV1,
|
||||
Self::CollationFetchingV1(_) => Protocol::CollationFetchingV1,
|
||||
Self::CollationFetchingVStaging(_) => Protocol::CollationFetchingVStaging,
|
||||
Self::PoVFetchingV1(_) => Protocol::PoVFetchingV1,
|
||||
Self::AvailableDataFetchingV1(_) => Protocol::AvailableDataFetchingV1,
|
||||
Self::StatementFetchingV1(_) => Protocol::StatementFetchingV1,
|
||||
Self::DisputeSendingV1(_) => Protocol::DisputeSendingV1,
|
||||
Self::AttestedCandidateVStaging(_) => Protocol::AttestedCandidateVStaging,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +74,12 @@ impl Requests {
|
||||
match self {
|
||||
Self::ChunkFetchingV1(r) => r.encode_request(),
|
||||
Self::CollationFetchingV1(r) => r.encode_request(),
|
||||
Self::CollationFetchingVStaging(r) => r.encode_request(),
|
||||
Self::PoVFetchingV1(r) => r.encode_request(),
|
||||
Self::AvailableDataFetchingV1(r) => r.encode_request(),
|
||||
Self::StatementFetchingV1(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"] }
|
||||
thiserror = "1.0.31"
|
||||
fatality = "0.0.6"
|
||||
bitvec = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
|
||||
async-channel = "1.8.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-keyring = { 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" }
|
||||
futures-timer = "3.0.2"
|
||||
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.
|
||||
|
||||
use polkadot_node_network_protocol::PeerId;
|
||||
use polkadot_node_subsystem::SubsystemError;
|
||||
use polkadot_node_subsystem_util::runtime;
|
||||
use polkadot_primitives::{CandidateHash, Hash};
|
||||
use polkadot_node_subsystem::{RuntimeApiError, SubsystemError};
|
||||
use polkadot_node_subsystem_util::{
|
||||
backing_implicit_view::FetchError as ImplicitViewFetchError, runtime,
|
||||
};
|
||||
use polkadot_primitives::{CandidateHash, Hash, Id as ParaId};
|
||||
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use crate::LOG_TARGET;
|
||||
|
||||
@@ -56,6 +60,27 @@ pub enum Error {
|
||||
#[error("Error while accessing runtime information")]
|
||||
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")]
|
||||
NoSuchHead(Hash),
|
||||
|
||||
@@ -76,6 +101,10 @@ pub enum Error {
|
||||
// Responder no longer waits for our data. (Should not happen right now.)
|
||||
#[error("Oneshot `GetData` channel closed")]
|
||||
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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -32,7 +32,10 @@ use polkadot_node_subsystem::{Span, Stage};
|
||||
use polkadot_node_subsystem_util::TimeoutExt;
|
||||
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
|
||||
// retry, even though we have not yet discovered any new peers. Or in other words how long to
|
||||
+2
-2
@@ -48,8 +48,8 @@ pub enum ResponderMessage {
|
||||
|
||||
/// 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
|
||||
/// `CommittedCandidateReceipt` from peers, whether this can be used to re-assemble one ore
|
||||
/// 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 or
|
||||
/// many `SignedFullStatement`s needs to be verified by the caller.
|
||||
pub async fn respond(
|
||||
mut receiver: IncomingRequestReceiver<StatementFetchingRequest>,
|
||||
+213
-28
@@ -14,7 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 futures::executor;
|
||||
use futures_timer::Delay;
|
||||
@@ -26,19 +30,21 @@ use polkadot_node_network_protocol::{
|
||||
v1::{StatementFetchingRequest, StatementFetchingResponse},
|
||||
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::{
|
||||
jaeger,
|
||||
messages::{
|
||||
network_bridge_event, AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest,
|
||||
},
|
||||
ActivatedLeaf, LeafStatus,
|
||||
ActivatedLeaf, LeafStatus, RuntimeApiError,
|
||||
};
|
||||
use polkadot_node_subsystem_test_helpers::mock::make_ferdie_keystore;
|
||||
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::{
|
||||
dummy_committed_candidate_receipt, dummy_hash, AlwaysZeroRng,
|
||||
@@ -54,6 +60,30 @@ use util::reputation::add_reputation;
|
||||
// Some deterministic genesis hash for protocol names
|
||||
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]
|
||||
fn active_head_accepts_only_2_seconded_per_validator() {
|
||||
let validators = vec![
|
||||
@@ -496,6 +526,7 @@ fn peer_view_update_sends_messages() {
|
||||
|
||||
let mut peer_data = PeerData {
|
||||
view: old_view,
|
||||
protocol_version: ValidationVersion::V1,
|
||||
view_knowledge: {
|
||||
let mut k = HashMap::new();
|
||||
|
||||
@@ -554,8 +585,9 @@ fn peer_view_update_sends_messages() {
|
||||
for statement in active_head.statements_about(candidate_hash) {
|
||||
let message = handle.recv().await;
|
||||
let expected_to = vec![peer];
|
||||
let expected_payload =
|
||||
statement_message(hash_c, statement.statement.clone(), &Metrics::default());
|
||||
let expected_payload = VersionedValidationProtocol::from(Versioned::V1(
|
||||
v1_statement_message(hash_c, statement.statement.clone(), &Metrics::default()),
|
||||
));
|
||||
|
||||
assert_matches!(
|
||||
message,
|
||||
@@ -596,6 +628,7 @@ fn circulated_statement_goes_to_all_peers_with_view() {
|
||||
|
||||
let peer_data_from_view = |view: View| PeerData {
|
||||
view: view.clone(),
|
||||
protocol_version: ValidationVersion::V1,
|
||||
view_knowledge: view.iter().map(|v| (*v, Default::default())).collect(),
|
||||
maybe_authority: None,
|
||||
};
|
||||
@@ -697,7 +730,7 @@ fn circulated_statement_goes_to_all_peers_with_view() {
|
||||
|
||||
assert_eq!(
|
||||
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]
|
||||
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 pvd = dummy_pvd();
|
||||
|
||||
let candidate = {
|
||||
let mut c = dummy_committed_candidate_receipt(dummy_hash());
|
||||
c.descriptor.relay_parent = hash_a;
|
||||
c.descriptor.para_id = 1.into();
|
||||
c.descriptor.para_id = PARA_ID;
|
||||
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 (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 s = StatementDistributionSubsystem {
|
||||
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(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
@@ -758,6 +795,17 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
|
||||
)))
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(
|
||||
@@ -862,18 +910,32 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
|
||||
})
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))
|
||||
) if p == peer_a && r == BENEFIT_VALID_STATEMENT_FIRST.into() => {}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::CandidateBacking(
|
||||
CandidateBackingMessage::Statement(r, s)
|
||||
) if r == hash_a && s == statement => {}
|
||||
) if r == hash_a && s == statement_with_pvd => {}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
@@ -902,6 +964,9 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() {
|
||||
|
||||
#[test]
|
||||
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();
|
||||
let hash_a = Hash::repeat_byte(1);
|
||||
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 mut c = dummy_committed_candidate_receipt(dummy_hash());
|
||||
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
|
||||
};
|
||||
@@ -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 (statement_req_receiver, mut req_cfg) =
|
||||
IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
|
||||
let bg = async move {
|
||||
let s = StatementDistributionSubsystem {
|
||||
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(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
@@ -962,6 +1029,17 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
)))
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
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() => {}
|
||||
);
|
||||
|
||||
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!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
@@ -1303,7 +1395,7 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing(
|
||||
handle.recv().await,
|
||||
AllMessages::CandidateBacking(
|
||||
CandidateBackingMessage::Statement(r, s)
|
||||
) if r == hash_a && s == statement => {}
|
||||
) if r == hash_a && s == statement_with_pvd => {}
|
||||
);
|
||||
|
||||
// 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() {
|
||||
sp_tracing::try_init_simple();
|
||||
let hash_a = Hash::repeat_byte(1);
|
||||
let pvd = dummy_pvd();
|
||||
|
||||
let candidate = {
|
||||
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 (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 bg = async move {
|
||||
let s = StatementDistributionSubsystem {
|
||||
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(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| false),
|
||||
@@ -1458,6 +1553,17 @@ fn delay_reputation_changes() {
|
||||
)))
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(
|
||||
@@ -1768,9 +1874,18 @@ fn delay_reputation_changes() {
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::CandidateBacking(
|
||||
CandidateBackingMessage::Statement(r, s)
|
||||
) if r == hash_a && s == statement => {}
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
hash,
|
||||
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:
|
||||
@@ -1885,11 +2000,13 @@ fn share_prioritizes_backing_group() {
|
||||
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
|
||||
let (statement_req_receiver, mut req_cfg) =
|
||||
IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
let (candidate_req_receiver, _) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
|
||||
let bg = async move {
|
||||
let s = StatementDistributionSubsystem {
|
||||
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(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
@@ -1910,6 +2027,17 @@ fn share_prioritizes_backing_group() {
|
||||
)))
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(
|
||||
@@ -2069,9 +2197,17 @@ fn share_prioritizes_backing_group() {
|
||||
)
|
||||
.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,
|
||||
Statement::Seconded(candidate.clone()),
|
||||
Statement::Seconded(candidate.clone()).supply_pvd(pvd),
|
||||
&signing_context,
|
||||
ValidatorIndex(4),
|
||||
&ferdie_public.into(),
|
||||
@@ -2081,14 +2217,15 @@ fn share_prioritizes_backing_group() {
|
||||
.expect("should be signed")
|
||||
};
|
||||
|
||||
let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into());
|
||||
|
||||
handle
|
||||
.send(FromOrchestra::Communication {
|
||||
msg: StatementDistributionMessage::Share(hash_a, statement.clone()),
|
||||
})
|
||||
.await;
|
||||
|
||||
let statement = StatementWithPVD::drop_pvd_from_signed(statement);
|
||||
let metadata = derive_metadata_assuming_seconded(hash_a, statement.clone().into());
|
||||
|
||||
// Messages should go out:
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
@@ -2180,10 +2317,12 @@ fn peer_cant_flood_with_large_statements() {
|
||||
|
||||
let req_protocol_names = ReqProtocolNames::new(&GENESIS_HASH, None);
|
||||
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 s = StatementDistributionSubsystem {
|
||||
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(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
@@ -2204,6 +2343,17 @@ fn peer_cant_flood_with_large_statements() {
|
||||
)))
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(
|
||||
@@ -2341,6 +2491,7 @@ fn peer_cant_flood_with_large_statements() {
|
||||
#[test]
|
||||
fn handle_multiple_seconded_statements() {
|
||||
let relay_parent_hash = Hash::repeat_byte(1);
|
||||
let pvd = dummy_pvd();
|
||||
|
||||
let candidate = dummy_committed_candidate_receipt(relay_parent_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 (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 s = StatementDistributionSubsystem {
|
||||
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(),
|
||||
rng: AlwaysZeroRng,
|
||||
reputation: ReputationAggregator::new(|_| true),
|
||||
@@ -2409,6 +2562,17 @@ fn handle_multiple_seconded_statements() {
|
||||
)))
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(
|
||||
@@ -2575,6 +2739,18 @@ fn handle_multiple_seconded_statements() {
|
||||
})
|
||||
.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!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
@@ -2592,7 +2768,7 @@ fn handle_multiple_seconded_statements() {
|
||||
CandidateBackingMessage::Statement(r, s)
|
||||
) => {
|
||||
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;
|
||||
|
||||
let statement_with_pvd = extend_statement_with_pvd(statement.clone(), pvd.clone());
|
||||
|
||||
// Persisted validation data is cached.
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::NetworkBridgeTx(
|
||||
@@ -2692,7 +2872,7 @@ fn handle_multiple_seconded_statements() {
|
||||
CandidateBackingMessage::Statement(r, s)
|
||||
) => {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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>,
|
||||
active_leaves_update: prometheus::Histogram,
|
||||
share: prometheus::Histogram,
|
||||
network_bridge_update_v1: prometheus::HistogramVec,
|
||||
network_bridge_update: prometheus::HistogramVec,
|
||||
statements_unexpected: prometheus::CounterVec<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())
|
||||
}
|
||||
|
||||
/// Provide a timer for `network_bridge_update_v1` which observes on drop.
|
||||
pub fn time_network_bridge_update_v1(
|
||||
/// Provide a timer for `network_bridge_update` which observes on drop.
|
||||
pub fn time_network_bridge_update(
|
||||
&self,
|
||||
message_type: &'static str,
|
||||
) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
|
||||
self.0.as_ref().map(|metrics| {
|
||||
metrics
|
||||
.network_bridge_update_v1
|
||||
.with_label_values(&[message_type])
|
||||
.start_timer()
|
||||
metrics.network_bridge_update.with_label_values(&[message_type]).start_timer()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,11 +165,11 @@ impl metrics::Metrics for Metrics {
|
||||
)?,
|
||||
registry,
|
||||
)?,
|
||||
network_bridge_update_v1: prometheus::register(
|
||||
network_bridge_update: prometheus::register(
|
||||
prometheus::HistogramVec::new(
|
||||
prometheus::HistogramOpts::new(
|
||||
"polkadot_parachain_statement_distribution_network_bridge_update_v1",
|
||||
"Time spent within `statement_distribution::network_bridge_update_v1`",
|
||||
"polkadot_parachain_statement_distribution_network_bridge_update",
|
||||
"Time spent within `statement_distribution::network_bridge_update`",
|
||||
)
|
||||
.buckets(HISTOGRAM_LATENCY_BUCKETS.into()),
|
||||
&["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
@@ -89,6 +89,7 @@ pub fn dummy_overseer_builder<Spawner, SupportsParachains>(
|
||||
DummySubsystem,
|
||||
DummySubsystem,
|
||||
DummySubsystem,
|
||||
DummySubsystem,
|
||||
>,
|
||||
SubsystemError,
|
||||
>
|
||||
@@ -131,6 +132,7 @@ pub fn one_for_all_overseer_builder<Spawner, SupportsParachains, Sub>(
|
||||
Sub,
|
||||
Sub,
|
||||
Sub,
|
||||
Sub,
|
||||
>,
|
||||
SubsystemError,
|
||||
>
|
||||
@@ -159,7 +161,8 @@ where
|
||||
+ Subsystem<OverseerSubsystemContext<DisputeCoordinatorMessage>, SubsystemError>
|
||||
+ Subsystem<OverseerSubsystemContext<DisputeDistributionMessage>, SubsystemError>
|
||||
+ Subsystem<OverseerSubsystemContext<ChainSelectionMessage>, SubsystemError>
|
||||
+ Subsystem<OverseerSubsystemContext<PvfCheckerMessage>, SubsystemError>,
|
||||
+ Subsystem<OverseerSubsystemContext<PvfCheckerMessage>, SubsystemError>
|
||||
+ Subsystem<OverseerSubsystemContext<ProspectiveParachainsMessage>, SubsystemError>,
|
||||
{
|
||||
let metrics = <OverseerMetrics as MetricsTrait>::register(registry)?;
|
||||
|
||||
@@ -185,7 +188,8 @@ where
|
||||
.gossip_support(subsystem.clone())
|
||||
.dispute_coordinator(subsystem.clone())
|
||||
.dispute_distribution(subsystem.clone())
|
||||
.chain_selection(subsystem)
|
||||
.chain_selection(subsystem.clone())
|
||||
.prospective_parachains(subsystem.clone())
|
||||
.activation_external_listeners(Default::default())
|
||||
.span_per_active_leaf(Default::default())
|
||||
.active_leaves(Default::default())
|
||||
|
||||
@@ -81,7 +81,8 @@ use polkadot_node_subsystem_types::messages::{
|
||||
CandidateBackingMessage, CandidateValidationMessage, ChainApiMessage, ChainSelectionMessage,
|
||||
CollationGenerationMessage, CollatorProtocolMessage, DisputeCoordinatorMessage,
|
||||
DisputeDistributionMessage, GossipSupportMessage, NetworkBridgeRxMessage,
|
||||
NetworkBridgeTxMessage, ProvisionerMessage, RuntimeApiMessage, StatementDistributionMessage,
|
||||
NetworkBridgeTxMessage, ProspectiveParachainsMessage, ProvisionerMessage, RuntimeApiMessage,
|
||||
StatementDistributionMessage,
|
||||
};
|
||||
|
||||
pub use polkadot_node_subsystem_types::{
|
||||
@@ -466,11 +467,13 @@ pub struct Overseer<SupportsParachains> {
|
||||
#[subsystem(CandidateBackingMessage, sends: [
|
||||
CandidateValidationMessage,
|
||||
CollatorProtocolMessage,
|
||||
ChainApiMessage,
|
||||
AvailabilityDistributionMessage,
|
||||
AvailabilityStoreMessage,
|
||||
StatementDistributionMessage,
|
||||
ProvisionerMessage,
|
||||
RuntimeApiMessage,
|
||||
ProspectiveParachainsMessage,
|
||||
])]
|
||||
candidate_backing: CandidateBacking,
|
||||
|
||||
@@ -478,6 +481,8 @@ pub struct Overseer<SupportsParachains> {
|
||||
NetworkBridgeTxMessage,
|
||||
CandidateBackingMessage,
|
||||
RuntimeApiMessage,
|
||||
ProspectiveParachainsMessage,
|
||||
ChainApiMessage,
|
||||
])]
|
||||
statement_distribution: StatementDistribution,
|
||||
|
||||
@@ -516,6 +521,7 @@ pub struct Overseer<SupportsParachains> {
|
||||
CandidateBackingMessage,
|
||||
ChainApiMessage,
|
||||
DisputeCoordinatorMessage,
|
||||
ProspectiveParachainsMessage,
|
||||
])]
|
||||
provisioner: Provisioner,
|
||||
|
||||
@@ -555,6 +561,8 @@ pub struct Overseer<SupportsParachains> {
|
||||
NetworkBridgeTxMessage,
|
||||
RuntimeApiMessage,
|
||||
CandidateBackingMessage,
|
||||
ChainApiMessage,
|
||||
ProspectiveParachainsMessage,
|
||||
])]
|
||||
collator_protocol: CollatorProtocol,
|
||||
|
||||
@@ -605,6 +613,12 @@ pub struct Overseer<SupportsParachains> {
|
||||
#[subsystem(blocking, ChainSelectionMessage, sends: [ChainApiMessage])]
|
||||
chain_selection: ChainSelection,
|
||||
|
||||
#[subsystem(ProspectiveParachainsMessage, sends: [
|
||||
RuntimeApiMessage,
|
||||
ChainApiMessage,
|
||||
])]
|
||||
prospective_parachains: ProspectiveParachains,
|
||||
|
||||
/// External listeners waiting for a hash to be in the active-leave set.
|
||||
pub activation_external_listeners: HashMap<Hash, Vec<oneshot::Sender<SubsystemResult<()>>>>,
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ use polkadot_node_subsystem_types::{
|
||||
ActivatedLeaf, LeafStatus,
|
||||
};
|
||||
use polkadot_primitives::{
|
||||
CandidateHash, CandidateReceipt, CollatorPair, InvalidDisputeStatementKind, PvfExecTimeoutKind,
|
||||
SessionIndex, ValidDisputeStatementKind, ValidatorIndex,
|
||||
CandidateHash, CandidateReceipt, CollatorPair, Id as ParaId, InvalidDisputeStatementKind,
|
||||
PvfExecTimeoutKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -786,7 +786,7 @@ fn test_candidate_validation_msg() -> CandidateValidationMessage {
|
||||
|
||||
fn test_candidate_backing_msg() -> CandidateBackingMessage {
|
||||
let (sender, _) = oneshot::channel();
|
||||
CandidateBackingMessage::GetBackedCandidates(Default::default(), Vec::new(), sender)
|
||||
CandidateBackingMessage::GetBackedCandidates(Vec::new(), sender)
|
||||
}
|
||||
|
||||
fn test_chain_api_msg() -> ChainApiMessage {
|
||||
@@ -797,7 +797,7 @@ fn test_chain_api_msg() -> ChainApiMessage {
|
||||
fn test_collator_generation_msg() -> CollationGenerationMessage {
|
||||
CollationGenerationMessage::Initialize(CollationGenerationConfig {
|
||||
key: CollatorPair::generate().0,
|
||||
collator: Box::new(|_, _| TestCollator.boxed()),
|
||||
collator: Some(Box::new(|_, _| TestCollator.boxed())),
|
||||
para_id: Default::default(),
|
||||
})
|
||||
}
|
||||
@@ -912,10 +912,17 @@ fn test_chain_selection_msg() -> ChainSelectionMessage {
|
||||
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.
|
||||
#[test]
|
||||
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.
|
||||
const NUM_SUBSYSTEMS_MESSAGED: usize = NUM_SUBSYSTEMS - 4;
|
||||
|
||||
@@ -1003,6 +1010,9 @@ fn overseer_all_subsystems_receive_signals_and_messages() {
|
||||
handle
|
||||
.send_msg_anon(AllMessages::ChainSelection(test_chain_selection_msg()))
|
||||
.await;
|
||||
handle
|
||||
.send_msg_anon(AllMessages::ProspectiveParachains(test_prospective_parachains_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
|
||||
@@ -1059,6 +1069,7 @@ fn context_holds_onto_message_until_enough_signals_received() {
|
||||
let (dispute_distribution_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 (prospective_parachains_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY);
|
||||
|
||||
let (candidate_validation_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 (chain_selection_unbounded_tx, _) = metered::unbounded();
|
||||
let (pvf_checker_unbounded_tx, _) = metered::unbounded();
|
||||
let (prospective_parachains_unbounded_tx, _) = metered::unbounded();
|
||||
|
||||
let channels_out = ChannelsOut {
|
||||
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(),
|
||||
chain_selection: chain_selection_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_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(),
|
||||
chain_selection_unbounded: chain_selection_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);
|
||||
|
||||
@@ -24,10 +24,10 @@ use parity_scale_codec::{Decode, Encode};
|
||||
use sp_application_crypto::AppCrypto;
|
||||
use sp_keystore::{Error as KeystoreError, KeystorePtr};
|
||||
|
||||
use super::{Statement, UncheckedSignedFullStatement};
|
||||
use polkadot_primitives::{
|
||||
CandidateHash, CandidateReceipt, DisputeStatement, InvalidDisputeStatementKind, SessionIndex,
|
||||
SigningContext, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
|
||||
CandidateHash, CandidateReceipt, CompactStatement, DisputeStatement, EncodeAs,
|
||||
InvalidDisputeStatementKind, SessionIndex, SigningContext, UncheckedSigned,
|
||||
ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
|
||||
};
|
||||
|
||||
/// `DisputeMessage` and related types.
|
||||
@@ -270,19 +270,23 @@ impl SignedDisputeStatement {
|
||||
/// along with the signing context.
|
||||
///
|
||||
/// This does signature checks again with the data provided.
|
||||
pub fn from_backing_statement(
|
||||
backing_statement: &UncheckedSignedFullStatement,
|
||||
pub fn from_backing_statement<T>(
|
||||
backing_statement: &UncheckedSigned<T, CompactStatement>,
|
||||
signing_context: SigningContext,
|
||||
validator_public: ValidatorId,
|
||||
) -> Result<Self, ()> {
|
||||
let (statement_kind, candidate_hash) = match backing_statement.unchecked_payload() {
|
||||
Statement::Seconded(candidate) => (
|
||||
) -> Result<Self, ()>
|
||||
where
|
||||
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),
|
||||
candidate.hash(),
|
||||
candidate_hash,
|
||||
),
|
||||
Statement::Valid(candidate_hash) => (
|
||||
CompactStatement::Valid(candidate_hash) => (
|
||||
ValidDisputeStatementKind::BackingValid(signing_context.parent_hash),
|
||||
*candidate_hash,
|
||||
candidate_hash,
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use polkadot_primitives::{
|
||||
BlakeTwo256, BlockNumber, CandidateCommitments, CandidateHash, CollatorPair,
|
||||
CommittedCandidateReceipt, CompactStatement, EncodeAs, Hash, HashT, HeadData, Id as ParaId,
|
||||
PersistedValidationData, SessionIndex, Signed, UncheckedSigned, ValidationCode, ValidatorIndex,
|
||||
MAX_CODE_SIZE, MAX_POV_SIZE,
|
||||
PersistedValidationData, SessionIndex, Signed, UncheckedSigned, ValidationCode,
|
||||
ValidationCodeHash, ValidatorIndex, MAX_CODE_SIZE, MAX_POV_SIZE,
|
||||
};
|
||||
pub use sp_consensus_babe::{
|
||||
AllowedSlots as BabeAllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch,
|
||||
@@ -197,6 +197,14 @@ impl Statement {
|
||||
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 {
|
||||
@@ -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.
|
||||
///
|
||||
/// 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.
|
||||
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
|
||||
#[derive(Debug)]
|
||||
pub enum InvalidCandidate {
|
||||
@@ -287,6 +380,18 @@ pub enum MaybeCompressedPoV {
|
||||
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"))]
|
||||
impl MaybeCompressedPoV {
|
||||
/// 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
|
||||
/// - contains a proof of validity.
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
#[cfg(not(target_os = "unknown"))]
|
||||
pub struct Collation<BlockNumber = polkadot_primitives::BlockNumber> {
|
||||
/// 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.
|
||||
pub key: CollatorPair,
|
||||
/// 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
|
||||
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.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
|
||||
pub struct AvailableData {
|
||||
@@ -528,3 +655,10 @@ pub fn maybe_compress_pov(pov: PoV) -> PoV {
|
||||
let pov = PoV { block_data: BlockData(raw) };
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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-selection = { path = "../core/chain-selection", 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-pvf = { path = "../core/pvf", 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-selection",
|
||||
"polkadot-node-core-dispute-coordinator",
|
||||
"polkadot-node-core-prospective-parachains",
|
||||
"polkadot-node-core-provisioner",
|
||||
"polkadot-node-core-runtime-api",
|
||||
"polkadot-statement-distribution",
|
||||
@@ -220,3 +222,5 @@ runtime-metrics = [
|
||||
"polkadot-runtime?/runtime-metrics",
|
||||
"polkadot-runtime-parachains/runtime-metrics"
|
||||
]
|
||||
|
||||
network-protocol-staging = ["polkadot-node-network-protocol/network-protocol-staging"]
|
||||
|
||||
@@ -859,13 +859,20 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
|
||||
net_config.add_request_response_protocol(cfg);
|
||||
let (chunk_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
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);
|
||||
let (available_data_req_receiver, cfg) =
|
||||
IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
net_config.add_request_response_protocol(cfg);
|
||||
let (statement_req_receiver, cfg) = IncomingRequest::get_config_receiver(&req_protocol_names);
|
||||
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);
|
||||
net_config.add_request_response_protocol(cfg);
|
||||
|
||||
@@ -1050,9 +1057,11 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
|
||||
authority_discovery_service,
|
||||
pov_req_receiver,
|
||||
chunk_req_receiver,
|
||||
collation_req_receiver,
|
||||
collation_req_v1_receiver,
|
||||
collation_req_vstaging_receiver,
|
||||
available_data_req_receiver,
|
||||
statement_req_receiver,
|
||||
candidate_req_vstaging_receiver,
|
||||
dispute_req_receiver,
|
||||
registry: prometheus_registry.as_ref(),
|
||||
spawner,
|
||||
|
||||
@@ -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_network_protocol::{
|
||||
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))]
|
||||
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_selection::ChainSelectionSubsystem;
|
||||
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_pvf_checker::PvfCheckerSubsystem;
|
||||
pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem;
|
||||
@@ -95,13 +98,24 @@ where
|
||||
pub sync_service: Arc<sc_network_sync::SyncingService<Block>>,
|
||||
/// Underlying authority discovery service.
|
||||
pub authority_discovery_service: AuthorityDiscoveryService,
|
||||
/// POV request receiver
|
||||
/// POV request receiver.
|
||||
pub pov_req_receiver: IncomingRequestReceiver<request_v1::PoVFetchingRequest>,
|
||||
/// Erasure chunks request receiver.
|
||||
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:
|
||||
IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
|
||||
/// Receiver for incoming large statement requests.
|
||||
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>,
|
||||
/// Prometheus registry, commonly used for production systems, less so for test.
|
||||
pub registry: Option<&'a Registry>,
|
||||
@@ -143,9 +157,11 @@ pub fn prepared_overseer_builder<Spawner, RuntimeClient>(
|
||||
authority_discovery_service,
|
||||
pov_req_receiver,
|
||||
chunk_req_receiver,
|
||||
collation_req_receiver,
|
||||
collation_req_v1_receiver,
|
||||
collation_req_vstaging_receiver,
|
||||
available_data_req_receiver,
|
||||
statement_req_receiver,
|
||||
candidate_req_vstaging_receiver,
|
||||
dispute_req_receiver,
|
||||
registry,
|
||||
spawner,
|
||||
@@ -193,6 +209,7 @@ pub fn prepared_overseer_builder<Spawner, RuntimeClient>(
|
||||
DisputeCoordinatorSubsystem,
|
||||
DisputeDistributionSubsystem<AuthorityDiscoveryService>,
|
||||
ChainSelectionSubsystem,
|
||||
ProspectiveParachainsSubsystem,
|
||||
>,
|
||||
Error,
|
||||
>
|
||||
@@ -267,12 +284,13 @@ where
|
||||
.collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?))
|
||||
.collator_protocol({
|
||||
let side = match is_parachain_node {
|
||||
IsParachainNode::Collator(collator_pair) => ProtocolSide::Collator(
|
||||
network_service.local_peer_id(),
|
||||
IsParachainNode::Collator(collator_pair) => ProtocolSide::Collator {
|
||||
peer_id: network_service.local_peer_id(),
|
||||
collator_pair,
|
||||
collation_req_receiver,
|
||||
Metrics::register(registry)?,
|
||||
),
|
||||
request_receiver_v1: collation_req_v1_receiver,
|
||||
request_receiver_vstaging: collation_req_vstaging_receiver,
|
||||
metrics: Metrics::register(registry)?,
|
||||
},
|
||||
IsParachainNode::FullNode => ProtocolSide::None,
|
||||
IsParachainNode::No => ProtocolSide::Validator {
|
||||
keystore: keystore.clone(),
|
||||
@@ -291,6 +309,7 @@ where
|
||||
.statement_distribution(StatementDistributionSubsystem::new(
|
||||
keystore.clone(),
|
||||
statement_req_receiver,
|
||||
candidate_req_vstaging_receiver,
|
||||
Metrics::register(registry)?,
|
||||
rand::rngs::StdRng::from_entropy(),
|
||||
))
|
||||
@@ -320,6 +339,7 @@ where
|
||||
Metrics::register(registry)?,
|
||||
))
|
||||
.chain_selection(ChainSelectionSubsystem::new(chain_selection_config, parachains_db))
|
||||
.prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?))
|
||||
.activation_external_listeners(Default::default())
|
||||
.span_per_active_leaf(Default::default())
|
||||
.active_leaves(Default::default())
|
||||
|
||||
@@ -35,16 +35,18 @@ use polkadot_node_primitives::{
|
||||
approval::{BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote},
|
||||
AvailableData, BabeEpoch, BlockWeight, CandidateVotes, CollationGenerationConfig,
|
||||
CollationSecondedSignal, DisputeMessage, DisputeStatus, ErasureChunk, PoV,
|
||||
SignedDisputeStatement, SignedFullStatement, ValidationResult,
|
||||
SignedDisputeStatement, SignedFullStatement, SignedFullStatementWithPVD, SubmitCollationParams,
|
||||
ValidationResult,
|
||||
};
|
||||
use polkadot_primitives::{
|
||||
slashing, AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, CandidateHash,
|
||||
CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreState,
|
||||
DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Header as BlockHeader,
|
||||
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet,
|
||||
OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, PvfExecTimeoutKind,
|
||||
SessionIndex, SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields,
|
||||
ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
|
||||
slashing, vstaging as vstaging_primitives, AuthorityDiscoveryId, BackedCandidate, BlockNumber,
|
||||
CandidateEvent, CandidateHash, CandidateIndex, CandidateReceipt, CollatorId,
|
||||
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupIndex,
|
||||
GroupRotationInfo, Hash, Header as BlockHeader, Id as ParaId, InboundDownwardMessage,
|
||||
InboundHrmpMessage, MultiDisputeStatementSet, OccupiedCoreAssumption, PersistedValidationData,
|
||||
PvfCheckStatement, PvfExecTimeoutKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield,
|
||||
SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
|
||||
ValidatorSignature,
|
||||
};
|
||||
use polkadot_statement_table::v2::Misbehavior;
|
||||
use std::{
|
||||
@@ -56,20 +58,42 @@ use std::{
|
||||
pub mod network_bridge_event;
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
pub enum CandidateBackingMessage {
|
||||
/// Requests a set of backable candidates that could be backed in a child of the given
|
||||
/// relay-parent, referenced by its hash.
|
||||
GetBackedCandidates(Hash, Vec<CandidateHash>, oneshot::Sender<Vec<BackedCandidate>>),
|
||||
/// Requests a set of backable candidates attested by the subsystem.
|
||||
///
|
||||
/// 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
|
||||
/// of the given relay-parent (ref. by hash). This candidate must be validated.
|
||||
Second(Hash, CandidateReceipt, PoV),
|
||||
/// Note a validator's statement about a particular candidate. Disagreements about validity
|
||||
/// must be escalated to a broader check by the Disputes Subsystem, though that escalation is
|
||||
/// deferred until the approval voting stage to guarantee availability. Agreements are simply
|
||||
/// tallied until a quorum is reached.
|
||||
Statement(Hash, SignedFullStatement),
|
||||
Second(Hash, CandidateReceipt, PersistedValidationData, PoV),
|
||||
/// Note a validator's statement about a particular candidate in the context of the given
|
||||
/// relay-parent. Disagreements about validity must be escalated to a broader check by the
|
||||
/// Disputes Subsystem, though that escalation is deferred until the approval voting stage to
|
||||
/// guarantee availability. Agreements are simply tallied until a quorum is reached.
|
||||
Statement(Hash, SignedFullStatementWithPVD),
|
||||
}
|
||||
|
||||
/// Blanket error for validation failing for internal reasons.
|
||||
@@ -165,10 +189,16 @@ pub enum CollatorProtocolMessage {
|
||||
/// This should be sent before any `DistributeCollation` message.
|
||||
CollateOn(ParaId),
|
||||
/// 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
|
||||
/// 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
|
||||
/// and blacklist of the collator.
|
||||
ReportCollator(CollatorId),
|
||||
@@ -184,6 +214,13 @@ pub enum CollatorProtocolMessage {
|
||||
///
|
||||
/// The hash is the relay parent.
|
||||
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 {
|
||||
@@ -527,7 +564,7 @@ pub enum ChainApiMessage {
|
||||
/// Request the last finalized block number.
|
||||
/// This request always succeeds.
|
||||
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`
|
||||
/// filled with ancestors hashes with the following order:
|
||||
/// `parent`, `grandparent`, ... up to the hash of genesis block
|
||||
@@ -654,6 +691,14 @@ pub enum RuntimeApiRequest {
|
||||
slashing::OpaqueKeyOwnershipProof,
|
||||
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 {
|
||||
@@ -673,6 +718,11 @@ impl RuntimeApiRequest {
|
||||
|
||||
/// `SubmitReportDisputeLost`
|
||||
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.
|
||||
@@ -687,7 +737,14 @@ pub enum RuntimeApiMessage {
|
||||
pub enum StatementDistributionMessage {
|
||||
/// We have originated a signed statement in the context of
|
||||
/// 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.
|
||||
#[from]
|
||||
NetworkBridgeUpdate(NetworkBridgeEvent<net_protocol::StatementDistributionMessage>),
|
||||
@@ -740,6 +797,11 @@ pub enum ProvisionerMessage {
|
||||
pub enum CollationGenerationMessage {
|
||||
/// Initialize the collation generation subsystem
|
||||
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.
|
||||
@@ -897,3 +959,175 @@ pub enum GossipSupportMessage {
|
||||
#[from]
|
||||
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,
|
||||
) -> 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 ===
|
||||
|
||||
/// Returns information regarding the current epoch.
|
||||
@@ -231,6 +224,29 @@ pub trait RuntimeApiSubsystemClient {
|
||||
&self,
|
||||
at: Hash,
|
||||
) -> 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.
|
||||
@@ -456,4 +472,20 @@ where
|
||||
|
||||
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(¶_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
@@ -43,11 +43,11 @@ use futures::channel::{mpsc, oneshot};
|
||||
use parity_scale_codec::Encode;
|
||||
|
||||
use polkadot_primitives::{
|
||||
AuthorityDiscoveryId, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState,
|
||||
EncodeAs, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption,
|
||||
PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed,
|
||||
SigningContext, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
|
||||
ValidatorSignature,
|
||||
vstaging as vstaging_primitives, AuthorityDiscoveryId, CandidateEvent, CandidateHash,
|
||||
CommittedCandidateReceipt, CoreState, EncodeAs, GroupIndex, GroupRotationInfo, Hash,
|
||||
Id as ParaId, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes,
|
||||
SessionIndex, SessionInfo, Signed, SigningContext, ValidationCode, ValidationCodeHash,
|
||||
ValidatorId, ValidatorIndex, ValidatorSignature,
|
||||
};
|
||||
pub use rand;
|
||||
use sp_application_crypto::AppCrypto;
|
||||
@@ -67,11 +67,17 @@ pub mod reexports {
|
||||
pub use polkadot_overseer::gen::{SpawnedSubsystem, Spawner, Subsystem, SubsystemContext};
|
||||
}
|
||||
|
||||
/// Convenient and efficient runtime info access.
|
||||
pub mod runtime;
|
||||
|
||||
/// A utility for managing the implicit view of the relay-chain derived from active
|
||||
/// 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.
|
||||
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
|
||||
///
|
||||
@@ -200,6 +206,7 @@ macro_rules! specialize_requests {
|
||||
}
|
||||
|
||||
specialize_requests! {
|
||||
fn request_runtime_api_version() -> u32; Version;
|
||||
fn request_authorities() -> Vec<AuthorityDiscoveryId>; Authorities;
|
||||
fn request_validators() -> Vec<ValidatorId>; Validators;
|
||||
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_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_staging_async_backing_params() -> vstaging_primitives::AsyncBackingParams; StagingAsyncBackingParams;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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)
|
||||
}
|
||||
|
||||
/// From the given set of validators, find the first key we can sign with, if any, and return it
|
||||
/// along with the validator index.
|
||||
pub fn signing_key_and_index(
|
||||
validators: &[ValidatorId],
|
||||
pub fn signing_key_and_index<'a>(
|
||||
validators: impl IntoIterator<Item = &'a ValidatorId>,
|
||||
keystore: &KeystorePtr,
|
||||
) -> 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)]) {
|
||||
return Some((v.clone(), ValidatorIndex(i as _)))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ use sp_application_crypto::AppCrypto;
|
||||
use sp_core::crypto::ByteArray;
|
||||
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::{
|
||||
vstaging, CandidateEvent, CandidateHash, CoreState, EncodeAs, GroupIndex, GroupRotationInfo,
|
||||
Hash, IndexedVec, OccupiedCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed,
|
||||
@@ -36,8 +38,8 @@ use polkadot_primitives::{
|
||||
use crate::{
|
||||
request_availability_cores, request_candidate_events, request_key_ownership_proof,
|
||||
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_validator_groups,
|
||||
request_staging_async_backing_params, request_submit_report_dispute_lost,
|
||||
request_unapplied_slashes, request_validation_code_by_hash, request_validator_groups,
|
||||
};
|
||||
|
||||
/// Errors that can happen on runtime fetches.
|
||||
@@ -46,6 +48,8 @@ mod error;
|
||||
use error::{recv_runtime, Result};
|
||||
pub use error::{Error, FatalError, JfyiError};
|
||||
|
||||
const LOG_TARGET: &'static str = "parachain::runtime-info";
|
||||
|
||||
/// Configuration for construction a `RuntimeInfo`.
|
||||
pub struct Config {
|
||||
/// Needed for retrieval of `ValidatorInfo`
|
||||
@@ -393,3 +397,62 @@ where
|
||||
)
|
||||
.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 _,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +340,8 @@ impl PolkadotTestNode {
|
||||
para_id: ParaId,
|
||||
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
|
||||
.send_msg(CollationGenerationMessage::Initialize(config), "Collator")
|
||||
|
||||
Reference in New Issue
Block a user