Candidate Types
Para candidates are some of the most common types, both within the runtime and on the Node-side. Candidates are the fundamental datatype for advancing parachains, encapsulating the collator's signature, the context of the parablock, the commitments to the output, and a commitment to the data which proves it valid.
In a way, this entire guide is about these candidates: how they are scheduled, constructed, backed, included, and challenged.
This section will describe the base candidate type, its components, and variants that contain extra data.
Para Id
A unique 32-bit identifier referring to a specific para (chain or thread). The relay-chain runtime guarantees that
ParaId
s are unique for the duration of any session, but recycling and reuse over a longer period of time is permitted.
#![allow(unused)] fn main() { struct ParaId(u32); }
Candidate Receipt
Compact representation of the result of a validation. This is what validators receive from collators, together with the PoV.
#![allow(unused)] fn main() { /// A candidate-receipt. struct CandidateReceipt { /// The descriptor of the candidate. descriptor: CandidateDescriptor, /// The hash of the encoded commitments made as a result of candidate execution. commitments_hash: Hash, } }
Committed Candidate Receipt
This is a variant of the candidate receipt which includes the commitments of the candidate receipt alongside the
descriptor. This should be favored over the Candidate Receipt
in situations where the candidate
is not going to be executed but the actual data committed to is important. This is often the case in the backing phase.
The hash of the committed candidate receipt will be the same as the corresponding Candidate Receipt
, because it is computed by first hashing the encoding of the commitments to form a plain
Candidate Receipt
.
#![allow(unused)] fn main() { /// A candidate-receipt with commitments directly included. struct CommittedCandidateReceipt { /// The descriptor of the candidate. descriptor: CandidateDescriptor, /// The commitments of the candidate receipt. commitments: CandidateCommitments, } }
Candidate Descriptor
This struct is pure description of the candidate, in a lightweight format.
#![allow(unused)] fn main() { /// A unique descriptor of the candidate receipt. struct CandidateDescriptor { /// The ID of the para this is a candidate for. para_id: ParaId, /// The hash of the relay-chain block this is executed in the context of. relay_parent: Hash, /// The collator's sr25519 public key. collator: CollatorId, /// The blake2-256 hash of the persisted validation data. These are extra parameters /// derived from relay-chain state that influence the validity of the block which /// must also be kept available for approval checkers. persisted_validation_data_hash: Hash, /// The blake2-256 hash of the `pov-block`. pov_hash: Hash, /// The root of a block's erasure encoding Merkle tree. erasure_root: Hash, /// Signature on blake2-256 of components of this receipt: /// The parachain index, the relay parent, the validation data hash, and the `pov_hash`. signature: CollatorSignature, /// Hash of the para header that is being generated by this candidate. para_head: Hash, /// The blake2-256 hash of the validation code bytes. validation_code_hash: ValidationCodeHash, } }
ValidationParams
#![allow(unused)] fn main() { /// Validation parameters for evaluating the parachain validity function. pub struct ValidationParams { /// Previous head-data. pub parent_head: HeadData, /// The collation body. pub block_data: BlockData, /// The current relay-chain block number. pub relay_parent_number: RelayChainBlockNumber, /// The relay-chain block's storage root. pub relay_parent_storage_root: Hash, } }
PersistedValidationData
The validation data provides information about how to create the inputs for validation of a candidate. This information is derived from the chain state and will vary from para to para, although some of the fields may be the same for every para.
Since this data is used to form inputs to the validation function, it needs to be persisted by the availability system to avoid dependence on availability of the relay-chain state.
Furthermore, the validation data acts as a way to authorize the additional data the collator needs to pass to the validation function. For example, the validation function can check whether the incoming messages (e.g. downward messages) were actually sent by using the data provided in the validation data using so called MQC heads.
Since the commitments of the validation function are checked by the relay-chain, approval checkers can rely on the invariant that the relay-chain only includes para-blocks for which these checks have already been done. As such, there is no need for the validation data used to inform validators and collators about the checks the relay-chain will perform to be persisted by the availability system.
The PersistedValidationData
should be relatively lightweight primarily because it is constructed during inclusion for
each candidate and therefore lies on the critical path of inclusion.
#![allow(unused)] fn main() { struct PersistedValidationData { /// The parent head-data. parent_head: HeadData, /// The relay-chain block number this is in the context of. This informs the collator. relay_parent_number: BlockNumber, /// The relay-chain block storage root this is in the context of. relay_parent_storage_root: Hash, /// The list of MQC heads for the inbound channels paired with the sender para ids. This /// vector is sorted ascending by the para id and doesn't contain multiple entries with the same /// sender. /// /// The HRMP MQC heads will be used by the validation function to authorize the input messages passed /// by the collator. hrmp_mqc_heads: Vec<(ParaId, Hash)>, /// The maximum legal size of a POV block, in bytes. pub max_pov_size: u32, } }
HeadData
Head data is a type-safe abstraction around bytes (Vec<u8>
) for the purposes of representing heads of parachains.
#![allow(unused)] fn main() { struct HeadData(Vec<u8>); }
Candidate Commitments
The execution and validation of parachain candidates produces a number of values which either must be committed to blocks on the relay chain or committed to the state of the relay chain.
#![allow(unused)] fn main() { /// Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. #[derive(PartialEq, Eq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Default))] struct CandidateCommitments { /// Messages directed to other paras routed via the relay chain. horizontal_messages: Vec<OutboundHrmpMessage>, /// Messages destined to be interpreted by the Relay chain itself. upward_messages: Vec<UpwardMessage>, /// New validation code. new_validation_code: Option<ValidationCode>, /// The head-data produced as a result of execution. head_data: HeadData, /// The number of messages processed from the DMQ. processed_downward_messages: u32, /// The mark which specifies the block number up to which all inbound HRMP messages are processed. hrmp_watermark: BlockNumber, } }
Signing Context
This struct provides context to signatures by combining with various payloads to localize the signature to a particular session index and relay-chain hash. Having these fields included in the signature makes misbehavior attribution much simpler.
#![allow(unused)] fn main() { struct SigningContext { /// The relay-chain block hash this signature is in the context of. parent_hash: Hash, /// The session index this signature is in the context of. session_index: SessionIndex, } }