Files
pezkuwi-subxt/polkadot/roadmap/implementers-guide/src/node/backing/candidate-backing.md
T
Robert Klotzner ca6297c853 Reduce dispute coordinator load (#5785)
* Don't import backing statements directly

into the dispute coordinator. This also gets rid of a redundant
signature check. Both should have some impact on backing performance.
In general this PR should make us scale better in the number of parachains.

Reasoning (aka why this is fine):

For the signature check: As mentioned, it is a redundant check. The
signature has already been checked at this point. This is even made
obvious by the used types. The smart constructor is not perfect as
discussed [here](https://github.com/paritytech/polkadot/issues/3455),
but is still a reasonable security.

For not importing to the dispute-coordinator: This should be good as the
dispute coordinator does scrape backing votes from chain. This suffices
in practice as a super majority of validators must have seen a backing
fork in order for a candidate to get included and only included
candidates pose a threat to our system. The import from chain is
preferable over direct import of backing votes for two reasons:

1. The import is batched, greatly improving import performance. All
   backing votes for a candidate are imported with a single import.
   And indeed we were able to see in metrics that importing votes
   from chain is fast.
2. We do less work in general as not every candidate for which
   statements are gossiped might actually make it on a chain. The
   dispute coordinator as with the current implementation would still
   import and keep those votes around for six sessions.

While redundancy is good for reliability in the event of bugs, this also
comes at a non negligible cost. The dispute-coordinator right now is the
subsystem with the highest load, despite the fact that it should not be
doing much during mormal operation and it is only getting worse
with more parachains as the load is a direct function of the number of statements.

We'll see on Versi how much of a performance improvement this PR

* Get rid of dead code.

* Dont send approval vote

* Make it pass CI

* Bring back tests for fixing them later.

* Explicit signature check.

* Resurrect approval-voting tests (not fixed yet)

* Send out approval votes in dispute-distribution.

Use BTreeMap for ordered dispute votes.

* Bring back an important warning.

* Fix approval voting tests.

* Don't send out dispute message on import + test

+ Some cleanup.

* Guide changes.

Note that the introduced complexity is actually redundant.

* WIP: guide changes.

* Finish guide changes about dispute-coordinator

conceputally. Requires more proof read still.

Also removed obsolete implementation details, where the code is better
suited as the source of truth.

* Finish guide changes for now.

* Remove own approval vote import logic.

* Implement logic for retrieving approval-votes

into approval-voting and approval-distribution subsystems.

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

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

* Review feedback.

In particular: Add note about disputes of non included candidates.

* Incorporate Review Remarks

* Get rid of superfluous space.

* Tidy up import logic a bit.

Logical vote import is now separated, making the code more readable and
maintainable.

Also: Accept import if there is at least one invalid signer that has not
exceeded its spam slots, instead of requiring all of them to not exceed
their limits. This is more correct and a preparation for vote batching.

* We don't need/have empty imports.

* Fix tests and bugs.

* Remove error prone redundancy.

* Import approval votes on dispute initiated/concluded.

* Add test for approval vote import.

* Make guide checker happy (hopefully)

* Another sanity check + better logs.

* Reasoning about boundedness.

* Use `CandidateIndex` as opposed to `CoreIndex`.

* Remove redundant import.

* Review remarks.

* Add metric for calls to request signatures

* More review remarks.

* Add metric on imported approval votes.

* Include candidate hash in logs.

* More trace log

* Break cycle.

* Add some tracing.

* Cleanup allowed messages.

* fmt

* Tracing + timeout for get inherent data.

* Better error.

* Break cycle in all places.

* Clarified comment some more.

* Typo.

* Break cycle approval-distribution - approval-voting.

Co-authored-by: asynchronous rob <rphmeier@gmail.com>
2022-08-16 08:16:30 +02:00

9.1 KiB

Candidate Backing

The Candidate Backing subsystem ensures every parablock considered for relay block inclusion has been seconded by at least one validator, and approved by a quorum. Parablocks for which not enough validators will assert correctness are discarded. If the block later proves invalid, the initial backers are slashable; this gives polkadot a rational threat model during subsequent stages.

Its role is to produce backable candidates for inclusion in new relay-chain blocks. It does so by issuing signed Statements and tracking received statements signed by other validators. Once enough statements are received, they can be combined into backing for specific candidates.

Note that though the candidate backing subsystem attempts to produce as many backable candidates as possible, it does not attempt to choose a single authoritative one. The choice of which actually gets included is ultimately up to the block author, by whatever metrics it may use; those are opaque to this subsystem.

Once a sufficient quorum has agreed that a candidate is valid, this subsystem notifies the Provisioner, which in turn engages block production mechanisms to include the parablock.

Protocol

Input: CandidateBackingMessage

Output:

Functionality

The Collator Protocol subsystem is the primary source of non-overseer messages into this subsystem. That subsystem generates appropriate CandidateBackingMessages and passes them to this subsystem.

This subsystem requests validation from the Candidate Validation and generates an appropriate Statement. All Statements are then passed on to the Statement Distribution subsystem to be gossiped to peers. When Candidate Validation decides that a candidate is invalid, and it was recommended to us to second by our own Collator Protocol subsystem, a message is sent to the Collator Protocol subsystem with the candidate's hash so that the collator which recommended it can be penalized.

The subsystem should maintain a set of handles to Candidate Backing Jobs that are currently live, as well as the relay-parent to which they correspond.

On Overseer Signal

  • If the signal is an OverseerSignal::ActiveLeavesUpdate:
    • spawn a Candidate Backing Job for each activated head referring to a fresh leaf, storing a bidirectional channel with the Candidate Backing Job in the set of handles.
    • cease the Candidate Backing Job for each deactivated head, if any.
  • If the signal is an OverseerSignal::Conclude: Forward conclude messages to all jobs, wait a small amount of time for them to join, and then exit.

On Receiving CandidateBackingMessage

  • If the message is a CandidateBackingMessage::GetBackedCandidates, get all backable candidates from the statement table and send them back.
  • If the message is a CandidateBackingMessage::Second, sign and dispatch a Seconded statement only if we have not seconded any other candidate and have not signed a Valid statement for the requested candidate. Signing both a Seconded and Valid message is a double-voting misbehavior with a heavy penalty, and this could occur if another validator has seconded the same candidate and we've received their message before the internal seconding request.
  • If the message is a CandidateBackingMessage::Statement, count the statement to the quorum. If the statement in the message is Seconded and it contains a candidate that belongs to our assignment, request the corresponding PoV from the backing node via AvailabilityDistribution and launch validation. Issue our own Valid or Invalid statement as a result.

If the seconding node did not provide us with the PoV we will retry fetching from other backing validators.

big TODO: "contextual execution"

  • At the moment we only allow inclusion of new parachain candidates validated by current validators.
  • Allow inclusion of old parachain candidates validated by current validators.
  • Allow inclusion of old parachain candidates validated by old validators.

This will probably blur the lines between jobs, will probably require inter-job communication and a short-term memory of recently backable, but not backed candidates.

Candidate Backing Job

The Candidate Backing Job represents the work a node does for backing candidates with respect to a particular relay-parent.

The goal of a Candidate Backing Job is to produce as many backable candidates as possible. This is done via signed Statements by validators. If a candidate receives a majority of supporting Statements from the Parachain Validators currently assigned, then that candidate is considered backable.

On Startup

On Receiving New Candidate Backing Message

match msg {
  GetBackedCandidates(hashes, tx) => {
    // Send back a set of backable candidates.
  }
  CandidateBackingMessage::Second(hash, candidate) => {
    if candidate is unknown and in local assignment {
      if spawn_validation_work(candidate, parachain head, validation function).await == Valid {
        send(DistributePoV(pov))
      }
    }
  }
  CandidateBackingMessage::Statement(hash, statement) => {
    // count to the votes on this candidate
    if let Statement::Seconded(candidate) = statement {
      if candidate.parachain_id == our_assignment {
        spawn_validation_work(candidate, parachain head, validation function)
      }
    }
  }
}

Add Seconded statements and Valid statements to a quorum. If the quorum reaches a pre-defined threshold, send a ProvisionerMessage::ProvisionableData(ProvisionableData::BackedCandidate(CandidateReceipt)) message. Invalid statements that conflict with already witnessed Seconded and Valid statements for the given candidate, statements that are double-votes, self-contradictions and so on, should result in issuing a ProvisionerMessage::MisbehaviorReport message for each newly detected case of this kind.

Backing does not need to concern itself with providing statements to the dispute coordinator as the dispute coordinator scrapes them from chain. This way the import is batched and contains only statements that actually made it on some chain.

Validating Candidates.

fn spawn_validation_work(candidate, parachain head, validation function) {
  asynchronously {
    let pov = (fetch pov block).await

    let valid = (validate pov block).await;
    if valid {
      // make PoV available for later distribution. Send data to the availability store to keep.
      // sign and dispatch `valid` statement to network if we have not seconded the given candidate.
    } else {
      // sign and dispatch `invalid` statement to network.
    }
  }
}

Fetch PoV Block

Create a (sender, receiver) pair. Dispatch a AvailabilityDistributionMessage::FetchPoV{ validator_index, pov_hash, candidate_hash, tx, } and listen on the passed receiver for a response. Availability distribution will send the request to the validator specified by validator_index`, which might not be serving it for whatever reasons, therefore we need to retry with other backing validators in that case.

Validate PoV Block

Create a (sender, receiver) pair. Dispatch a CandidateValidationMessage::Validate(validation function, candidate, pov, BACKING_EXECUTION_TIMEOUT, sender) and listen on the receiver for a response.

Distribute Signed Statement

Dispatch a StatementDistributionMessage::Share(relay_parent, SignedFullStatement).