ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Polkadot PVF Pre-checking Runtime API 详解:`pvfs_require_precheck` 与 `submit_pvf_check_statement`

Polkadot PVF Pre-checking Runtime API 详解:`pvfs_require_precheck` 与 `submit_pvf_check_statement` 区块链【免费下载链接】polkadotPolkadot Node Implementation项目地址https://gitcode.com/gh_mirrors/po/polkadot点击查看免费下载本指南围绕 Polkadot 实现者指南Implementers Guide中 runtime-api/pvf-prechecking.md 展开完整讲解 PVF Pre-checking验证代码预检查投票机制所依赖的两个 runtime API如何查询待预检查的 PVF 列表、如何提交验证者对 PVF 的判定投票并深入结合paraspallet 的底层实现、离线状态机与客户端 Pvf-Checker 子系统帮助读者理解从代码升级/上链申请到投票达成超级多数、裁决生效的完整闭环。背景什么是 PVF Pre-checking在 Polkadot 平行链协议中每一条平行链都需要向中继链提交一份Validation Code验证代码即 PVFParachain Validation Function。这份代码的质量与安全性直接决定中继链的可用性与安全性如果某份验证代码存在漏洞、非法指令或会导致中继链验证器超时的行为那么它一旦被启用就可能影响整条链的共识。为此中继链在**新平行链上链Onboarding与已有平行链升级验证代码Upgrade**两条路径上都引入了 PVF Pre-checking 流程验证器需要先在一个受限的沙箱环境中执行待检查的验证代码确认其能够正常编译、执行并给出结果然后再通过投票决定该 PVF 是否被接受。相关设计文档见 node/pvf/README.md仓库内 PVF 子系统说明以及 runtime-api/validation-code.md验证代码的查询 API。PVF Pre-checking 的两个 runtime API 于运行时版本 v2 加入见 primitives/src/runtime_api.rs 中/***** Added in v2 *****/注释段它们是该流程与链上状态交互的唯一入口/// 返回所有需要当前活跃验证器投票的 PVF以验证代码哈希标识。 /// 一旦某个 PVF 获得了所需支持它就不会再被该 API 返回。 fn pvfs_require_precheck() - VecValidationCodeHash; /// 提交验证者对一个 PVF 的判定通过或拒绝。 /// 该调用以无签名交易unsigned transaction的形式进入交易池。 fn submit_pvf_check_statement(stmt: PvfCheckStatement, signature: ValidatorSignature);PvfCheckStatement投票的载体类型投票的核心数据结构是PvfCheckStatement它承载了验证者对某个 PVF 的判定结果定义在 roadmap/implementers-guide/src/types/pvf-prechecking.md 中并对应着polkadot-primitives中的正式 Rust 类型见 primitives/src/v5/mod.rsstruct PvfCheckStatement { /// true 表示被检查的 PVF 通过了预检查false 表示未通过。 pub accept: bool, /// 被检查的验证代码的哈希。 pub subject: ValidationCodeHash, /// 该语句仅在指定的会话session内有效。 pub session_index: SessionIndex, /// 发起该语句的验证者在活跃验证者集合中的索引。 pub validator_index: ValidatorIndex, }值得注意的语义约束是session_index一条投票只在它声明的那个会话内有效。因为每个会话的活跃验证者集合不同投票在会话切换后会自动失效。与此同时节点侧在签名前需要生成签名负载其格式在源码中有明确约定见 primitives/src/v5/mod.rspub fn signing_payload(self) - Vecu8 { const MAGIC: [u8; 4] *bVCPC; // for validation code pre-checking (MAGIC, self.accept, self.subject, self.session_index, self.validator_index).encode() }签名负载以魔术字节VCPC开头再拼接语句的全部字段用于防止跨域重放例如把其他协议的签名数据误用作投票。Runtime API 一pvfs_require_precheck查询待投票 PVF该 API 的作用是返回所有需要当前活跃验证者参与投票的 PVF 的代码哈希列表。它是预检查流程的拉取端验证者节点周期性地调用它拿到清单后逐一执行预检查。在 runtime 实现层paraspallet 直接读取存储项PvfActiveVoteList返回结果见 runtime/parachains/src/paras/mod.rs/// Returns the list of PVFs (aka validation code) that require casting a vote by a validator in /// the active validator set. pub(crate) fn pvfs_require_precheck() - VecValidationCodeHash { PvfActiveVoteList::T::get() }而 runtime API 的对外实现则位于 runtime/parachains/src/runtime_api_impl/v5.rs/// Returns the list of all PVF code hashes that require pre-checking. See /// [paras::Pallet::pvfs_require_precheck]. pub fn pvfs_require_precheckT: paras::Config() - VecValidationCodeHash { paras::PalletT::pvfs_require_precheck() }谁会把 PVF 加入待检查列表当出现以下两类事件时新的 PVF 会被插入PvfActiveVoteList新平行链上链申请Onboardingschedule_para_initialize在登记平行链时如果验证代码此前从未被检查过就会启动一次 PVF 预检查见 runtime/parachains/src/paras/mod.rs 的注释说明If the validation code is not already stored in the code storage, then a PVF pre-checking process will be initiated已有平行链代码升级Upgrade平行链提交新代码后同样会触发预检查。核心的登记逻辑位于schedule_code_upgrade/schedule_para_initialize内部见 runtime/parachains/src/paras/mod.rs其分支逻辑可以概括为若PvfActiveVoteMap中没有该代码哈希的活跃投票记录如果该代码哈希已经在CodeByHash存储中说明它此前被检查并通过过则直接**快速通道fast-track**为已接受状态并执行enact_pvf_accepted否则创建新的PvfCheckActiveVoteState并把代码哈希插入PvfActiveVoteList若已有活跃投票记录执行合并Coalescing——不重复开启新的投票而是把新的原因cause追加到现有投票的causes列表中让多个平行链共用同一次预检查。这也解释了为什么pvfs_require_precheck的返回值会不断收缩一旦某 PVF 达成超级多数它就会从PvfActiveVoteList中移除后续调用自然不再返回它。客户端如何消费该 APIPvf-Checker 子系统在节点侧pvf-checker子系统是pvfs_require_precheck的主要消费者。它在每次激活新区块activation时调用该 API见 node/core/pvf-checker/src/lib.rslet pending_pvfs match runtime_api::pvfs_require_precheck(sender, leaf_hash).await { Err(runtime_api::RuntimeRequestError::NotSupported) return None, Err(_) { gum::debug!(target: LOG_TARGET, relay_parent ?leaf_hash, cannot fetch PVFs that require pre-checking from runtime API); Vec::new() }, Ok(v) v, };注意这里的错误处理约定如果 runtime API 返回NotSupported即节点连接的运行时版本低于 v2子系统会直接返回None并放弃该 leaf 的处理其他错误则回退为空列表。拿到清单后子系统对每个待检查的代码哈希发起预检查见 node/core/pvf-checker/src/lib.rsasync fn initiate_precheck(state: mut State, sender: mut impl overseer::PvfCheckerSenderTrait, relay_parent: Hash, validation_code_hash: ValidationCodeHash, metrics: Metrics) { ... let (tx, rx) oneshot::channel(); sender.send_message(CandidateValidationMessage::PreCheck(relay_parent, validation_code_hash, tx)).await; ... }预检查请求被发送给Candidate-Validation 子系统由它调度 PVF 的编译与执行涉及node/pvf中的 prepare/execute worker见 node/pvf/src/lib.rs。检查结果Judgement随后进入投票提交流程。Runtime API 二submit_pvf_check_statement提交判定投票第二个 API 用于提交验证者对某个 PVF 的判定。关键设计点投票以无签名交易unsigned transaction的形式传播与上链——验证者节点并不直接修改链上状态而是把语句通过 gossip 网络像普通交易一样广播出去最终由某个区块生产者将语句包含进区块再由运行时处理。fn submit_pvf_check_statement(stmt: PvfCheckStatement, signature: ValidatorSignature);从节点到交易池离线提交路径在 runtime 实现中该 API 调用paraspallet 的submit_pvf_check_statement方法后者借助frame_system::offchain::SubmitTransaction将Call::include_pvf_check_statement作为无签名交易提交进交易池见 runtime/parachains/src/paras/mod.rs/// Submits a given PVF check statement with corresponding signature as an unsigned transaction /// into the memory pool. Ultimately, that disseminates the transaction accross the network. /// /// This function expects an offchain context and cannot be callable from the on-chain logic. pub(crate) fn submit_pvf_check_statement(stmt: PvfCheckStatement, signature: ValidatorSignature) { use frame_system::offchain::SubmitTransaction; if let Err(e) SubmitTransaction::T, CallT::submit_unsigned_transaction( Call::include_pvf_check_statement { stmt, signature }.into(), ) { log::error!(target: LOG_TARGET, Error submitting pvf check statement: {:?}, e); } }runtime API 层的包装见 runtime/parachains/src/runtime_api_impl/v5.rs。无签名交易的校验ValidateUnsigned由于是无签名交易运行时必须通过ValidateUnsigned预先筛选合法交易防止垃圾交易进入交易池。该逻辑位于 runtime/parachains/src/paras/mod.rs校验顺序为会话检查stmt.session_index current_session返回InvalidTransaction::Stale current_session返回InvalidTransaction::Future——即投票必须针对当前会话验证者索引检查stmt.validator_index必须落在当前活跃验证者集合范围内否则返回自定义错误码INVALID_TX_BAD_VALIDATOR_IDXu8 1签名检查用stmt.signing_payload()对签名进行验签失败返回InvalidTransaction::BadProof主题检查PvfActiveVoteMap中必须存在对应代码哈希的活跃投票否则返回INVALID_TX_BAD_SUBJECTu8 2防重复投票该验证者在本会话内不能重复投票否则返回INVALID_TX_DOUBLE_VOTEu8 3。自定义错误码常量定义在同文件 runtime/parachains/src/paras/mod.rs// custom transaction error codes const INVALID_TX_BAD_VALIDATOR_IDX: u8 1; const INVALID_TX_BAD_SUBJECT: u8 2; const INVALID_TX_DOUBLE_VOTE: u8 3;通过校验后交易被赋予ValidTransaction属性优先级取自T::UnsignedPriority存活期longevity约为平均会话长度的一半并以(session_index, validator_index, subject)三元组作为and_provides键用于交易池内的去重与依赖管理ValidTransaction::with_tag_prefix(PvfPreCheckingVote) .priority(T::UnsignedPriority::get()) .longevity(TryInto::u64::try_into( T::NextSessionRotation::average_session_length() / 2u32.into(), ).unwrap_or(64_u64)) .and_provides((stmt.session_index, stmt.validator_index, stmt.subject)) .propagate(true) .build()注意pre_dispatch直接返回Ok因为真正完整的校验会在include_pvf_check_statement调度dispatch时再执行一遍见 runtime/parachains/src/paras/mod.rs 的注释说明。上链裁决include_pvf_check_statement调度逻辑当无签名交易被包含进区块后运行时执行include_pvf_check_statementcall_index 7见 runtime/parachains/src/paras/mod.rs。其流程如下ensure_none(origin)?; // 必须是无签名交易来源 let validators shared::Pallet::T::active_validator_keys(); let current_session shared::Pallet::T::session_index(); // 1. 会话必须等于当前会话 if stmt.session_index current_session { return Err(Error::T::PvfCheckStatementStale.into()) } else if stmt.session_index current_session { return Err(Error::T::PvfCheckStatementFuture.into()) } // 2. 验证者索引与公钥 let validator_index stmt.validator_index.0 as usize; let validator_public validators.get(validator_index) .ok_or(Error::T::PvfCheckValidatorIndexOutOfBounds)?; // 3. 验签 let signing_payload stmt.signing_payload(); ensure!(signature.verify(signing_payload[..], validator_public), Error::T::PvfCheckInvalidSignature); // 4. 该 PVF 必须存在活跃投票 let mut active_vote PvfActiveVoteMap::T::get(stmt.subject) .ok_or(Error::T::PvfCheckSubjectInvalid)?; // 5. 防重复投票 ensure!(!active_vote.has_vote(validator_index) .ok_or(Error::T::PvfCheckValidatorIndexOutOfBounds)?, Error::T::PvfCheckDoubleVote);随后根据stmt.accept写入对应的投票位votes_accept或votes_reject再调用active_vote.quorum(validators.len())检查是否达成结果。若达成则从活跃投票映射与列表中移除该代码哈希并根据结果执行裁决Accepted通过→enact_pvf_accepted见 runtime/parachains/src/paras/mod.rs对每个 cause 触发事件PvfCheckAcceptedOnboarding 原因会推进平行链入列ActionsQueueUpgrade 原因则根据validation_upgrade_delay与minimum_validation_upgrade_delay计算升级生效区块并写入FutureCodeUpgradesRejected拒绝→enact_pvf_rejected见 runtime/parachains/src/paras/mod.rs对每个 cause 触发事件PvfCheckRejected同时减少验证代码的引用计数decrease_code_ref并回滚对应路径Onboarding 原因移除UpcomingParasGenesis、CurrentCodeHash、ParaLifecyclesUpgrade 原因写入UpgradeGoAhead::Abort信号并移除FutureCodeHash。投票状态机与超级多数判定投票的中间状态由PvfCheckActiveVoteState描述定义于 runtime/parachains/src/paras/mod.rspub(crate) struct PvfCheckActiveVoteStateBlockNumber { votes_accept: BitVecu8, BitOrderLsb0, // 每个验证者一个 bit投通过置 1 votes_reject: BitVecu8, BitOrderLsb0, // 每个验证者一个 bit投拒绝置 1 age: SessionIndex, // 该投票经历过的会话变更次数 created_at: BlockNumber, // 投票创建的区块号 causes: VecPvfCheckCauseBlockNumber, // 触发本次预检查的原因列表至少一个 }几个值得展开的设计点位向量即选票votes_accept与votes_reject的长度始终等于当前活跃验证者数量一个验证者只能在其中一方置位置位后即不可再投票has_vote检查见 runtime/parachains/src/paras/mod.rs会话边界重置每次会话切换时reinitialize_ballots会把两个位向量清零并按新会话的验证者数量重新调整大小见 runtime/parachains/src/paras/mod.rs因为旧会话的投票已失效同时age自增供后续上链/升级延迟计算使用原因合并Coalescing多个平行链可以共享同一次 PVF 预检查causes记录所有等待该结果的平行链裁决时逐条执行。超级多数supermajority判定逻辑位于quorum方法见 runtime/parachains/src/paras/mod.rsfn quorum(self, n_validators: usize) - OptionPvfCheckOutcome { let accept_threshold primitives::supermajority_threshold(n_validators); // 达到该阈值后通过已无可能因此判为拒绝 let reject_threshold n_validators - accept_threshold; if self.votes_accept.count_ones() accept_threshold { Some(PvfCheckOutcome::Accepted) } else if self.votes_reject.count_ones() reject_threshold { Some(PvfCheckOutcome::Rejected) } else { None } }supermajority_threshold与拜占庭阈值byzantine_threshold的关系定义在 primitives/src/v5/mod.rspub const fn byzantine_threshold(n: usize) - usize { n.saturating_sub(1) / 3 } /// The supermajority threshold of validators which represents a subset /// guaranteed to have at least f1 honest validators. pub const fn supermajority_threshold(n: usize) - usize { n - byzantine_threshold(n) }即通过需要至少 n - (n-1)/3 票保证包含 f1 个诚实验证者而拒绝的判定更宽松——当剩余票数已不足以让通过达成超级多数即拒绝票数 n - supermajority_threshold时就直接判为拒绝。该函数带有单元测试见 primitives/src/v5/mod.rsassert_eq!(supermajority_threshold(4), 3); assert_eq!(supermajority_threshold(5), 4); assert_eq!(supermajority_threshold(6), 5); assert_eq!(supermajority_threshold(7), 5);节点侧的投票去重与提交在节点侧pvf-checker子系统在提交前维护一个voted: mut HashSetValidationCodeHash对同一代码哈希只会提交一次见 node/core/pvf-checker/src/lib.rs。提交前先构造语句并签名let stmt PvfCheckStatement { accept: judgement.is_valid(), session_index, subject: validation_code_hash, validator_index: credentials.validator_index, }; let signature match polkadot_node_subsystem_util::sign( keystore, credentials.validator_key, stmt.signing_payload(), ) { Ok(Some(signature)) signature, ... };随后调用runtime_api::submit_pvf_check_statement见 node/core/pvf-checker/src/lib.rs。同时子系统会记录投票指标on_vote_submitted、on_vote_duplicate等相关定义见 node/core/pvf-checker/src/metrics.rs。会话与近期区块锚定投票只对当前会话有效投票的会话有效性在运行时与节点两层都有约束运行时include_pvf_check_statement与ValidateUnsigned都会检查stmt.session_index current_session旧会话或未来会话的投票分别被拒绝PvfCheckStatementStale/PvfCheckStatementFuture节点pvf-checker子系统在每次新区块激活时通过session_index_for_childruntime API 获取下一个会话索引只有在新会话到来state.latest_session.map_or(true, |l| l session_index)时才重新获取签名凭据见 node/core/pvf-checker/src/lib.rs。签名凭据通过check_signing_credentials从当前活跃验证者列表validatorsAPI与本地 keystore 中匹配获得见 node/core/pvf-checker/src/lib.rs。测试验证与实测路径paraspallet 的测试套件对预检查全流程有完整的覆盖见 runtime/parachains/src/paras/tests.rs预检查发起后pvfs_require_precheck()返回非空列表测试中对 Onboarding/Upgrade 两种 cause 均有断言见该文件中assert!(!Paras::pvfs_require_precheck().is_empty())的多处出现投票达成后列表清空assert!(Paras::pvfs_require_precheck().is_empty())验证达成所需支持后不再返回这一语义。此外runtime/parachains/src/paras/benchmarking/pvf_check.rs提供了include_pvf_check_statement相关基准用于生成WeightInfo中五个权重函数include_pvf_check_statement、..._finalize_upgrade_accept/reject、..._finalize_onboarding_accept/reject的实测值接口定义见 runtime/parachains/src/paras/mod.rs。完整数据流总结至此可以给出 PVF Pre-checking 的端到端数据流触发新平行链上链或代码升级时paraspallet 将代码哈希插入PvfActiveVoteList同一哈希可被多个 cause 合并共享拉取验证者节点的pvf-checker子系统在新区块激活时调用pvfs_require_precheck获取待检查清单检查子系统把每个代码哈希通过CandidateValidationMessage::PreCheck交给 Candidate-Validation 子系统由 PVF 的 prepare/execute 沙箱 worker 执行验证代码相关实现见 node/pvf/src/lib.rs 与 node/pvf/execute、node/pvf/prepare签名与提交得到Judgement后子系统构造PvfCheckStatement、用验证者私钥对signing_payload()含VCPC魔数签名再调用submit_pvf_check_statement把无签名交易注入交易池传播与上链ValidateUnsigned校验通过后交易经 gossip 网络传播被区块生产者包含进区块裁决include_pvf_check_statement完成会话、索引、签名、主题、防重复投票五项校验后记票达超级多数则移除该 PVF 并执行enact_pvf_accepted/enact_pvf_rejected从此pvfs_require_precheck不再返回该代码哈希。相关文档与源码索引实现者指南PVF Pre-checking 设计页 runtime-api/pvf-prechecking.md、类型定义页 types/pvf-prechecking.md、验证代码查询 API runtime-api/validation-code.mdruntime API 声明PvfPrecheckingApi位于 primitives/src/runtime_api.rs类型与签名负载PvfCheckStatement、supermajority_threshold位于 primitives/src/v5/mod.rs 与 primitives/src/v5/mod.rsruntime 实现paraspallet 的投票状态机、调度逻辑与裁决逻辑位于 runtime/parachains/src/paras/mod.rsruntime API 包装位于 runtime/parachains/src/runtime_api_impl/v5.rs节点侧消费方pvf-checker子系统 node/core/pvf-checker/src/lib.rs 及其指标 node/core/pvf-checker/src/metrics.rs测试与基准runtime/parachains/src/paras/tests.rs、runtime/parachains/src/paras/benchmarking/pvf_check.rs赞分享区块链【免费下载链接】polkadotPolkadot Node Implementation项目地址https://gitcode.com/gh_mirrors/po/polkadot点击查看免费下载相关推荐local-deep-research News API 结构化异常处理NewsAPIException 异常层级设计与 FastAPI 集成local deep research News API 结构化异常处理NewsAPIException 异常层级设计与 FastAPI 集成 本文以 loc区块链Microsoft.Extensions.Configuration.Binder 源码级解析从反射绑定到源生成器的完整指南Microsoft.Extensions.Configuration.Binder 源码级解析从反射绑定到源生成器的完整指南 Microsoft.Extens区块链Polkadot 实现者指南persisted_validation_data Runtime API 与持久化验证数据详解Polkadot 实现者指南 persisted_validation_data Runtime API 与持久化验证数据详解 本篇指南聚焦 Polkadot区块链上一篇WarcraftHelper5分钟让魔兽争霸3在现代电脑上焕发新生下一篇Open CoDesign Agent 运行协议加固指南从「一次性生成器」到「可见的设计协作者」创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表