feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +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.
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,189 @@
|
||||
# Candidate Backing
|
||||
|
||||
> NOTE: This module has suffered changes for the elastic scaling implementation. As a result, parts of this document may
|
||||
be out of date and will be updated at a later time. Issue tracking the update:
|
||||
https://github.com/pezkuwichain/pezkuwi-sdk/issues/132
|
||||
|
||||
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 Pezkuwi 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.
|
||||
|
||||
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.
|
||||
|
||||
## Protocol
|
||||
|
||||
Input: [`CandidateBackingMessage`][CBM]
|
||||
|
||||
Output:
|
||||
|
||||
* [`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.
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
* 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.
|
||||
|
||||
### 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 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_ teyrchain candidates validated by _current_ validators.
|
||||
> * Allow inclusion of _old_ teyrchain candidates validated by _current_ validators.
|
||||
> * Allow inclusion of _old_ teyrchain 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
|
||||
[`Statement`s][STMT] by validators. If a candidate receives a majority of supporting Statements from the Teyrchain
|
||||
Validators currently assigned, then that candidate is considered backable.
|
||||
|
||||
### On Startup
|
||||
|
||||
* Fetch current validator set, validator -> teyrchain 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 teyrchain head and validation function from the [`Runtime API`][RA] for the
|
||||
teyrchain 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
|
||||
|
||||
```rust
|
||||
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, teyrchain 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.teyrchain_id == our_assignment {
|
||||
spawn_validation_work(candidate, teyrchain head, validation function)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
chain.
|
||||
|
||||
### Validating Candidates
|
||||
|
||||
```rust
|
||||
fn spawn_validation_work(candidate, teyrchain 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`][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.
|
||||
|
||||
### Distribute Signed Statement
|
||||
|
||||
Dispatch a [`StatementDistributionMessage`][SDM]`::Share(relay_parent, SignedFullStatementWithPVD)`.
|
||||
|
||||
[OverseerSignal]: ../../types/overseer-protocol.md#overseer-signal
|
||||
[Statement]: ../../types/backing.md#statement-type
|
||||
[STMT]: ../../types/backing.md#statement-type
|
||||
[CPM]: ../../types/overseer-protocol.md#collator-protocol-message
|
||||
[RAM]: ../../types/overseer-protocol.md#runtime-api-message
|
||||
[CVM]: ../../types/overseer-protocol.md#validation-request-type
|
||||
[PM]: ../../types/overseer-protocol.md#provisioner-message
|
||||
[CBM]: ../../types/overseer-protocol.md#candidate-backing-message
|
||||
[ADM]: ../../types/overseer-protocol.md#availability-distribution-message
|
||||
[SDM]: ../../types/overseer-protocol.md#statement-distribution-message
|
||||
[DCM]: ../../types/overseer-protocol.md#dispute-coordinator-message
|
||||
|
||||
[CP]: ../collators/collator-protocol.md
|
||||
[CV]: ../utility/candidate-validation.md
|
||||
[SD]: statement-distribution.md
|
||||
[RA]: ../utility/runtime-api.md
|
||||
[PV]: ../utility/provisioner.md
|
||||
@@ -0,0 +1 @@
|
||||
# PoV Distribution
|
||||
@@ -0,0 +1,162 @@
|
||||
# Prospective Teyrchains
|
||||
|
||||
> NOTE: This module has suffered changes for the elastic scaling implementation. As a result, parts of this document may
|
||||
be out of date and will be updated at a later time. Issue tracking the update:
|
||||
https://github.com/pezkuwichain/pezkuwi-sdk/issues/132
|
||||
|
||||
## Overview
|
||||
|
||||
**Purpose:** Tracks and handles prospective teyrchain fragments and informs
|
||||
other backing-stage subsystems of work to be done.
|
||||
|
||||
"prospective":
|
||||
- [*prə'spɛktɪv*] adj.
|
||||
- future, likely, potential
|
||||
|
||||
Asynchronous backing changes the runtime to accept teyrchain candidates from a
|
||||
certain allowed range of historic relay-parents. This means we can now build
|
||||
*prospective teyrchains* – that is, trees of potential (but likely) future
|
||||
teyrchain blocks. This is the subsystem responsible for doing so.
|
||||
|
||||
Other subsystems such as Backing rely on Prospective Teyrchains, e.g. for
|
||||
determining if a candidate can be seconded. This subsystem is the main
|
||||
coordinator of work within the node for the collation and backing phases of
|
||||
teyrchain consensus.
|
||||
|
||||
Prospective Teyrchains is primarily an implementation of fragment trees. It also
|
||||
handles concerns such as:
|
||||
|
||||
- the relay-chain being forkful
|
||||
- session changes
|
||||
|
||||
See the following sections for more details.
|
||||
|
||||
### Fragment Trees
|
||||
|
||||
This subsystem builds up fragment trees, which are trees of prospective para
|
||||
candidates. Each path through the tree represents a possible state transition
|
||||
path for the para. Each potential candidate is a fragment, or a node, in the
|
||||
tree. Candidates are validated against constraints as they are added.
|
||||
|
||||
This subsystem builds up trees for each relay-chain block in the view, for each
|
||||
para. These fragment trees are used for:
|
||||
|
||||
- providing backable candidates to other subsystems
|
||||
- sanity-checking that candidates can be seconded
|
||||
- getting seconded candidates under active leaves
|
||||
- etc.
|
||||
|
||||
For example, here is a tree with several possible paths:
|
||||
|
||||
```
|
||||
Para Head registered by the relay chain: included_head
|
||||
↲ ↳
|
||||
depth 0: head_0_a head_0_b
|
||||
↲ ↳
|
||||
depth 1: head_1_a head_1_b
|
||||
↲ | ↳
|
||||
depth 2: head_2_a1 head_2_a2 head_2_a3
|
||||
```
|
||||
|
||||
### The Relay-Chain Being Forkful
|
||||
|
||||
We account for the same candidate possibly appearing in different forks. While
|
||||
we still build fragment trees for each head in each fork, we are efficient with
|
||||
how we reference candidates to save space.
|
||||
|
||||
### Session Changes
|
||||
|
||||
Allowed ancestry doesn't cross session boundary. That is, you can only build on
|
||||
top of the freshest relay parent when the session starts. This is a current
|
||||
limitation that may be lifted in the future.
|
||||
|
||||
Also, runtime configuration values needed for constraints (such as
|
||||
`max_pov_size`) are constant within a session. This is important when building
|
||||
prospective validation data. This is unlikely to change.
|
||||
|
||||
## Messages
|
||||
|
||||
### Incoming
|
||||
|
||||
- `ActiveLeaves`
|
||||
- Notification of a change in the set of active leaves.
|
||||
- Constructs fragment trees for each para for each new leaf.
|
||||
- `ProspectiveTeyrchainsMessage::IntroduceCandidate`
|
||||
- Informs the subsystem of a new candidate.
|
||||
- Sent by the Backing Subsystem when it is importing a statement for a
|
||||
new candidate.
|
||||
- `ProspectiveTeyrchainsMessage::CandidateSeconded`
|
||||
- Informs the subsystem that a previously introduced candidate has
|
||||
been seconded.
|
||||
- Sent by the Backing Subsystem when it is importing a statement for a
|
||||
new candidate after it sends `IntroduceCandidate`, if that wasn't
|
||||
rejected by Prospective Teyrchains.
|
||||
- `ProspectiveTeyrchainsMessage::CandidateBacked`
|
||||
- Informs the subsystem that a previously introduced candidate has
|
||||
been backed.
|
||||
- Sent by the Backing Subsystem after it successfully imports a
|
||||
statement giving a candidate the necessary quorum of backing votes.
|
||||
- `ProspectiveTeyrchainsMessage::GetBackableCandidates`
|
||||
- Get the requested number of backable candidate hashes along with their relay parent for a given
|
||||
teyrchain,under a given relay-parent (leaf) hash, which are descendants of given candidate
|
||||
hashes.
|
||||
- Sent by the Provisioner when requesting backable candidates, when
|
||||
selecting candidates for a given relay-parent.
|
||||
- `ProspectiveTeyrchainsMessage::GetHypotheticalMembership`
|
||||
- Gets the hypothetical frontier membership of candidates with the
|
||||
given properties under the specified active leaves' fragment trees.
|
||||
- Sent by the Backing Subsystem when sanity-checking whether a candidate can
|
||||
be seconded based on its hypothetical frontiers.
|
||||
- `ProspectiveTeyrchainsMessage::GetMinimumRelayParents`
|
||||
- Gets the minimum accepted relay-parent number for each para in the
|
||||
fragment tree for the given relay-chain block hash.
|
||||
- That is, this returns the minimum relay-parent block number in the
|
||||
same branch of the relay-chain which is accepted in the fragment
|
||||
tree for each para-id.
|
||||
- Sent by the Backing, Statement Distribution, and Collator Protocol
|
||||
subsystems when activating leaves in the implicit view.
|
||||
- `ProspectiveTeyrchainsMessage::GetProspectiveValidationData`
|
||||
- Gets the validation data of some prospective candidate. The
|
||||
candidate doesn't need to be part of any fragment tree.
|
||||
- Sent by the Collator Protocol subsystem (validator side) when
|
||||
handling a fetched collation result.
|
||||
|
||||
### Outgoing
|
||||
|
||||
- `RuntimeApiRequest::ParaBackingState`
|
||||
- Gets the backing state of the given para (the constraints of the para and
|
||||
candidates pending availability).
|
||||
- `RuntimeApiRequest::BackingConstraints`
|
||||
- Gets the constraints on the actions that can be taken by a new teyrchain
|
||||
block.
|
||||
- `RuntimeApiRequest::AvailabilityCores`
|
||||
- Gets information on all availability cores.
|
||||
- `ChainApiMessage::Ancestors`
|
||||
- Requests the `k` ancestor block hashes of a block with the given
|
||||
hash.
|
||||
- `ChainApiMessage::BlockHeader`
|
||||
- Requests the block header by hash.
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Candidate storage:** Stores candidates and information about them
|
||||
such as their relay-parents and their backing states. Is indexed in
|
||||
various ways.
|
||||
- **Constraints:**
|
||||
- Constraints on the actions that can be taken by a new teyrchain
|
||||
block.
|
||||
- Exhaustively define the set of valid inputs and outputs to teyrchain
|
||||
execution.
|
||||
- **Fragment:** A prospective para block (that is, a block not yet referenced by
|
||||
the relay-chain). Fragments are anchored to the relay-chain at a particular
|
||||
relay-parent.
|
||||
- **Fragment tree:**
|
||||
- A tree of fragments. Together, these fragments define one or more
|
||||
prospective paths a teyrchain's state may transition through.
|
||||
- See the "Fragment Tree" section.
|
||||
- **Inclusion emulation:** Emulation of the logic that the runtime uses
|
||||
for checking teyrchain blocks.
|
||||
- **Relay-parent:** A particular relay-chain block that a fragment is
|
||||
anchored to.
|
||||
- **Scope:** The scope of a fragment tree, defining limits on nodes
|
||||
within the tree.
|
||||
@@ -0,0 +1,412 @@
|
||||
# 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 redundancy to
|
||||
ensure a fast backing process.
|
||||
|
||||
## Overview
|
||||
|
||||
**Goal:** every well-connected node is aware of every next potential teyrchain block.
|
||||
|
||||
Validators can either:
|
||||
|
||||
- receive teyrchain block from collator, check block, and gossip statement.
|
||||
- receive statements from other validators, check the teyrchain 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 teyrchain
|
||||
candidates + backing statements that could be placed on-chain in the next block.
|
||||
|
||||
[This blog post](https://pezkuwichain.io/blog/polkadot-v1-0-sharding-and-economic-security) puts it another way:
|
||||
"Validators who aren't assigned to the teyrchain still listen for the attestations [statements] because whichever
|
||||
validator ends up being the author of the relay-chain block needs to bundle up attested teyrchain blocks for several
|
||||
teyrchains 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).
|
||||
|
||||
### With Async Backing
|
||||
|
||||
Asynchronous backing changes the runtime to accept teyrchain candidates from a certain allowed range of historic
|
||||
relay-parents. These candidates must be backed by the group assigned to the teyrchain 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.
|
||||
|
||||
A high-level description of the protocol follows:
|
||||
|
||||
### Messages
|
||||
|
||||
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.
|
||||
|
||||
### 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`.
|
||||
|
||||
### Importability and the Hypothetical Frontier
|
||||
|
||||
The **prospective teyrchains** subsystem maintains prospective "fragment trees" which can be used to determine whether a
|
||||
particular teyrchain 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.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
- `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.
|
||||
- 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 teyrchains 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.
|
||||
- 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.
|
||||
|
||||
### Disabled validators
|
||||
|
||||
After a validator is disabled in the runtime, other validators should no longer
|
||||
accept statements from it. Filtering out of statements from disabled validators
|
||||
on the node side is purely an optimization, as it will be done in the runtime
|
||||
as well.
|
||||
|
||||
We use the state of the relay parent to check whether a validator is disabled
|
||||
to avoid race conditions and ensure that disabling works well in the presence
|
||||
of re-enabling.
|
||||
|
||||
## Messages
|
||||
|
||||
### Incoming
|
||||
|
||||
- `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.
|
||||
- `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~.
|
||||
- `StatementDistributionMessage::NetworkBridgeUpdate`
|
||||
- See next section.
|
||||
|
||||
#### Network bridge events
|
||||
|
||||
- v1 compatibility
|
||||
- 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.
|
||||
- `BackedCandidateManifest`
|
||||
- Notification of a backed candidate being known by the sending node.
|
||||
- For the candidate being requested by the receiving node if needed.
|
||||
- Announcement.
|
||||
- Sent by a peer's Statement Distribution subsystem.
|
||||
- `BackedCandidateKnown`
|
||||
- Notification of a backed candidate being known by the sending node.
|
||||
- For informing a receiving node which already has the candidate.
|
||||
- Acknowledgement.
|
||||
- Sent by a peer's Statement Distribution subsystem.
|
||||
|
||||
### Outgoing
|
||||
|
||||
- `NetworkBridgeTxMessage::SendValidationMessages`
|
||||
- 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.
|
||||
- `NetworkBridgeTxMessage::ReportPeer`
|
||||
- Reports a peer (either good or bad).
|
||||
- `CandidateBackingMessage::Statement`
|
||||
- Note a validator's statement about a particular candidate.
|
||||
- `ProspectiveTeyrchainsMessage::GetHypotheticalMembership`
|
||||
- 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.
|
||||
|
||||
### Protocol
|
||||
|
||||
1. Requesting Validator
|
||||
|
||||
- Requests are queued up with `RequestManager::get_or_insert`.
|
||||
- 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.
|
||||
|
||||
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`.
|
||||
- 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.
|
||||
|
||||
3. Requesting Validator
|
||||
|
||||
- `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.
|
||||
- Runs on the `MuxedMessage` receiver.
|
||||
- `handle_response` handles the response.
|
||||
|
||||
### API
|
||||
|
||||
- `dispatch_requests`
|
||||
- Dispatches pending requests for candidate data & statements.
|
||||
- `answer_request`
|
||||
- Answers an incoming request for a candidate.
|
||||
- Takes an incoming `AttestedCandidateRequest`.
|
||||
- `receive_response`
|
||||
- Wait on the next incoming response.
|
||||
- If there are no requests pending, this future never resolves.
|
||||
- Returns `UnhandledResponse`
|
||||
- `handle_response`
|
||||
- Handles an incoming response.
|
||||
- Takes `UnhandledResponse`
|
||||
|
||||
## 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:
|
||||
|
||||
- `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.
|
||||
|
||||
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:
|
||||
|
||||
- They announced, we acknowledged.
|
||||
- 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:
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
1. candidate seconded
|
||||
1. backable in cluster
|
||||
1. distributed along grid
|
||||
1. latecomers issue statements
|
||||
1. candidate posted on chain
|
||||
1. really latecomers issue statements
|
||||
|
||||
## 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 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.
|
||||
|
||||
### 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
### Example
|
||||
|
||||
For size 11, the matrix would be:
|
||||
|
||||
```
|
||||
0 1 2
|
||||
3 4 5
|
||||
6 7 8
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
## 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:
|
||||
|
||||
- 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.
|
||||
|
||||
## 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.
|
||||
- **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 teyrchain fragment not referenced by the relay-chain. It is a tree of prospective teyrchain
|
||||
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.
|
||||
- **Statement:** Signed statements that can be made about teyrchain candidates.
|
||||
- **Seconded:** Proposal of a teyrchain candidate. Implicit validity vote.
|
||||
- **Valid:** States that a teyrchain candidate is valid.
|
||||
- **Target:** Target validator to send a statement to.
|
||||
- **View:** Current knowledge of the chain state.
|
||||
- **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 teyrchains at those leaves.
|
||||
Reference in New Issue
Block a user