mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 23:45:40 +00:00
Markdown linter (#1309)
* Add markdown linting - add linter default rules - adapt rules to current code - fix the code for linting to pass - add CI check fix #1243 * Fix markdown for Substrate * Fix tooling install * Fix workflow * Add documentation * Remove trailing spaces * Update .github/.markdownlint.yaml Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix mangled markdown/lists * Fix captalization issues on known words
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
# Backing Subsystems
|
||||
|
||||
The backing subsystems, when conceived as a black box, receive an arbitrary quantity of parablock candidates and associated proofs of validity from arbitrary untrusted collators. From these, they produce a bounded quantity of backable candidates which relay chain block authors may choose to include in a subsequent block.
|
||||
The backing subsystems, when conceived as a black box, receive an arbitrary quantity of parablock candidates and
|
||||
associated proofs of validity from arbitrary untrusted collators. From these, they produce a bounded quantity of
|
||||
backable candidates which relay chain block authors may choose to include in a subsequent block.
|
||||
|
||||
In broad strokes, the flow operates like this:
|
||||
|
||||
- **Candidate Selection** winnows the field of parablock candidates, selecting up to one of them to second.
|
||||
- **Candidate Backing** ensures that a seconding candidate is valid, then generates the appropriate `Statement`. It also keeps track of which candidates have received the backing of a quorum of other validators.
|
||||
- **Statement Distribution** is the networking component which ensures that all validators receive each others' statements.
|
||||
- **PoV Distribution** is the networking component which ensures that validators considering a candidate can get the appropriate PoV.
|
||||
- **Candidate Backing** ensures that a seconding candidate is valid, then generates the appropriate `Statement`. It also
|
||||
keeps track of which candidates have received the backing of a quorum of other validators.
|
||||
- **Statement Distribution** is the networking component which ensures that all validators receive each others'
|
||||
statements.
|
||||
- **PoV Distribution** is the networking component which ensures that validators considering a candidate can get the
|
||||
appropriate PoV.
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
# 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.
|
||||
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 [`Statement`s][Statement] and tracking received statements signed by other validators. Once enough statements are received, they can be combined into backing for specific candidates.
|
||||
Its role is to produce backable candidates for inclusion in new relay-chain blocks. It does so by issuing signed
|
||||
[`Statement`s][Statement] 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.
|
||||
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][PV], which in turn engages block production mechanisms to include the parablock.
|
||||
Once a sufficient quorum has agreed that a candidate is valid, this subsystem notifies the [Provisioner][PV], which in
|
||||
turn engages block production mechanisms to include the parablock.
|
||||
|
||||
## Protocol
|
||||
|
||||
@@ -14,33 +22,49 @@ Input: [`CandidateBackingMessage`][CBM]
|
||||
|
||||
Output:
|
||||
|
||||
- [`CandidateValidationMessage`][CVM]
|
||||
- [`RuntimeApiMessage`][RAM]
|
||||
- [`CollatorProtocolMessage`][CPM]
|
||||
- [`ProvisionerMessage`][PM]
|
||||
- [`AvailabilityDistributionMessage`][ADM]
|
||||
- [`StatementDistributionMessage`][SDM]
|
||||
* [`CandidateValidationMessage`][CVM]
|
||||
* [`RuntimeApiMessage`][RAM]
|
||||
* [`CollatorProtocolMessage`][CPM]
|
||||
* [`ProvisionerMessage`][PM]
|
||||
* [`AvailabilityDistributionMessage`][ADM]
|
||||
* [`StatementDistributionMessage`][SDM]
|
||||
|
||||
## Functionality
|
||||
|
||||
The [Collator Protocol][CP] subsystem is the primary source of non-overseer messages into this subsystem. That subsystem generates appropriate [`CandidateBackingMessage`s][CBM] and passes them to this subsystem.
|
||||
The [Collator Protocol][CP] subsystem is the primary source of non-overseer messages into this subsystem. That subsystem
|
||||
generates appropriate [`CandidateBackingMessage`s][CBM] and passes them to this subsystem.
|
||||
|
||||
This subsystem requests validation from the [Candidate Validation][CV] and generates an appropriate [`Statement`][Statement]. All `Statement`s are then passed on to the [Statement Distribution][SD] subsystem to be gossiped to peers. When [Candidate Validation][CV] decides that a candidate is invalid, and it was recommended to us to second by our own [Collator Protocol][CP] subsystem, a message is sent to the [Collator Protocol][CP] subsystem with the candidate's hash so that the collator which recommended it can be penalized.
|
||||
This subsystem requests validation from the [Candidate Validation][CV] and generates an appropriate
|
||||
[`Statement`][Statement]. All `Statement`s are then passed on to the [Statement Distribution][SD] subsystem to be
|
||||
gossiped to peers. When [Candidate Validation][CV] decides that a candidate is invalid, and it was recommended to us to
|
||||
second by our own [Collator Protocol][CP] subsystem, a message is sent to the [Collator Protocol][CP] 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.
|
||||
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`][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.
|
||||
* 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`][OverseerSignal]`::Conclude`: Forward conclude messages to all jobs, wait a small amount of time for them to join, and then exit.
|
||||
* If the signal is an [`OverseerSignal`][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`][CBM]`::GetBackedCandidates`, get all backable candidates from the statement table and send them back.
|
||||
* If the message is a [`CandidateBackingMessage`][CBM]`::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`][CBM]`::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 message is a [`CandidateBackingMessage`][CBM]`::GetBackedCandidates`, get all backable candidates from the
|
||||
statement table and send them back.
|
||||
* If the message is a [`CandidateBackingMessage`][CBM]`::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`][CBM]`::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.
|
||||
|
||||
@@ -51,19 +75,25 @@ If the seconding node did not provide us with the `PoV` we will retry fetching f
|
||||
> * 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.
|
||||
> 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 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 [`Statement`s][STMT] by validators. If a candidate receives a majority of supporting Statements from the Parachain Validators currently assigned, then that candidate is considered backable.
|
||||
The goal of a Candidate Backing Job is to produce as many backable candidates as possible. This is done via signed
|
||||
[`Statement`s][STMT] 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
|
||||
|
||||
* Fetch current validator set, validator -> parachain assignments from [`Runtime API`][RA] subsystem using [`RuntimeApiRequest::Validators`][RAM] and [`RuntimeApiRequest::ValidatorGroups`][RAM]
|
||||
* Fetch current validator set, validator -> parachain assignments from [`Runtime API`][RA] subsystem using
|
||||
[`RuntimeApiRequest::Validators`][RAM] and [`RuntimeApiRequest::ValidatorGroups`][RAM]
|
||||
* Determine if the node controls a key in the current validator set. Call this the local key if so.
|
||||
* If the local key exists, extract the parachain head and validation function from the [`Runtime API`][RA] for the parachain the local key is assigned to by issuing a [`RuntimeApiRequest::Validators`][RAM]
|
||||
* If the local key exists, extract the parachain head and validation function from the [`Runtime API`][RA] for the
|
||||
parachain the local key is assigned to by issuing a [`RuntimeApiRequest::Validators`][RAM]
|
||||
* Issue a [`RuntimeApiRequest::SigningContext`][RAM] message to get a context that will later be used upon signing.
|
||||
|
||||
### On Receiving New Candidate Backing Message
|
||||
@@ -91,15 +121,17 @@ match msg {
|
||||
}
|
||||
```
|
||||
|
||||
Add `Seconded` statements and `Valid` statements to a quorum. If the quorum reaches a pre-defined threshold, send a [`ProvisionerMessage`][PM]`::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`][PM]`::MisbehaviorReport` message for each newly detected case of this kind.
|
||||
Add `Seconded` statements and `Valid` statements to a quorum. If the quorum reaches a pre-defined threshold, send a
|
||||
[`ProvisionerMessage`][PM]`::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`][PM]`::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
|
||||
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.
|
||||
### Validating Candidates
|
||||
|
||||
```rust
|
||||
fn spawn_validation_work(candidate, parachain head, validation function) {
|
||||
@@ -119,14 +151,16 @@ fn spawn_validation_work(candidate, parachain head, validation function) {
|
||||
|
||||
### Fetch PoV Block
|
||||
|
||||
Create a `(sender, receiver)` pair.
|
||||
Dispatch a [`AvailabilityDistributionMessage`][ADM]`::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.
|
||||
Create a `(sender, receiver)` pair. Dispatch a [`AvailabilityDistributionMessage`][ADM]`::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.
|
||||
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
|
||||
|
||||
|
||||
+95
-53
@@ -1,18 +1,16 @@
|
||||
# Statement Distribution (Legacy)
|
||||
|
||||
This describes the legacy, backwards-compatible version of the Statement
|
||||
Distribution subsystem.
|
||||
This describes the legacy, backwards-compatible version of the Statement Distribution subsystem.
|
||||
|
||||
**Note:** All the V1 (legacy) code was extracted out to a `legacy_v1` module of
|
||||
the `statement-distribution` crate, which doesn't alter any logic. V2 (new
|
||||
protocol) peers also run `legacy_v1` and communicate with V1 peers using V1
|
||||
messages and with V2 peers using V2 messages. Once the runtime upgrade goes
|
||||
through on all networks, this `legacy_v1` code will no longer be triggered and
|
||||
will be vestigial and can be removed.
|
||||
**Note:** All the V1 (legacy) code was extracted out to a `legacy_v1` module of the `statement-distribution` crate,
|
||||
which doesn't alter any logic. V2 (new protocol) peers also run `legacy_v1` and communicate with V1 peers using V1
|
||||
messages and with V2 peers using V2 messages. Once the runtime upgrade goes through on all networks, this `legacy_v1`
|
||||
code will no longer be triggered and will be vestigial and can be removed.
|
||||
|
||||
## Overview
|
||||
|
||||
The Statement Distribution Subsystem is responsible for distributing statements about seconded candidates between validators.
|
||||
The Statement Distribution Subsystem is responsible for distributing statements about seconded candidates between
|
||||
validators.
|
||||
|
||||
## Protocol
|
||||
|
||||
@@ -31,89 +29,133 @@ Output:
|
||||
|
||||
## Functionality
|
||||
|
||||
Implemented as a gossip protocol. Handles updates to our view and peers' views. Neighbor packets are used to inform peers which chain heads we are interested in data for.
|
||||
Implemented as a gossip protocol. Handles updates to our view and peers' views. Neighbor packets are used to inform
|
||||
peers which chain heads we are interested in data for.
|
||||
|
||||
The Statement Distribution Subsystem is responsible for distributing signed statements that we have generated and for forwarding statements generated by other validators. It also detects a variety of Validator misbehaviors for reporting to the [Provisioner Subsystem](../utility/provisioner.md). During the Backing stage of the inclusion pipeline, Statement Distribution is the main point of contact with peer nodes. On receiving a signed statement from a peer in the same backing group, assuming the peer receipt state machine is in an appropriate state, it sends the Candidate Receipt to the [Candidate Backing subsystem](candidate-backing.md) to handle the validator's statement. On receiving `StatementDistributionMessage::Share` we make sure to send messages to our backing group in addition to random other peers, to ensure a fast backing process and getting all statements quickly for distribution.
|
||||
The Statement Distribution Subsystem is responsible for distributing signed statements that we have generated and for
|
||||
forwarding statements generated by other validators. It also detects a variety of Validator misbehaviors for reporting
|
||||
to the [Provisioner Subsystem](../utility/provisioner.md). During the Backing stage of the inclusion pipeline, Statement
|
||||
Distribution is the main point of contact with peer nodes. On receiving a signed statement from a peer in the same
|
||||
backing group, assuming the peer receipt state machine is in an appropriate state, it sends the Candidate Receipt to the
|
||||
[Candidate Backing subsystem](candidate-backing.md) to handle the validator's statement. On receiving
|
||||
`StatementDistributionMessage::Share` we make sure to send messages to our backing group in addition to random other
|
||||
peers, to ensure a fast backing process and getting all statements quickly for distribution.
|
||||
|
||||
This subsystem tracks equivocating validators and stops accepting information from them. It establishes a data-dependency order:
|
||||
This subsystem tracks equivocating validators and stops accepting information from them. It establishes a
|
||||
data-dependency order:
|
||||
|
||||
- In order to receive a `Seconded` message we have the corresponding chain head in our view
|
||||
- In order to receive a `Valid` message we must have received the corresponding `Seconded` message.
|
||||
|
||||
And respect this data-dependency order from our peers by respecting their views. This subsystem is responsible for checking message signatures.
|
||||
And respect this data-dependency order from our peers by respecting their views. This subsystem is responsible for
|
||||
checking message signatures.
|
||||
|
||||
The Statement Distribution subsystem sends statements to peer nodes.
|
||||
|
||||
## Peer Receipt State Machine
|
||||
|
||||
There is a very simple state machine which governs which messages we are willing to receive from peers. Not depicted in the state machine: on initial receipt of any [`SignedFullStatement`](../../types/backing.md#signed-statement-type), validate that the provided signature does in fact sign the included data. Note that each individual parablock candidate gets its own instance of this state machine; it is perfectly legal to receive a `Valid(X)` before a `Seconded(Y)`, as long as a `Seconded(X)` has been received.
|
||||
There is a very simple state machine which governs which messages we are willing to receive from peers. Not depicted in
|
||||
the state machine: on initial receipt of any [`SignedFullStatement`](../../types/backing.md#signed-statement-type),
|
||||
validate that the provided signature does in fact sign the included data. Note that each individual parablock candidate
|
||||
gets its own instance of this state machine; it is perfectly legal to receive a `Valid(X)` before a `Seconded(Y)`, as
|
||||
long as a `Seconded(X)` has been received.
|
||||
|
||||
A: Initial State. Receive `SignedFullStatement(Statement::Second)`: extract `Statement`, forward to Candidate Backing, proceed to B. Receive any other `SignedFullStatement` variant: drop it.
|
||||
A: Initial State. Receive `SignedFullStatement(Statement::Second)`: extract `Statement`, forward to Candidate Backing,
|
||||
proceed to B. Receive any other `SignedFullStatement` variant: drop it.
|
||||
|
||||
B: Receive any `SignedFullStatement`: check signature and determine whether the statement is new to us. if new, forward to Candidate Backing and circulate to other peers. Receive `OverseerMessage::StopWork`: proceed to C.
|
||||
B: Receive any `SignedFullStatement`: check signature and determine whether the statement is new to us. if new, forward
|
||||
to Candidate Backing and circulate to other peers. Receive `OverseerMessage::StopWork`: proceed to C.
|
||||
|
||||
C: Receive any message for this block: drop it.
|
||||
|
||||
For large statements (see below), we also keep track of the total received large
|
||||
statements per peer and have a hard limit on that number for flood protection.
|
||||
This is necessary as in the current code we only forward statements once we have
|
||||
all the data, therefore flood protection for large statement is a bit more
|
||||
subtle. This will become an obsolete problem once [off chain code
|
||||
upgrades](https://github.com/paritytech/polkadot/issues/2979) are implemented.
|
||||
For large statements (see below), we also keep track of the total received large statements per peer and have a hard
|
||||
limit on that number for flood protection. This is necessary as in the current code we only forward statements once we
|
||||
have all the data, therefore flood protection for large statement is a bit more subtle. This will become an obsolete
|
||||
problem once [off chain code upgrades](https://github.com/paritytech/polkadot/issues/2979) are implemented.
|
||||
|
||||
## Peer Knowledge Tracking
|
||||
|
||||
The peer receipt state machine implies that for parsimony of network resources, we should model the knowledge of our peers, and help them out. For example, let's consider a case with peers A, B, and C, validators X and Y, and candidate M. A sends us a `Statement::Second(M)` signed by X. We've double-checked it, and it's valid. While we're checking it, we receive a copy of X's `Statement::Second(M)` from `B`, along with a `Statement::Valid(M)` signed by Y.
|
||||
The peer receipt state machine implies that for parsimony of network resources, we should model the knowledge of our
|
||||
peers, and help them out. For example, let's consider a case with peers A, B, and C, validators X and Y, and candidate
|
||||
M. A sends us a `Statement::Second(M)` signed by X. We've double-checked it, and it's valid. While we're checking it, we
|
||||
receive a copy of X's `Statement::Second(M)` from `B`, along with a `Statement::Valid(M)` signed by Y.
|
||||
|
||||
Our response to A is just the `Statement::Valid(M)` signed by Y. However, we haven't heard anything about this from C. Therefore, we send it everything we have: first a copy of X's `Statement::Second`, then Y's `Statement::Valid`.
|
||||
Our response to A is just the `Statement::Valid(M)` signed by Y. However, we haven't heard anything about this from C.
|
||||
Therefore, we send it everything we have: first a copy of X's `Statement::Second`, then Y's `Statement::Valid`.
|
||||
|
||||
This system implies a certain level of duplication of messages--we received X's `Statement::Second` from both our peers, and C may experience the same--but it minimizes the degree to which messages are simply dropped.
|
||||
This system implies a certain level of duplication of messages--we received X's `Statement::Second` from both our peers,
|
||||
and C may experience the same--but it minimizes the degree to which messages are simply dropped.
|
||||
|
||||
And respect this data-dependency order from our peers. This subsystem is responsible for checking message signatures.
|
||||
|
||||
No jobs. We follow view changes from the [`NetworkBridge`](../utility/network-bridge.md), which in turn is updated by the overseer.
|
||||
No jobs. We follow view changes from the [`NetworkBridge`](../utility/network-bridge.md), which in turn is updated by
|
||||
the overseer.
|
||||
|
||||
## Equivocations and Flood Protection
|
||||
|
||||
An equivocation is a double-vote by a validator. The [Candidate Backing](candidate-backing.md) Subsystem is better-suited than this one to detect equivocations as it adds votes to quorum trackers.
|
||||
An equivocation is a double-vote by a validator. The [Candidate Backing](candidate-backing.md) Subsystem is
|
||||
better-suited than this one to detect equivocations as it adds votes to quorum trackers.
|
||||
|
||||
At this level, we are primarily concerned about flood-protection, and to some extent, detecting equivocations is a part of that. In particular, we are interested in detecting equivocations of `Seconded` statements. Since every other statement is dependent on `Seconded` statements, ensuring that we only ever hold a bounded number of `Seconded` statements is sufficient for flood-protection.
|
||||
At this level, we are primarily concerned about flood-protection, and to some extent, detecting equivocations is a part
|
||||
of that. In particular, we are interested in detecting equivocations of `Seconded` statements. Since every other
|
||||
statement is dependent on `Seconded` statements, ensuring that we only ever hold a bounded number of `Seconded`
|
||||
statements is sufficient for flood-protection.
|
||||
|
||||
The simple approach is to say that we only receive up to two `Seconded` statements per validator per chain head. However, the marginal cost of equivocation, conditional on having already equivocated, is close to 0, since a single double-vote offence is counted as all double-vote offences for a particular chain-head. Even if it were not, there is some amount of equivocations that can be done such that the marginal cost of issuing further equivocations is close to 0, as there would be an amount of equivocations necessary to be completely and totally obliterated by the slashing algorithm. We fear the validator with nothing left to lose.
|
||||
The simple approach is to say that we only receive up to two `Seconded` statements per validator per chain head.
|
||||
However, the marginal cost of equivocation, conditional on having already equivocated, is close to 0, since a single
|
||||
double-vote offence is counted as all double-vote offences for a particular chain-head. Even if it were not, there is
|
||||
some amount of equivocations that can be done such that the marginal cost of issuing further equivocations is close to
|
||||
0, as there would be an amount of equivocations necessary to be completely and totally obliterated by the slashing
|
||||
algorithm. We fear the validator with nothing left to lose.
|
||||
|
||||
With that in mind, this simple approach has a caveat worth digging deeper into.
|
||||
|
||||
First: We may be aware of two equivocated `Seconded` statements issued by a validator. A totally honest peer of ours can also be aware of one or two different `Seconded` statements issued by the same validator. And yet another peer may be aware of one or two _more_ `Seconded` statements. And so on. This interacts badly with pre-emptive sending logic. Upon sending a `Seconded` statement to a peer, we will want to pre-emptively follow up with all statements relative to that candidate. Waiting for acknowledgment introduces latency at every hop, so that is best avoided. What can happen is that upon receipt of the `Seconded` statement, the peer will discard it as it falls beyond the bound of 2 that it is allowed to store. It cannot store anything in memory about discarded candidates as that would introduce a DoS vector. Then, the peer would receive from us all of the statements pertaining to that candidate, which, from its perspective, would be undesired - they are data-dependent on the `Seconded` statement we sent them, but they have erased all record of that from their memory. Upon receiving a potential flood of undesired statements, this 100% honest peer may choose to disconnect from us. In this way, an adversary may be able to partition the network with careful distribution of equivocated `Seconded` statements.
|
||||
First: We may be aware of two equivocated `Seconded` statements issued by a validator. A totally honest peer of ours can
|
||||
also be aware of one or two different `Seconded` statements issued by the same validator. And yet another peer may be
|
||||
aware of one or two _more_ `Seconded` statements. And so on. This interacts badly with pre-emptive sending logic. Upon
|
||||
sending a `Seconded` statement to a peer, we will want to pre-emptively follow up with all statements relative to that
|
||||
candidate. Waiting for acknowledgment introduces latency at every hop, so that is best avoided. What can happen is that
|
||||
upon receipt of the `Seconded` statement, the peer will discard it as it falls beyond the bound of 2 that it is allowed
|
||||
to store. It cannot store anything in memory about discarded candidates as that would introduce a DoS vector. Then, the
|
||||
peer would receive from us all of the statements pertaining to that candidate, which, from its perspective, would be
|
||||
undesired - they are data-dependent on the `Seconded` statement we sent them, but they have erased all record of that
|
||||
from their memory. Upon receiving a potential flood of undesired statements, this 100% honest peer may choose to
|
||||
disconnect from us. In this way, an adversary may be able to partition the network with careful distribution of
|
||||
equivocated `Seconded` statements.
|
||||
|
||||
The fix is to track, per-peer, the hashes of up to 4 candidates per validator (per relay-parent) that the peer is aware of. It is 4 because we may send them 2 and they may send us 2 different ones. We track the data that they are aware of as the union of things we have sent them and things they have sent us. If we receive a 1st or 2nd `Seconded` statement from a peer, we note it in the peer's known candidates even if we do disregard the data locally. And then, upon receipt of any data dependent on that statement, we do not reduce that peer's standing in our eyes, as the data was not undesired.
|
||||
The fix is to track, per-peer, the hashes of up to 4 candidates per validator (per relay-parent) that the peer is aware
|
||||
of. It is 4 because we may send them 2 and they may send us 2 different ones. We track the data that they are aware of
|
||||
as the union of things we have sent them and things they have sent us. If we receive a 1st or 2nd `Seconded` statement
|
||||
from a peer, we note it in the peer's known candidates even if we do disregard the data locally. And then, upon receipt
|
||||
of any data dependent on that statement, we do not reduce that peer's standing in our eyes, as the data was not
|
||||
undesired.
|
||||
|
||||
There is another caveat to the fix: we don't want to allow the peer to flood us because it has set things up in a way that it knows we will drop all of its traffic.
|
||||
We also track how many statements we have received per peer, per candidate, and per chain-head. This is any statement concerning a particular candidate: `Seconded`, `Valid`, or `Invalid`. If we ever receive a statement from a peer which would push any of these counters beyond twice the amount of validators at the chain-head, we begin to lower the peer's standing and eventually disconnect. This bound is a massive overestimate and could be reduced to twice the number of validators in the corresponding validator group. It is worth noting that the goal at the time of writing is to ensure any finite bound on the amount of stored data, as any equivocation results in a large slash.
|
||||
There is another caveat to the fix: we don't want to allow the peer to flood us because it has set things up in a way
|
||||
that it knows we will drop all of its traffic. We also track how many statements we have received per peer, per
|
||||
candidate, and per chain-head. This is any statement concerning a particular candidate: `Seconded`, `Valid`, or
|
||||
`Invalid`. If we ever receive a statement from a peer which would push any of these counters beyond twice the amount of
|
||||
validators at the chain-head, we begin to lower the peer's standing and eventually disconnect. This bound is a massive
|
||||
overestimate and could be reduced to twice the number of validators in the corresponding validator group. It is worth
|
||||
noting that the goal at the time of writing is to ensure any finite bound on the amount of stored data, as any
|
||||
equivocation results in a large slash.
|
||||
|
||||
## Large statements
|
||||
|
||||
Seconded statements can become quite large on parachain runtime upgrades for
|
||||
example. For this reason, there exists a `LargeStatement` constructor for the
|
||||
`StatementDistributionMessage` wire message, which only contains light metadata
|
||||
of a statement. The actual candidate data is not included. This message type is
|
||||
used whenever a message is deemed large. The receiver of such a message needs to
|
||||
request the actual payload via request/response by means of a
|
||||
Seconded statements can become quite large on parachain runtime upgrades for example. For this reason, there exists a
|
||||
`LargeStatement` constructor for the `StatementDistributionMessage` wire message, which only contains light metadata of
|
||||
a statement. The actual candidate data is not included. This message type is used whenever a message is deemed large.
|
||||
The receiver of such a message needs to request the actual payload via request/response by means of a
|
||||
`StatementFetchingV1` request.
|
||||
|
||||
This is necessary as distribution of a large payload (mega bytes) via gossip
|
||||
would make the network collapse and timely distribution of statements would no
|
||||
longer be possible. By using request/response it is ensured that each peer only
|
||||
transferes large data once. We only take good care to detect an overloaded
|
||||
peer early and immediately move on to a different peer for fetching the data.
|
||||
This mechanism should result in a good load distribution and therefore a rather
|
||||
This is necessary as distribution of a large payload (mega bytes) via gossip would make the network collapse and timely
|
||||
distribution of statements would no longer be possible. By using request/response it is ensured that each peer only
|
||||
transferes large data once. We only take good care to detect an overloaded peer early and immediately move on to a
|
||||
different peer for fetching the data. This mechanism should result in a good load distribution and therefore a rather
|
||||
optimal distribution path.
|
||||
|
||||
With these optimizations, distribution of payloads in the size of up to 3 to 4
|
||||
MB should work with Kusama validator specifications. For scaling up even more,
|
||||
runtime upgrades and message passing should be done off chain at some point.
|
||||
With these optimizations, distribution of payloads in the size of up to 3 to 4 MB should work with Kusama validator
|
||||
specifications. For scaling up even more, runtime upgrades and message passing should be done off chain at some point.
|
||||
|
||||
Flood protection considerations: For making DoS attacks slightly harder on this
|
||||
subsystem, nodes will only respond to large statement requests, when they
|
||||
previously notified that peer via gossip about that statement. So, it is not
|
||||
possible to DoS nodes at scale, by requesting candidate data over and over
|
||||
again.
|
||||
Flood protection considerations: For making DoS attacks slightly harder on this subsystem, nodes will only respond to
|
||||
large statement requests, when they previously notified that peer via gossip about that statement. So, it is not
|
||||
possible to DoS nodes at scale, by requesting candidate data over and over again.
|
||||
|
||||
@@ -1,158 +1,127 @@
|
||||
# Statement Distribution
|
||||
|
||||
This subsystem is responsible for distributing signed statements that we have generated and forwarding statements generated by our peers. Received candidate receipts and statements are passed to the [Candidate Backing subsystem](candidate-backing.md) to handle producing local statements. On receiving `StatementDistributionMessage::Share`, this subsystem distributes the message across the network with redundency to ensure a fast backing process.
|
||||
This subsystem is responsible for distributing signed statements that we have generated and forwarding statements
|
||||
generated by our peers. Received candidate receipts and statements are passed to the [Candidate Backing
|
||||
subsystem](candidate-backing.md) to handle producing local statements. On receiving
|
||||
`StatementDistributionMessage::Share`, this subsystem distributes the message across the network with redundency to
|
||||
ensure a fast backing process.
|
||||
|
||||
## Overview
|
||||
|
||||
**Goal:** every well-connected node is aware of every next potential parachain
|
||||
block.
|
||||
**Goal:** every well-connected node is aware of every next potential parachain block.
|
||||
|
||||
Validators can either:
|
||||
|
||||
- receive parachain block from collator, check block, and gossip statement.
|
||||
- receive statements from other validators, check the parachain block if it
|
||||
originated within their own group, gossip forward statement if valid.
|
||||
- receive statements from other validators, check the parachain block if it originated within their own group, gossip
|
||||
forward statement if valid.
|
||||
|
||||
Validators must have statements, candidates, and persisted validation from all
|
||||
other validators. This is because we need to store statements from validators
|
||||
who've checked the candidate on the relay chain, so we know who to hold
|
||||
accountable in case of disputes. Any validator can be selected as the next
|
||||
relay-chain block author, and this is not revealed in advance for security
|
||||
reasons. As a result, all validators must have a up to date view of all possible
|
||||
parachain candidates + backing statements that could be placed on-chain in the
|
||||
next block.
|
||||
Validators must have statements, candidates, and persisted validation from all other validators. This is because we need
|
||||
to store statements from validators who've checked the candidate on the relay chain, so we know who to hold accountable
|
||||
in case of disputes. Any validator can be selected as the next relay-chain block author, and this is not revealed in
|
||||
advance for security reasons. As a result, all validators must have a up to date view of all possible parachain
|
||||
candidates + backing statements that could be placed on-chain in the next block.
|
||||
|
||||
[This blog post](https://polkadot.network/blog/polkadot-v1-0-sharding-and-economic-security)
|
||||
puts it another way: "Validators who aren't assigned to the parachain still
|
||||
listen for the attestations [statements] because whichever validator ends up
|
||||
being the author of the relay-chain block needs to bundle up attested parachain
|
||||
blocks for several parachains and place them into the relay-chain block."
|
||||
[This blog post](https://polkadot.network/blog/polkadot-v1-0-sharding-and-economic-security) puts it another way:
|
||||
"Validators who aren't assigned to the parachain still listen for the attestations [statements] because whichever
|
||||
validator ends up being the author of the relay-chain block needs to bundle up attested parachain blocks for several
|
||||
parachains and place them into the relay-chain block."
|
||||
|
||||
Backing-group quorum (that is, enough backing group votes) must be reached
|
||||
before the block author will consider the candidate. Therefore, validators need
|
||||
to consider _all_ seconded candidates within their own group, because that's
|
||||
what they're assigned to work on. Validators only need to consider _backable_
|
||||
candidates from other groups. This informs the design of the statement
|
||||
distribution protocol to have separate phases for in-group and out-group
|
||||
distribution, respectively called "cluster" and "grid" mode (see below).
|
||||
Backing-group quorum (that is, enough backing group votes) must be reached before the block author will consider the
|
||||
candidate. Therefore, validators need to consider _all_ seconded candidates within their own group, because that's what
|
||||
they're assigned to work on. Validators only need to consider _backable_ candidates from other groups. This informs the
|
||||
design of the statement distribution protocol to have separate phases for in-group and out-group distribution,
|
||||
respectively called "cluster" and "grid" mode (see below).
|
||||
|
||||
### With Async Backing
|
||||
|
||||
Asynchronous backing changes the runtime to accept parachain candidates from a
|
||||
certain allowed range of historic relay-parents. These candidates must be backed
|
||||
by the group assigned to the parachain as-of their corresponding relay parents.
|
||||
Asynchronous backing changes the runtime to accept parachain candidates from a certain allowed range of historic
|
||||
relay-parents. These candidates must be backed by the group assigned to the parachain as-of their corresponding relay
|
||||
parents.
|
||||
|
||||
## Protocol
|
||||
|
||||
To address the concern of dealing with large numbers of spam candidates or
|
||||
statements, the overall design approach is to combine a focused "clustering"
|
||||
protocol for legitimate fresh candidates with a broad-distribution "grid"
|
||||
protocol to quickly get backed candidates into the hands of many validators.
|
||||
Validators do not eagerly send each other heavy `CommittedCandidateReceipt`,
|
||||
but instead request these lazily through request/response protocols.
|
||||
To address the concern of dealing with large numbers of spam candidates or statements, the overall design approach is to
|
||||
combine a focused "clustering" protocol for legitimate fresh candidates with a broad-distribution "grid" protocol to
|
||||
quickly get backed candidates into the hands of many validators. Validators do not eagerly send each other heavy
|
||||
`CommittedCandidateReceipt`, but instead request these lazily through request/response protocols.
|
||||
|
||||
A high-level description of the protocol follows:
|
||||
|
||||
### Messages
|
||||
|
||||
Nodes can send each other a few kinds of messages: `Statement`,
|
||||
`BackedCandidateManifest`, `BackedCandidateAcknowledgement`.
|
||||
Nodes can send each other a few kinds of messages: `Statement`, `BackedCandidateManifest`,
|
||||
`BackedCandidateAcknowledgement`.
|
||||
|
||||
- `Statement` messages contain only a signed compact statement, without full
|
||||
candidate info.
|
||||
- `BackedCandidateManifest` messages advertise a description of a backed
|
||||
candidate and stored statements.
|
||||
- `BackedCandidateAcknowledgement` messages acknowledge that a backed candidate
|
||||
is fully known.
|
||||
- `Statement` messages contain only a signed compact statement, without full candidate info.
|
||||
- `BackedCandidateManifest` messages advertise a description of a backed candidate and stored statements.
|
||||
- `BackedCandidateAcknowledgement` messages acknowledge that a backed candidate is fully known.
|
||||
|
||||
### Request/response protocol
|
||||
|
||||
Nodes can request the full `CommittedCandidateReceipt` and
|
||||
`PersistedValidationData`, along with statements, over a request/response
|
||||
protocol. This is the `AttestedCandidateRequest`; the response is
|
||||
`AttestedCandidateResponse`.
|
||||
Nodes can request the full `CommittedCandidateReceipt` and `PersistedValidationData`, along with statements, over a
|
||||
request/response protocol. This is the `AttestedCandidateRequest`; the response is `AttestedCandidateResponse`.
|
||||
|
||||
### Importability and the Hypothetical Frontier
|
||||
|
||||
The **prospective parachains** subsystem maintains prospective "fragment trees"
|
||||
which can be used to determine whether a particular parachain candidate could
|
||||
possibly be included in the future. Candidates which either are within a
|
||||
fragment tree or _would be_ part of a fragment tree if accepted are said to be
|
||||
in the "hypothetical frontier".
|
||||
The **prospective parachains** subsystem maintains prospective "fragment trees" which can be used to determine whether a
|
||||
particular parachain candidate could possibly be included in the future. Candidates which either are within a fragment
|
||||
tree or _would be_ part of a fragment tree if accepted are said to be in the "hypothetical frontier".
|
||||
|
||||
The **statement-distribution** subsystem keeps track of all candidates, and
|
||||
updates its knowledge of the hypothetical frontier based on events such as new
|
||||
relay parents, new confirmed candidates, and newly backed candidates.
|
||||
The **statement-distribution** subsystem keeps track of all candidates, and updates its knowledge of the hypothetical
|
||||
frontier based on events such as new relay parents, new confirmed candidates, and newly backed candidates.
|
||||
|
||||
We only consider statements as "importable" when the corresponding candidate is
|
||||
part of the hypothetical frontier, and only send "importable" statements to the
|
||||
backing subsystem itself.
|
||||
We only consider statements as "importable" when the corresponding candidate is part of the hypothetical frontier, and
|
||||
only send "importable" statements to the backing subsystem itself.
|
||||
|
||||
### Cluster Mode
|
||||
|
||||
- Validator nodes are partitioned into groups (with some exceptions), and
|
||||
validators within a group at a relay-parent can send each other `Statement`
|
||||
messages for any candidates within that group and based on that relay-parent.
|
||||
- Validator nodes are partitioned into groups (with some exceptions), and validators within a group at a relay-parent
|
||||
can send each other `Statement` messages for any candidates within that group and based on that relay-parent.
|
||||
- This is referred to as the "cluster" mode.
|
||||
- Right now these are the same as backing groups, though "cluster"
|
||||
specifically refers to the set of nodes communicating with each other in the
|
||||
first phase of distribution.
|
||||
- Right now these are the same as backing groups, though "cluster" specifically refers to the set of nodes
|
||||
communicating with each other in the first phase of distribution.
|
||||
- `Seconded` statements must be sent before `Valid` statements.
|
||||
- `Seconded` statements may only be sent to other members of the group when the
|
||||
candidate is fully known by the local validator.
|
||||
- "Fully known" means the validator has the full `CommittedCandidateReceipt`
|
||||
and `PersistedValidationData`, which it receives on request from other
|
||||
validators or from a collator.
|
||||
- The reason for this is that sending a statement (which is always a
|
||||
`CompactStatement` carrying nothing but a hash and signature) to the
|
||||
cluster, is also a signal that the sending node is available to request the
|
||||
candidate from.
|
||||
- This makes the protocol easier to reason about, while also reducing network
|
||||
messages about candidates that don't really exist.
|
||||
- Validators in a cluster receiving messages about unknown candidates request
|
||||
the candidate (and statements) from other cluster members which have it.
|
||||
- `Seconded` statements may only be sent to other members of the group when the candidate is fully known by the local
|
||||
validator.
|
||||
- "Fully known" means the validator has the full `CommittedCandidateReceipt` and `PersistedValidationData`, which it
|
||||
receives on request from other validators or from a collator.
|
||||
- The reason for this is that sending a statement (which is always a `CompactStatement` carrying nothing but a hash
|
||||
and signature) to the cluster, is also a signal that the sending node is available to request the candidate from.
|
||||
- This makes the protocol easier to reason about, while also reducing network messages about candidates that don't
|
||||
really exist.
|
||||
- Validators in a cluster receiving messages about unknown candidates request the candidate (and statements) from other
|
||||
cluster members which have it.
|
||||
- Spam considerations
|
||||
- The maximum depth of candidates allowed in asynchronous backing determines
|
||||
the maximum amount of `Seconded` statements originating from a validator V
|
||||
which each validator in a cluster may send to others. This bounds the number
|
||||
of candidates.
|
||||
- There is a small number of validators in each group, which further limits
|
||||
the amount of candidates.
|
||||
- We accept candidates which don't fit in the fragment trees of any relay
|
||||
parents.
|
||||
- "Accept" means "attempt to request and store in memory until useful or
|
||||
expired".
|
||||
- We listen to prospective parachains subsystem to learn of new additions to
|
||||
the fragment trees.
|
||||
- The maximum depth of candidates allowed in asynchronous backing determines the maximum amount of `Seconded`
|
||||
statements originating from a validator V which each validator in a cluster may send to others. This bounds the
|
||||
number of candidates.
|
||||
- There is a small number of validators in each group, which further limits the amount of candidates.
|
||||
- We accept candidates which don't fit in the fragment trees of any relay parents.
|
||||
- "Accept" means "attempt to request and store in memory until useful or expired".
|
||||
- We listen to prospective parachains subsystem to learn of new additions to the fragment trees.
|
||||
- Use this to attempt to import the candidate later.
|
||||
|
||||
### Grid Mode
|
||||
|
||||
- Every consensus session provides randomness and a fixed validator set, which
|
||||
is used to build a redundant grid topology.
|
||||
- It's redundant in the sense that there are 2 paths from every node to every
|
||||
other node. See "Grid Topology" section for more details.
|
||||
- This grid topology is used to create a sending path from each validator group
|
||||
to every validator.
|
||||
- When a node observes a candidate as backed, it sends a
|
||||
`BackedCandidateManifest` to their "receiving" nodes.
|
||||
- Every consensus session provides randomness and a fixed validator set, which is used to build a redundant grid
|
||||
topology.
|
||||
- It's redundant in the sense that there are 2 paths from every node to every other node. See "Grid Topology" section
|
||||
for more details.
|
||||
- This grid topology is used to create a sending path from each validator group to every validator.
|
||||
- When a node observes a candidate as backed, it sends a `BackedCandidateManifest` to their "receiving" nodes.
|
||||
- If receiving nodes don't yet know the candidate, they request it.
|
||||
- Once they know the candidate, they respond with a
|
||||
`BackedCandidateAcknowledgement`.
|
||||
- Once two nodes perform a manifest/acknowledgement exchange, they can send
|
||||
`Statement` messages directly to each other for any new statements they might
|
||||
need.
|
||||
- This limits the amount of statements we'd have to deal with w.r.t.
|
||||
candidates that don't really exist. See "Manifest Exchange" section.
|
||||
- There are limitations on the number of candidates that can be advertised by
|
||||
each peer, similar to those in the cluster. Validators do not request
|
||||
candidates which exceed these limitations.
|
||||
- Validators request candidates as soon as they are advertised, but do not
|
||||
import the statements until the candidate is part of the hypothetical
|
||||
frontier, and do not re-advertise or acknowledge until the candidate is
|
||||
considered both backable and part of the hypothetical frontier.
|
||||
- Note that requesting is not an implicit acknowledgement, and an explicit
|
||||
acknowledgement must be sent upon receipt.
|
||||
- Once they know the candidate, they respond with a `BackedCandidateAcknowledgement`.
|
||||
- Once two nodes perform a manifest/acknowledgement exchange, they can send `Statement` messages directly to each other
|
||||
for any new statements they might need.
|
||||
- This limits the amount of statements we'd have to deal with w.r.t. candidates that don't really exist. See "Manifest
|
||||
Exchange" section.
|
||||
- There are limitations on the number of candidates that can be advertised by each peer, similar to those in the
|
||||
cluster. Validators do not request candidates which exceed these limitations.
|
||||
- Validators request candidates as soon as they are advertised, but do not import the statements until the candidate is
|
||||
part of the hypothetical frontier, and do not re-advertise or acknowledge until the candidate is considered both
|
||||
backable and part of the hypothetical frontier.
|
||||
- Note that requesting is not an implicit acknowledgement, and an explicit acknowledgement must be sent upon receipt.
|
||||
|
||||
## Messages
|
||||
|
||||
@@ -161,27 +130,23 @@ backing subsystem itself.
|
||||
- `ActiveLeaves`
|
||||
- Notification of a change in the set of active leaves.
|
||||
- `StatementDistributionMessage::Share`
|
||||
- Notification of a locally-originating statement. That is, this statement
|
||||
comes from our node and should be distributed to other nodes.
|
||||
- Sent by the Backing Subsystem after it successfully imports a
|
||||
locally-originating statement.
|
||||
- Notification of a locally-originating statement. That is, this statement comes from our node and should be
|
||||
distributed to other nodes.
|
||||
- Sent by the Backing Subsystem after it successfully imports a locally-originating statement.
|
||||
- `StatementDistributionMessage::Backed`
|
||||
- Notification of a candidate being backed (received enough validity votes
|
||||
from the backing group).
|
||||
- Sent by the Backing Subsystem after it successfully imports a statement for
|
||||
the first time and after sending ~Share~.
|
||||
- Notification of a candidate being backed (received enough validity votes from the backing group).
|
||||
- Sent by the Backing Subsystem after it successfully imports a statement for the first time and after sending
|
||||
~Share~.
|
||||
- `StatementDistributionMessage::NetworkBridgeUpdate`
|
||||
- See next section.
|
||||
|
||||
#### Network bridge events
|
||||
|
||||
- v1 compatibility
|
||||
- Messages for the v1 protocol are routed to the legacy statement
|
||||
distribution.
|
||||
- Messages for the v1 protocol are routed to the legacy statement distribution.
|
||||
- `Statement`
|
||||
- Notification of a signed statement.
|
||||
- Sent by a peer's Statement Distribution subsystem when circulating
|
||||
statements.
|
||||
- Sent by a peer's Statement Distribution subsystem when circulating statements.
|
||||
- `BackedCandidateManifest`
|
||||
- Notification of a backed candidate being known by the sending node.
|
||||
- For the candidate being requested by the receiving node if needed.
|
||||
@@ -196,26 +161,23 @@ backing subsystem itself.
|
||||
### Outgoing
|
||||
|
||||
- `NetworkBridgeTxMessage::SendValidationMessages`
|
||||
- Sends a peer all pending messages / acknowledgements / statements for a
|
||||
relay parent, either through the cluster or the grid.
|
||||
- Sends a peer all pending messages / acknowledgements / statements for a relay parent, either through the cluster or
|
||||
the grid.
|
||||
- `NetworkBridgeTxMessage::SendValidationMessage`
|
||||
- Circulates a compact statement to all peers who need it, either through the
|
||||
cluster or the grid.
|
||||
- Circulates a compact statement to all peers who need it, either through the cluster or the grid.
|
||||
- `NetworkBridgeTxMessage::ReportPeer`
|
||||
- Reports a peer (either good or bad).
|
||||
- `CandidateBackingMessage::Statement`
|
||||
- Note a validator's statement about a particular candidate.
|
||||
- `ProspectiveParachainsMessage::GetHypotheticalFrontier`
|
||||
- Gets the hypothetical frontier membership of candidates under active leaves'
|
||||
fragment trees.
|
||||
- Gets the hypothetical frontier membership of candidates under active leaves' fragment trees.
|
||||
- `NetworkBridgeTxMessage::SendRequests`
|
||||
- Sends requests, initiating the request/response protocol.
|
||||
|
||||
## Request/Response
|
||||
|
||||
We also have a request/response protocol because validators do not eagerly send
|
||||
each other heavy `CommittedCandidateReceipt`, but instead need to request these
|
||||
lazily.
|
||||
We also have a request/response protocol because validators do not eagerly send each other heavy
|
||||
`CommittedCandidateReceipt`, but instead need to request these lazily.
|
||||
|
||||
### Protocol
|
||||
|
||||
@@ -225,16 +187,13 @@ lazily.
|
||||
- Done as needed, when handling incoming manifests/statements.
|
||||
- `RequestManager::dispatch_requests` sends any queued-up requests.
|
||||
- Calls `RequestManager::next_request` to completion.
|
||||
- Creates the `OutgoingRequest`, saves the receiver in
|
||||
`RequestManager::pending_responses`.
|
||||
- Does nothing if we have more responses pending than the limit of parallel
|
||||
requests.
|
||||
- Creates the `OutgoingRequest`, saves the receiver in `RequestManager::pending_responses`.
|
||||
- Does nothing if we have more responses pending than the limit of parallel requests.
|
||||
|
||||
2. Peer
|
||||
|
||||
- Requests come in on a peer on the `IncomingRequestReceiver`.
|
||||
- Runs in a background responder task which feeds requests to `answer_request`
|
||||
through `MuxedMessage`.
|
||||
- Runs in a background responder task which feeds requests to `answer_request` through `MuxedMessage`.
|
||||
- This responder task has a limit on the number of parallel requests.
|
||||
- `answer_request` on the peer takes the request and sends a response.
|
||||
- Does this using the response sender on the request.
|
||||
@@ -243,8 +202,7 @@ lazily.
|
||||
|
||||
- `receive_response` on the original validator yields a response.
|
||||
- Response was sent on the request's response sender.
|
||||
- Uses `RequestManager::await_incoming` to await on pending responses in an
|
||||
unordered fashion.
|
||||
- Uses `RequestManager::await_incoming` to await on pending responses in an unordered fashion.
|
||||
- Runs on the `MuxedMessage` receiver.
|
||||
- `handle_response` handles the response.
|
||||
|
||||
@@ -265,25 +223,23 @@ lazily.
|
||||
|
||||
## Manifests
|
||||
|
||||
A manifest is a message about a known backed candidate, along with a description
|
||||
of the statements backing it. It can be one of two kinds:
|
||||
A manifest is a message about a known backed candidate, along with a description of the statements backing it. It can be
|
||||
one of two kinds:
|
||||
|
||||
- `Full`: Contains information about the candidate and should be sent to peers
|
||||
who may not have the candidate yet. This is also called an `Announcement`.
|
||||
- `Acknowledgement`: Omits information implicit in the candidate, and should be
|
||||
sent to peers which are guaranteed to have the candidate already.
|
||||
- `Full`: Contains information about the candidate and should be sent to peers who may not have the candidate yet. This
|
||||
is also called an `Announcement`.
|
||||
- `Acknowledgement`: Omits information implicit in the candidate, and should be sent to peers which are guaranteed to
|
||||
have the candidate already.
|
||||
|
||||
### Manifest Exchange
|
||||
|
||||
Manifest exchange is when a receiving node received a `Full` manifest and
|
||||
replied with an `Acknowledgement`. It indicates that both nodes know the
|
||||
candidate as valid and backed. This allows the nodes to send `Statement`
|
||||
messages directly to each other for any new statements.
|
||||
Manifest exchange is when a receiving node received a `Full` manifest and replied with an `Acknowledgement`. It
|
||||
indicates that both nodes know the candidate as valid and backed. This allows the nodes to send `Statement` messages
|
||||
directly to each other for any new statements.
|
||||
|
||||
Why? This limits the amount of statements we'd have to deal with w.r.t.
|
||||
candidates that don't really exist. Limiting out-of-group statement distribution
|
||||
between peers to only candidates that both peers agree are backed and exist
|
||||
ensures we only have to store statements about real candidates.
|
||||
Why? This limits the amount of statements we'd have to deal with w.r.t. candidates that don't really exist. Limiting
|
||||
out-of-group statement distribution between peers to only candidates that both peers agree are backed and exist ensures
|
||||
we only have to store statements about real candidates.
|
||||
|
||||
In practice, manifest exchange means that one of three things have happened:
|
||||
|
||||
@@ -291,36 +247,31 @@ In practice, manifest exchange means that one of three things have happened:
|
||||
- We announced, they acknowledged.
|
||||
- We announced, they announced.
|
||||
|
||||
Concerning the last case, note that it is possible for two nodes to have each
|
||||
other in their sending set. Consider:
|
||||
Concerning the last case, note that it is possible for two nodes to have each other in their sending set. Consider:
|
||||
|
||||
```
|
||||
1 2
|
||||
3 4
|
||||
```
|
||||
|
||||
If validators 2 and 4 are in group B, then there is a path `2->1->3` and
|
||||
`4->3->1`. Therefore, 1 and 3 might send each other manifests for the same
|
||||
candidate at the same time, without having seen the other's yet. This also
|
||||
counts as a manifest exchange, but is only allowed to occur in this way.
|
||||
If validators 2 and 4 are in group B, then there is a path `2->1->3` and `4->3->1`. Therefore, 1 and 3 might send each
|
||||
other manifests for the same candidate at the same time, without having seen the other's yet. This also counts as a
|
||||
manifest exchange, but is only allowed to occur in this way.
|
||||
|
||||
After the exchange is complete, we update pending statements. Pending statements
|
||||
are those we know locally that the remote node does not.
|
||||
After the exchange is complete, we update pending statements. Pending statements are those we know locally that the
|
||||
remote node does not.
|
||||
|
||||
#### Alternative Paths Through The Topology
|
||||
|
||||
Nodes should send a `BackedCandidateAcknowledgement(CandidateHash,
|
||||
StatementFilter)` notification to any peer which has sent a manifest, and the
|
||||
candidate has been acquired by other means. This keeps alternative paths through
|
||||
the topology open, which allows nodes to receive additional statements that come
|
||||
later, but not after the candidate has been posted on-chain.
|
||||
Nodes should send a `BackedCandidateAcknowledgement(CandidateHash, StatementFilter)` notification to any peer which has
|
||||
sent a manifest, and the candidate has been acquired by other means. This keeps alternative paths through the topology
|
||||
open, which allows nodes to receive additional statements that come later, but not after the candidate has been posted
|
||||
on-chain.
|
||||
|
||||
This is mostly about the limitation that the runtime has no way for block
|
||||
authors to post statements that come after the parablock is posted on-chain and
|
||||
ensure those validators still get rewarded. Technically, we only need enough
|
||||
statements to back the candidate and the manifest + request will provide that.
|
||||
But more statements might come shortly afterwards, and we want those to end up
|
||||
on-chain as well to ensure all validators in the group are rewarded.
|
||||
This is mostly about the limitation that the runtime has no way for block authors to post statements that come after the
|
||||
parablock is posted on-chain and ensure those validators still get rewarded. Technically, we only need enough statements
|
||||
to back the candidate and the manifest + request will provide that. But more statements might come shortly afterwards,
|
||||
and we want those to end up on-chain as well to ensure all validators in the group are rewarded.
|
||||
|
||||
For clarity, here is the full timeline:
|
||||
|
||||
@@ -333,52 +284,42 @@ For clarity, here is the full timeline:
|
||||
|
||||
## Cluster Module
|
||||
|
||||
The cluster module provides direct distribution of unbacked candidates within a
|
||||
group. By utilizing this initial phase of propagating only within
|
||||
clusters/groups, we bound the number of `Seconded` messages per validator per
|
||||
relay-parent, helping us prevent spam. Validators can try to circumvent this,
|
||||
but they would only consume a few KB of memory and it is trivially slashable on
|
||||
chain.
|
||||
The cluster module provides direct distribution of unbacked candidates within a group. By utilizing this initial phase
|
||||
of propagating only within clusters/groups, we bound the number of `Seconded` messages per validator per relay-parent,
|
||||
helping us prevent spam. Validators can try to circumvent this, but they would only consume a few KB of memory and it is
|
||||
trivially slashable on chain.
|
||||
|
||||
The cluster module determines whether to accept/reject messages from other
|
||||
validators in the same group. It keeps track of what we have sent to other
|
||||
validators in the group, and pending statements. For the full protocol, see
|
||||
"Protocol".
|
||||
The cluster module determines whether to accept/reject messages from other validators in the same group. It keeps track
|
||||
of what we have sent to other validators in the group, and pending statements. For the full protocol, see "Protocol".
|
||||
|
||||
## Grid Module
|
||||
|
||||
The grid module provides distribution of backed candidates and late statements
|
||||
outside the backing group. For the full protocol, see the "Protocol" section.
|
||||
The grid module provides distribution of backed candidates and late statements outside the backing group. For the full
|
||||
protocol, see the "Protocol" section.
|
||||
|
||||
### Grid Topology
|
||||
|
||||
For distributing outside our cluster (aka backing group) we use a 2D grid
|
||||
topology. This limits the amount of peers we send messages to, and handles
|
||||
view updates.
|
||||
For distributing outside our cluster (aka backing group) we use a 2D grid topology. This limits the amount of peers we
|
||||
send messages to, and handles view updates.
|
||||
|
||||
The basic operation of the grid topology is that:
|
||||
|
||||
- A validator producing a message sends it to its row-neighbors and its
|
||||
column-neighbors.
|
||||
- A validator receiving a message originating from one of its row-neighbors
|
||||
sends it to its column-neighbors.
|
||||
- A validator receiving a message originating from one of its column-neighbors
|
||||
sends it to its row-neighbors.
|
||||
- A validator producing a message sends it to its row-neighbors and its column-neighbors.
|
||||
- A validator receiving a message originating from one of its row-neighbors sends it to its column-neighbors.
|
||||
- A validator receiving a message originating from one of its column-neighbors sends it to its row-neighbors.
|
||||
|
||||
This grid approach defines 2 unique paths for every validator to reach every
|
||||
other validator in at most 2 hops, providing redundancy.
|
||||
This grid approach defines 2 unique paths for every validator to reach every other validator in at most 2 hops,
|
||||
providing redundancy.
|
||||
|
||||
Propagation follows these rules:
|
||||
|
||||
- Each node has a receiving set and a sending set. These are different for each
|
||||
group. That is, if a node receives a candidate from group A, it checks if it
|
||||
is allowed to receive from that node for candidates from group A.
|
||||
- Each node has a receiving set and a sending set. These are different for each group. That is, if a node receives a
|
||||
candidate from group A, it checks if it is allowed to receive from that node for candidates from group A.
|
||||
- For groups that we are in, receive from nobody and send to our X/Y peers.
|
||||
- For groups that we are not part of:
|
||||
- We receive from any validator in the group we share a slice with and send to
|
||||
the corresponding X/Y slice in the other dimension.
|
||||
- For any validators we don't share a slice with, we receive from the nodes
|
||||
which share a slice with them.
|
||||
- We receive from any validator in the group we share a slice with and send to the corresponding X/Y slice in the
|
||||
other dimension.
|
||||
- For any validators we don't share a slice with, we receive from the nodes which share a slice with them.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -391,81 +332,63 @@ For size 11, the matrix would be:
|
||||
9 10
|
||||
```
|
||||
|
||||
e.g. for index 10, the neighbors would be 1, 4, 7, 9 -- these are the nodes we
|
||||
could directly communicate with (e.g. either send to or receive from).
|
||||
e.g. for index 10, the neighbors would be 1, 4, 7, 9 -- these are the nodes we could directly communicate with (e.g.
|
||||
either send to or receive from).
|
||||
|
||||
Now, which of these neighbors can 10 receive from? Recall that the
|
||||
sending/receiving sets for 10 would be different for different groups. Here are
|
||||
some hypothetical scenarios:
|
||||
Now, which of these neighbors can 10 receive from? Recall that the sending/receiving sets for 10 would be different for
|
||||
different groups. Here are some hypothetical scenarios:
|
||||
|
||||
- **Scenario 1:** 9 belongs to group A but not 10. Here, 10 can directly receive
|
||||
candidates from group A from 9. 10 would propagate them to the nodes in {1, 4,
|
||||
7} that are not in A.
|
||||
- **Scenario 2:** 6 is in group A instead of 9, and 7 is not in group A. 10 can
|
||||
receive group A messages from 7 or 9. 10 will try to relay these messages, but
|
||||
7 and 9 together should have already propagated the message to all x/y
|
||||
peers of 10. If so, then 10 will just receive acknowledgements in reply rather
|
||||
than requests.
|
||||
- **Scenario 3:** 10 itself is in group A. 10 would not receive candidates from
|
||||
this group from any other nodes through the grid. It would itself send such
|
||||
candidates to all its neighbors that are not in A.
|
||||
- **Scenario 1:** 9 belongs to group A but not 10. Here, 10 can directly receive candidates from group A from 9. 10
|
||||
would propagate them to the nodes in {1, 4, 7} that are not in A.
|
||||
- **Scenario 2:** 6 is in group A instead of 9, and 7 is not in group A. 10 can receive group A messages from 7 or 9. 10
|
||||
will try to relay these messages, but 7 and 9 together should have already propagated the message to all x/y peers of
|
||||
10. If so, then 10 will just receive acknowledgements in reply rather than requests.
|
||||
- **Scenario 3:** 10 itself is in group A. 10 would not receive candidates from this group from any other nodes through
|
||||
the grid. It would itself send such candidates to all its neighbors that are not in A.
|
||||
|
||||
### Seconding Limit
|
||||
|
||||
The seconding limit is a per-validator limit. Before asynchronous backing, we
|
||||
had a rule that every validator was only allowed to second one candidate per
|
||||
relay parent. With asynchronous backing, we have a 'maximum depth' which makes
|
||||
it possible to second multiple candidates per relay parent. The seconding limit
|
||||
is set to `max depth + 1` to set an upper bound on candidates entering the
|
||||
system.
|
||||
The seconding limit is a per-validator limit. Before asynchronous backing, we had a rule that every validator was only
|
||||
allowed to second one candidate per relay parent. With asynchronous backing, we have a 'maximum depth' which makes it
|
||||
possible to second multiple candidates per relay parent. The seconding limit is set to `max depth + 1` to set an upper
|
||||
bound on candidates entering the system.
|
||||
|
||||
## Candidates Module
|
||||
|
||||
The candidates module provides a tracker for all known candidates in the view,
|
||||
whether they are confirmed or not, and how peers have advertised the candidates.
|
||||
What is a confirmed candidate? It is a candidate for which we have the full
|
||||
receipt and the persisted validation data. This module gets confirmed candidates
|
||||
from two sources:
|
||||
The candidates module provides a tracker for all known candidates in the view, whether they are confirmed or not, and
|
||||
how peers have advertised the candidates. What is a confirmed candidate? It is a candidate for which we have the full
|
||||
receipt and the persisted validation data. This module gets confirmed candidates from two sources:
|
||||
|
||||
- It can be that a validator fetched a collation directly from the collator and
|
||||
validated it.
|
||||
- The first time a validator gets an announcement for an unknown candidate, it
|
||||
will send a request for the candidate. Upon receiving a response and
|
||||
validating it (see `UnhandledResponse::validate_response`), it will mark the
|
||||
candidate as confirmed.
|
||||
- It can be that a validator fetched a collation directly from the collator and validated it.
|
||||
- The first time a validator gets an announcement for an unknown candidate, it will send a request for the candidate.
|
||||
Upon receiving a response and validating it (see `UnhandledResponse::validate_response`), it will mark the candidate
|
||||
as confirmed.
|
||||
|
||||
## Requests Module
|
||||
|
||||
The requests module provides a manager for pending requests for candidate data,
|
||||
as well as pending responses. See "Request/Response Protocol" for a high-level
|
||||
description of the flow. See module-docs for full details.
|
||||
The requests module provides a manager for pending requests for candidate data, as well as pending responses. See
|
||||
"Request/Response Protocol" for a high-level description of the flow. See module-docs for full details.
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Acknowledgement:** A partial manifest sent to a validator that already has the
|
||||
candidate to inform them that the sending node also knows the candidate.
|
||||
Concludes a manifest exchange.
|
||||
- **Announcement:** A full manifest indicating that a backed candidate is known by
|
||||
the sending node. Initiates a manifest exchange.
|
||||
- **Acknowledgement:** A partial manifest sent to a validator that already has the candidate to inform them that the
|
||||
sending node also knows the candidate. Concludes a manifest exchange.
|
||||
- **Announcement:** A full manifest indicating that a backed candidate is known by the sending node. Initiates a
|
||||
manifest exchange.
|
||||
- **Attestation:** See "Statement".
|
||||
- **Backable vs. Backed:**
|
||||
- Note that we sometimes use "backed" to refer to candidates that are
|
||||
"backable", but not yet backed on chain.
|
||||
- **Backed** should technically mean that the parablock candidate and its
|
||||
backing statements have been added to a relay chain block.
|
||||
- **Backable** is when the necessary backing statements have been acquired but
|
||||
those statements and the parablock candidate haven't been backed in a relay
|
||||
chain block yet.
|
||||
- **Fragment tree:** A parachain fragment not referenced by the relay-chain.
|
||||
It is a tree of prospective parachain blocks.
|
||||
- **Manifest:** A message about a known backed candidate, along with a
|
||||
description of the statements backing it. There are two kinds of manifest,
|
||||
`Acknowledgement` and `Announcement`. See "Manifests" section.
|
||||
- Note that we sometimes use "backed" to refer to candidates that are "backable", but not yet backed on chain.
|
||||
- **Backed** should technically mean that the parablock candidate and its backing statements have been added to a
|
||||
relay chain block.
|
||||
- **Backable** is when the necessary backing statements have been acquired but those statements and the parablock
|
||||
candidate haven't been backed in a relay chain block yet.
|
||||
- **Fragment tree:** A parachain fragment not referenced by the relay-chain. It is a tree of prospective parachain
|
||||
blocks.
|
||||
- **Manifest:** A message about a known backed candidate, along with a description of the statements backing it. There
|
||||
are two kinds of manifest, `Acknowledgement` and `Announcement`. See "Manifests" section.
|
||||
- **Peer:** Another validator that a validator is connected to.
|
||||
- **Request/response:** A protocol used to lazily request and receive heavy
|
||||
candidate data when needed.
|
||||
- **Reputation:** Tracks reputation of peers. Applies annoyance cost and good
|
||||
behavior benefits.
|
||||
- **Request/response:** A protocol used to lazily request and receive heavy candidate data when needed.
|
||||
- **Reputation:** Tracks reputation of peers. Applies annoyance cost and good behavior benefits.
|
||||
- **Statement:** Signed statements that can be made about parachain candidates.
|
||||
- **Seconded:** Proposal of a parachain candidate. Implicit validity vote.
|
||||
- **Valid:** States that a parachain candidate is valid.
|
||||
@@ -474,6 +397,5 @@ description of the flow. See module-docs for full details.
|
||||
- **Explicit view** / **immediate view**
|
||||
- The view a peer has of the relay chain heads and highest finalized block.
|
||||
- **Implicit view**
|
||||
- Derived from the immediate view. Composed of active leaves and minimum
|
||||
relay-parents allowed for candidates of various parachains at those
|
||||
leaves.
|
||||
- Derived from the immediate view. Composed of active leaves and minimum relay-parents allowed for candidates of
|
||||
various parachains at those leaves.
|
||||
|
||||
Reference in New Issue
Block a user