Jenner's Blog

不变秃,也要变强!

0%

平行链系统架构

这篇文章基于Polkadot的6be14014提交(2020/12),目的是介绍Polkadot在平行链角度上的系统架构。

角色

架构图

  • Relay chain:中继链,负责平台安全性。
  • Parachain:平行链,拥有自己独立的状态与业务逻辑,共享中继链提供的安全性。

    节点

  • validator:验证人,负责中继链的出块,同时验证来自收集者的证明,与其他验证人进行共识投票。中继链全节点,需要抵押DOT。
  • collator:收集者,为验证人收集平行链的交易和状态转移证明(collation)。平行链全节点,同时内嵌中继链全节点服务,不一定需要抵押DOT,可以由平行链自行激励。注意,平行线程的 collator 需要持有DOT,以便参与出块资格的拍卖。
  • fishermen:渔民,监控验证人和收集者,检查无效的候选收据。collator 或者 validator 都可以作为 fishermen,需要抵押DOT。

平行链节点结构

平行链节点主要有以下两点变动。

共识

collator 在平行链上的角色类似于以往的独立链上的 validator。
但是 collator 只提供候选区块,然后交由中继链上的 validator 进行共识。所以,平行链不再需要自己的共识机制。当然,可以保留对 collator 的选择机制。

双服务

平行链节点与以往单链节点的不同在于:需要启动一个中继链全节点服务。
内嵌的中继链全节点服务中,包含了 overseer (关于 overseer 在“中继链节点结构”部分介绍)与子系统服务,并且将 overseer_handle 共享给 collator,在collator_protocol上注册为 collator_side。因此,collator 能与 validator 通过 overseer 进行消息交互,例如传递候选区块相关消息。
另外,平行链全节点还需要通过内嵌的中继链节点来“跟随”中继链的出块。所谓“跟随”,指的是平行链全节点的最佳区块为中继链上最佳区块包含的相应平行链区块,终结区块亦如此。

中继链节点结构

relay_chain

中继链上除了必须的基础组件外,比较重要的就是overseer与子系统。

overseer

overseer

Overseer 主要有以下功能:

  • 启动和关闭一系列子系统;
  • 作为子系统之间的消息总线,避免消息争用;
  • 监听外部事件,触发子系统相应任务。

消息协议

overseer 向子系统发送两种类型的消息:Communication, Signal

  • Communication:子系统之间交互的消息被封装在Communication类型中,根据被封装的消息类型传递到指定的子系统。例如子系统A向子系统B发送一个消息M:1. A向overseer发送AllMessages::B{M};2. overseer收到后,向B发送FromOverseer::Communication{M}
  • Signal:系统消息,例如块导入、块终结、关闭子系统,被封装在Signal中。系统消息会被广播到所有子系统。

子系统

目前设计上共有18个子系统。

Collator相关

  • collation_generation_subsystem:collator在块更新时生成collation
  • collator_protocol_subsystem:collation的请求与回应,根据validator/collator的角色执行对应的任务

候选区块共识

  • candidate_selection_subsystem:触发对collation的请求,收到collation后请求投票
  • candidate_backing_subsystem:对collation投票,签署statement
  • statement_distribution_subsystem:广播statement
  • pov_distribution_subsystem:广播PoV
  • apporoval_subsystems:在finalize前对候选区块的再次检查

可用性相关

  • availability_distribution_subsystem
  • bitfield_signing_subsystem
  • bitfield_distribution_subsystem
  • availability_recovery

工具类子系统

  • candidate_validation_subsystem:验证候选区块
  • provisioner_subsystem:提供平行链相关的出块打包数据
  • runtime_api_subsystem:调用runtime api
  • availability_store_subsystem:存储可用性数据
  • network_bridge_subsystem:与collation相关数据在节点间传递的网桥协议
  • chain_api_subsystem
  • misbehavior_arbitration

Collator

以平行链上的Collator为例,介绍节点如何与子系统协作。

启动

  • 平行链启动时,build_polkadot_full_node启动一个中继链全节点,包含overseer和子系统,并且在 collator_protocol_subsystem 注册为collator_side。如果要以collator的身份启动节点,启动时需要设定--collator,类似于以往独立链设定--validator启动验证人节点。中继链全节点持有平行链的client,即平行链嵌套在中继链全节点中运行。
    • start_collatorpolkadot_full_node.client.execute_with(StartCollator},collator和polkadot_full_node共享一个overseer_handler
      • cumulus_consensus::run_parachain_consensus:根据中继链的出块来更新平行链
        • follow_new_best:1. 中继链监听到中继链的best区块包含新的平行链区块后,handle_new_best_parachain_head:如果collator已经导入了该区块,就把它当做best并且广播给平行链节点。如果还没有导入,存到unset_best_header中;2. collator导入区块时,优先导入unset_best_header
        • follow_finalized_head:中继链监听到中继链的finalize区块包含新的平行链区块后,collator进行finalize_block
      • 初始化collation_generation_subsystem,在此时注册collator.produce_candidate方法,用来生成collation,同时注册para_id,collatorId(公钥)。
      • 在 collator_protocol_subsystem 上注册 Collator 所在的para_id,在advertise_collation需要用到该id。

准备候选区块

  • collation_generation_subsystem 启动后会循环handle_incoming。接受到Signal(ActiveLeaves(ActiveLeavesUpdate{..})后触发handle_new_activations
    • 获取availability_cores:core的数量为max(n_parachains + config.parathread_cores, validators.len() / config.max_validators_per_core),第i个parachain分配第i个core。调用request_availability_cores_ctx -> runtime_api_impl/v1::availability_cores 得到CoreState。
    • 仅匹配CoreState::Scheduled;
    • 检查scheduled_core.para_id;
    • request_full_validation_data_ctx 会触发中继链调用runtime api获取ValidationData,实际调用make_persisted_validation_data构造辅助验证的数据,最终被打包进平行链的SystemInherentData。这时的ValidationData是平行链父区块所对应的;
    • task_config.collator -> produce_candidate
      • 500ms内propose一个候选区块;
      • build_collation:构建候选区块的Collation
      • 开启wait_to_announce任务,注册成为StatementListener。当收到validator发来的Statement::Seconded就广播当前候选区块。
    • collator_signature_payload签名,payload包括:relay_parent,para_id,persisted_validation_data_hash,pov_hash;
    • erasure_root:将available_data分成chunks,组合成 merkle trie,erasure_root为树根哈希。
    • 生成CandidateReceipt,发送CollatorProtocolMessage::DistributeCollation(CandidateReceipt, PoV)给 collator_protocol_subsystem。

schedule_core

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// AvailabilityCores
pub enum CoreOccupied {
/// A parathread.
Parathread(ParathreadEntry),
/// A parachain.
Parachain,
}

// Scheduled
pub enum AssignmentKind {
/// A parachain.
Parachain,
/// A parathread.
Parathread(CollatorId, u32),
}
pub struct CoreAssignment {
/// The core that is assigned.
pub core: CoreIndex,
/// The unique ID of the para that is assigned to the core.
pub para_id: ParaId,
/// The kind of the assignment.
pub kind: AssignmentKind,
/// The index of the validator group assigned to the core.
pub group_idx: GroupIndex,
}

request_availability_cores

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
pub struct OccupiedCore<H = Hash, N = BlockNumber> {
// NOTE: this has no ParaId as it can be deduced from the candidate descriptor.

/// If this core is freed by availability, this is the assignment that is next up on this
/// core, if any. None if there is nothing queued for this core.
pub next_up_on_available: Option<ScheduledCore>,
/// The relay-chain block number this began occupying the core at.
pub occupied_since: N,
/// The relay-chain block this will time-out at, if any.
pub time_out_at: N,
/// If this core is freed by being timed-out, this is the assignment that is next up on this
/// core. None if there is nothing queued for this core or there is no possibility of timing
/// out.
pub next_up_on_time_out: Option<ScheduledCore>,
/// A bitfield with 1 bit for each validator in the set. `1` bits mean that the corresponding
/// validators has attested to availability on-chain. A 2/3+ majority of `1` bits means that
/// this will be available.
#[cfg_attr(feature = "std", ignore_malloc_size_of = "outside type")]
pub availability: BitVec<bitvec::order::Lsb0, u8>,
/// The group assigned to distribute availability pieces of this candidate.
pub group_responsible: GroupIndex,
/// The hash of the candidate occupying the core.
pub candidate_hash: CandidateHash,
/// The descriptor of the candidate occupying the core.
pub candidate_descriptor: CandidateDescriptor<H>,
}

pub struct ScheduledCore {
/// The ID of a para scheduled.
pub para_id: Id,
/// The collator required to author the block, if any.
pub collator: Option<CollatorId>,
}

pub enum CoreState<H = Hash, N = BlockNumber> {
/// The core is currently occupied.
Occupied(OccupiedCore<H, N>),
/// The core is currently free, with a para scheduled and given the opportunity
/// to occupy.
/// If a particular Collator is required to author this block, that is also present in this
/// variant.
Scheduled(ScheduledCore),
/// The core is currently free and there is nothing scheduled. This can be the case for parathread
/// cores when there are no parathread blocks queued. Parachain cores will never be left idle.
Free,
}

PersistedValidationData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub struct PersistedValidationData<N = BlockNumber> {
/// The parent head-data.
pub parent_head: HeadData,
/// The relay-chain block number this is in the context of.
pub block_number: N,
/// The relay-chain block storage root this is in the context of.
pub relay_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.
pub hrmp_mqc_heads: Vec<(Id, Hash)>,
/// The MQC head for the DMQ.
///
/// The DMQ MQC head will be used by the validation function to authorize the downward messages
/// passed by the collator.
pub dmq_mqc_head: Hash,
/// The maximum legal size of a POV block, in bytes.
pub max_pov_size: u32,
}

平行链的SystemInherentData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   /// The payload that system inherent carries.
pub struct SystemInherentData {
pub validation_data: crate::PersistedValidationData,
/// A storage proof of a predefined set of keys from the relay-chain.
///
/// Specifically this witness contains the data for:
///
/// - active host configuration as per the relay parent,
/// - the relay dispatch queue sizes
/// - the list of egress HRMP channels (in the list of recipients form)
/// - the metadata for the egress HRMP channels
pub relay_chain_state: sp_trie::StorageProof,
/// Downward messages in the order they were sent.
pub downward_messages: Vec<InboundDownwardMessage>,
/// HRMP messages grouped by channels. The messages in the inner vec must be in order they
/// were sent. In combination with the rule of no more than one message in a channel per block,
/// this means `sent_at` is **strictly** greater than the previous one (if any).
pub horizontal_messages: BTreeMap<ParaId, Vec<InboundHrmpMessage>>,
}

对SystemInherentData创建的Inherent交易为set_validation_data:可更新parachain的runtime wasm,更新[存储中的PersistedValidationData,RelevantMessagingState,HostConfiguration,processed_downward_messages,hrmp_watermark],处理数据[on_validation_dataDownwardMessageHandlers::handle_downward_messageHrmpMessageHandlers::handle_hrmp_message]

Collation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pub struct Collation<BlockNumber = polkadot_primitives::v1::BlockNumber> {
/// Messages destined to be interpreted by the Relay chain itself.
pub upward_messages: Vec<UpwardMessage>,
/// The horizontal messages sent by the parachain.
pub horizontal_messages: Vec<OutboundHrmpMessage<ParaId>>,
/// New validation code. Used by upgrade
pub new_validation_code: Option<ValidationCode>,
/// The head-data produced as a result of execution.
// HeadData(ParachainBlockData.header.encode())
pub head_data: HeadData,
/// Proof to verify the state transition of the parachain.
pub proof_of_validity: PoV,
/// The number of messages processed from the DMQ.
pub processed_downward_messages: u32,
/// The mark which specifies the block number up to which all inbound HRMP messages are processed.
pub hrmp_watermark: BlockNumber,
}

pub struct PoV {
/// The block witness data.
pub block_data: BlockData,
}

// PoV = BlockData(ParachainBlockData.encode())
pub struct ParachainBlockData<B: BlockT> {
/// The header of the parachain block.
header: <B as BlockT>::Header,
/// The extrinsics of the parachain block without the `PolkadotInherent`.
extrinsics: Vec<<B as BlockT>::Extrinsic>,
/// The data that is required to emulate the storage accesses executed by all extrinsics.
storage_proof: StorageProof,
}

pub struct StorageProof {
trie_nodes: Vec<Vec<u8>>,
}

Ensure Code的available_data

1
2
3
4
5
6
pub struct AvailableData {
/// The Proof-of-Validation of the candidate.
pub pov: std::sync::Arc<PoV>,
/// The persisted validation data needed for secondary checks.
pub validation_data: PersistedValidationData,
}

连接并通知validator

  • collator_protocol_subsystem 监听Communication(CollatorProtocolMessage),process_msg处理消息。收到DistributeCollation后,检查para_id,然后distribute_collation向validator广播。
    • distribute_collation:要求collation在active-leaves上,collation未被发送
      • determine_core:计算当前平行链分配到的core
      • determine_our_validators:计算分配到该平行链的validator集合,包括当前集合和下一次分配的集合。每个session会依据n_coresvalidators.len()重新排列ValidatorGroups,但是不一定是一个session才更换分配给平行链的group。
      • connect_to_validators:同时连接当前validators和下一组validators。
  • 连接上新的validator后,在collator protocol子系统中handle_validator_connected
    • 发送一个CollatorProtocolMessage::Declare(CollatorId)给validator,注册collator
    • 如果validator是属于该平行链的验证人集合,那么add_peer_id_for_validator记录validators的peer_id,advertise_collation,向validator 发送CollatorProtocolMessage::AdvertiseCollation(relay_parent, collating_on) ,通知validator准备发送collation了。

Candidate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
pub struct CandidateReceipt<H = Hash> {
/// The descriptor of the candidate.
pub descriptor: CandidateDescriptor<H>,
/// The hash of the encoded commitments made as a result of candidate execution.
pub commitments_hash: Hash,
}

pub struct CandidateDescriptor<H = Hash> {
/// The ID of the para this is a candidate for.
pub para_id: Id,
/// The hash of the relay-chain block this is executed in the context of.
pub relay_parent: H,
/// The collator's sr25519 public key.
pub collator: CollatorId,
/// The blake2-256 hash of the persisted validation data. This is extra data derived from
/// relay-chain state which may vary based on bitfields included before the candidate.
/// Thus it cannot be derived entirely from the relay-parent.
pub persisted_validation_data_hash: Hash,
/// The blake2-256 hash of the pov.
pub pov_hash: Hash,
/// The root of a block's erasure encoding Merkle tree.
pub 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.
pub signature: CollatorSignature,
/// Hash of the para header that is being generated by this candidate.
pub para_head: Hash,
}

// CandidateCommitments = Collation - PoV
pub struct CandidateCommitments<N = BlockNumber> {
/// Messages destined to be interpreted by the Relay chain itself.
pub upward_messages: Vec<UpwardMessage>,
/// Horizontal messages sent by the parachain.
pub horizontal_messages: Vec<OutboundHrmpMessage<Id>>,
/// New validation code.
pub new_validation_code: Option<ValidationCode>,
/// The head-data produced as a result of execution.
pub head_data: HeadData,
/// The number of messages processed from the DMQ.
pub processed_downward_messages: u32,
/// The mark which specifies the block number up to which all inbound HRMP messages are processed.
pub hrmp_watermark: N,
}

回应validator的请求

  • 收到validator消息CollatorProtocolMessage::RequestCollation
    • send_collation:回应validtor,发送CollatorProtocolMessage::Collation(request_id,receipt,pov)给validator。对PoV进行compress再发送,减少通信压力。CandidateReceipt中只存储commitments_hash,validator在验证时从PoV中重新组装出commitments,最后验证hash是否一致。
      由于这是节点间通信,overseer的中继只能作用于节点内的子系统之间,所以这个消息是被封装在NetworkBridgeMessage::SendCollationMessage中的,通过 network_bridge_subsystem 向validator转发消息(一对一,非广播)。

出块

  • 当中继链上statement被checked后(状态为seconded),collator在平行链上announce_block,此时为暂时同步。
  • 最佳区块和终结区块跟随中继链的出块。

More

实际上,以上所描述的Collator与子系统的交互也可以说是以Collator角度的平行链出块流程。关于以Validator角度的平行链出块流程将在《平行链节点的出块流程》中介绍。

点击下方打赏按钮,获得支付宝二维码