Solana Commitment 机制剖析:BlockCommitment 数据结构、共识复用聚合算法与 getBlockCommitment RPC 实现 📅 发布时间:2026/9/14 19:24:49 👁 浏览次数: Solana Commitment 机制剖析BlockCommitment 数据结构、共识复用聚合算法与 getBlockCommitment RPC 实现【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana本文基于 Solana 仓库的设计文档 Commitment 提案 展开讲解 commitment确认度指标的设计动机、BlockCommitment数组的语义、getBlockCommitmentRPC 的查询链路以及提案中复用共识计算的增强算法在 核心节点代码 中的真实落地方式。读完本文你能从投票锁存的原始数据一路追到客户端确认度等级并能在源码中找到每一步的实现与测试佐证。Commitment 指标解决什么问题Commitment 指标的目标是给客户端一个衡量某个区块在网络中获得了多少确认、多少质押stake支持的标准化度量。客户端拿到这个原始数据后可以据此推导出自己业务所需的确认度标准即 Solana 确认度状态 中定义的 Processed / Confirmed / Finalized 三级属性ProcessedConfirmedFinalized已收到区块XXX区块位于多数派分叉上XXX区块包含目标交易XXX66% 以上 stake 投票了该区块-XX区块之上又确认了 31 个区块--X也就是说节点并不直接告诉客户端Confirmed或Finalized这样的结论而是给出每个确认级别上累计了多少 stake的原始分布让客户端自行决策。这一设计思想贯穿了后文的所有数据结构与算法。BlockCommitment[u64; N]数组的索引语义按设计文档的定义客户端可以通过 RPC 调用get_block_commitment(s: Signature) - BlockCommitment向验证者查询包含签名s的区块N的确认度。BlockCommitment结构体包含一个长度为MAX_CONFIRMATIONS的u64数组数组第i个元素的含义是该验证者观测到全簇共有s数量的 stake 在区块N上达成了i次确认i从 1 到MAX_CONFIRMATIONS观测点取自验证者最近一次投票的区块M即这份数据是截至 M 时刻的快照。在当前仓库中这个结构体落在 runtime/src/commitment.rspub type BlockCommitmentArray [u64; MAX_LOCKOUT_HISTORY 1]; #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub struct BlockCommitment { pub commitment: BlockCommitmentArray, }其中MAX_LOCKOUT_HISTORY在 vote/state/mod.rs 中定义为31因此实际数组长度为32。相比提案文档中的[u64; MAX_CONFIRMATIONS]当前实现有两点演进可直接从源码确认0 基索引increase_confirmation_stake(confirmation_count, stake)把 stake 累加到commitment[confirmation_count - 1]并用assert!保证1 confirmation_count MAX_LOCKOUT_HISTORY多出的一个桶专门存放已 rooted的 stakeincrease_rooted_stake/get_rooted_stake使用commitment[MAX_LOCKOUT_HISTORY]即第 32 个元素表示该 slot 已被超过 2/3 的 stake root——这正是Finalized级别的底层数据。围绕BlockCommitment还有一组聚合缓存结构同样定义在 runtime/src/commitment.rs/// A nodes view of cluster commitment as per a particular bank #[derive(Default)] pub struct BlockCommitmentCache { /// 当前祖先 slot 各确认级别上的 stake 汇总来自 bank 中的 vote account 数据 block_commitment: HashMapSlot, BlockCommitment, /// 缓存的关键 slot 信息避免每次调用都重新计算 commitment_slots: CommitmentSlots, /// 该 bank 所在 epoch 的总活跃 stake total_stake: u64, }其中CommitmentSlotsruntime/src/commitment.rs缓存了四个关键 slotslot计算所依据的 bank 的 slotroot本节点当前 roothighest_confirmed_slot全簇确认度最高的 slothighest_super_majority_root被全簇超级多数2/3 stakeroot 的最高 slot。这四个值就是 RPCslotWithCommitment类查询和订阅通知的数据源。getBlockCommitment RPC 的查询链路设计文档描述的入口是按签名查区块确认度。当前仓库中的 RPC 实现见 rpc/src/rpc.rs 的 trait 声明与 具体实现#[rpc(meta, name getBlockCommitment)] fn get_block_commitment( self, meta: Self::Metadata, block: Slot, ) - ResultRpcBlockCommitmentBlockCommitmentArray;实现体非常直接——读取全局block_commitment_cache取出对应 slot 的BlockCommitmentArray连同total_stake一起返回fn get_block_commitment(self, block: Slot) - RpcBlockCommitmentBlockCommitmentArray { let r_block_commitment self.block_commitment_cache.read().unwrap(); RpcBlockCommitment { commitment: r_block_commitment .get_block_commitment(block) .map(|block_commitment| block_commitment.commitment), total_stake: r_block_commitment.total_stake(), } }两点需要注意当前接口直接以slot为参数客户端可先自行把签名解析为所在 slot返回的total_stake让客户端能把绝对 stake 数值换算成stake 百分比可查询范围受 status cache 窗口限制。设计文档明确指出ancestors只包含当前 status cache 中存在的 slot更早的 bank 本来就无法通过签名查询因此也不纳入确认度计算。当前实现同样遵循这一约定——core/src/commitment_service.rs 中聚合时使用的正是aggregation_data.bank.status_cache_ancestors()它返回 status cache 窗口内的祖先 slot 列表窗口外的 slot 在block_commitment映射中根本不存在。复用共识已有的计算collect_vote_lockouts设计文档的核心洞见是构建BlockCommitment几乎不需要新的计算只需要搭便车于共识构建本来就在做的投票锁存汇总。consensus.rs中的collect_vote_lockouts函数当前实现见 core/src/consensus.rs由 replay_stage.rs 在每轮共识计算时调用会对每个可投票候选 bankb构建一个HashMapb, Stake其中每个条目(b, s)表示 slotb上获得的 stakes。文档给出的原始计算形态是let output: HashMapb, Stake HashMap::new(); for vote_account in b.vote_accounts { for v in vote_account.vote_stack { for a in ancestors(v) { f(*output.get_mut(a), vote_account, v); } } }其中f是某个累加函数用可从投票v和vote_account推导出的数据stake、lockout 等更新 slota的Stake条目。这里的ancestors只包含当前 status cache 中存在的 slot。对照当前源码collect_vote_lockouts 确实是遍历vote_accounts、解析每个vote_state.votes并对每个投票构建 lockout 区间lockout_intervals与投票 stakevoted_stakes——文档描述的计算骨架与实现一致。增强算法从锁存 stake到确认度分布设计文档随后提出把上述计算原地增强在汇总 lockout 的同一轮循环里为每个 bankb顺带构建BlockCommitment数组增加一个ForkCommitmentCache用于收集所有 bank 的BlockCommitment结构体文档称这一步是平凡的把累加函数f替换为f使其同时构建每个 bank 的BlockCommitment。文档强调了一个关键观察对某个验证者的投票账户a其在本 slots上的本地确认数就是v.num_confirmations其中v是投票栈a.votes中满足v.slot s的最老一笔投票——因为再往上看更晚的投票确认数只会更小无需考虑。据此增强后的计算文档原文为let output: HashMapb, Stake HashMap::new(); let fork_commitment_cache ForkCommitmentCache::default(); for vote_account in b.vote_accounts { // vote stack is sorted from oldest vote to newest vote for (v1, v2) in vote_account.vote_stack.windows(2) { for a in ancestors(v1).difference(ancestors(v2)) { f(*output.get_mut(a), *fork_commitment_cache.get_mut(a), vote_account, v); } } }其中f定义为fn f( stake: mut Stake, some_ancestor: mut BlockCommitment, vote_account: VoteAccount, v: Vote, total_stake: u64 ){ f(stake, vote_account, v); *some_ancestor.commitment[v.num_confirmations] vote_account.stake; }即f先执行原有的锁存累加f再把该投票账户的全部 stake 计入祖先 slot 对应确认级别v.num_confirmations的确认度桶中。用vote_stack.windows(2)做相邻投票的祖先集差集正是为了把每个祖先 slot 归给覆盖它的最老投票与上面最老一笔v.slot s的投票的语义一一对应。当前实现AggregateCommitmentService 如何落地该算法上述增强算法在仓库中的真实载体是 core/src/commitment_service.rs 中的AggregateCommitmentService提案中的ForkCommitmentCache对应实现即 BlockCommitmentCache。入口与调度AggregateCommitmentService::new在名为solAggCommitSvc的独立线程中循环接收CommitmentAggregationData{bank, root, total_stake}每收到一份数据取出bank.status_cache_ancestors()对应文档中的ancestors约束调用update_commitment_cache重建全局BlockCommitmentCache并通过subscriptions.notify_subscribers(...)触发 RPC 订阅通知见 commitment_service.rs。聚合主体aggregate_commitmentcommitment_service.rs断言 ancestors 已按升序排列后遍历bank.vote_accounts()对每个 stake 非零的投票账户调用aggregate_commitment_for_vote_account——这正是文档中f语义的展开fn aggregate_commitment_for_vote_account( commitment: mut HashMapSlot, BlockCommitment, rooted_stake: mut Vec(Slot, u64), vote_state: VoteState, ancestors: [Slot], lamports: u64, ) { let mut ancestors_index 0; // 1) root 之前的祖先全部记为已 rooted对应 increase_rooted_stake if let Some(root) vote_state.root_slot { for (i, a) in ancestors.iter().enumerate() { if *a root { commitment.entry(*a).or_default().increase_rooted_stake(lamports); } else { ancestors_index i; break; } } rooted_stake.push((root, lamports)); } // 2) root 之后的祖先找到覆盖它的最老投票按其确认数记账 for vote in vote_state.votes { while ancestors[ancestors_index] vote.slot() { commitment.entry(ancestors[ancestors_index]).or_default() .increase_confirmation_stake(vote.confirmation_count() as usize, lamports); ancestors_index 1; if ancestors_index ancestors.len() { return; } } } }对照文档可以看出对应关系双指针ancestors_index沿升序 ancestors 推进while ancestors[ancestors_index] vote.slot()保证每个祖先 slot 恰好被第一个 slot 不小于它的投票记账一次——这实现了取满足v.slot s的最老投票v按其确认数累加 stake的规则同时把f共识锁存与确认度分布累加合并在同一轮遍历中完成。超级多数 rootget_highest_super_majority_rootcommitment_service.rs把所有投票账户的(root_slot, stake)按 slot 降序累加直到超过VOTE_THRESHOLD_SIZEruntime/src/commitment.rs 定义为 2/3时返回该 root用于填充CommitmentSlots.highest_super_majority_root并保证缓存值单调不减max(new, old)见 update_commitment_cache。从原始 stake 到客户端确认度阈值与 CommitmentSlots拿到HashMapSlot, BlockCommitment后BlockCommitmentCache 提供把stake 分布翻译回确认级别的读接口get_lockout_count(slot, minimum_stake_percentage)从高确认桶到低确认桶反向累加 stake返回首个使累计 stake 超过总 stake 的minimum_stake_percentage的最低确认级别。get_confirmation_count固定使用VOTE_THRESHOLD_SIZE2/3作为门槛calculate_highest_confirmed_slot()即highest_slot_with_confirmation_count(1)从当前 slot 向下找第一个达到 1 次确认2/3 stake的 slot找不到则回落到 rootslot_with_commitment(commitment_level)把 SDK 中的CommitmentLevel映射为具体 slot——Processed/Recent返回slot()Root返回root()Single/Confirmed/SingleGossip返回highest_confirmed_slotMax/Finalized返回highest_super_majority_root。这套映射就是客户端发起 RPC 时传入processed / confirmed / finalized配置后节点选择用哪个 bank 应答的内部依据。测试如何验证整套语义仓库中与该机制直接相关的测试可分三层引用数据结构层runtime/src/commitment.rstest_get_confirmations构造 total_stake50、slot 0/1/2 的确认 stake 分布验证get_confirmation_count分别返回Some(2)、Some(1)、Some(0)窗口外返回Nonetest_highest_confirmed_slot覆盖多 slot 同级别确认、slot 空洞gap、无 1 次确认但有更高确认等边界验证calculate_highest_confirmed_slot的回退行为。聚合算法层core/src/commitment_service.rstest_aggregate_commitment_for_vote_account_1/2/3用固定的 ancestors 序列如[3, 4, 5, 7, 9, 11]逐一验证root 之前计 rooted、root 之后按最老覆盖投票的确认数计 stake的规则test_aggregate_commitment_validity在真实Bank上放置 4 个不同 stake 权重100/50/40/40与不同投票状态的验证者端到端核对每个 ancestor 桶的期望值并断言get_highest_super_majority_root结果。状态推进层test_highest_super_majority_root_advancecommitment_service.rs构造连续 33 个 bank 的投票推进 root再引入分叉 bank验证highest_super_majority_root在分叉后能正确回滚并随后续投票继续前进。小结与关键文件速查Commitment 机制的设计精髓在于零额外共识开销确认度分布完全寄生在每轮共识本就要做的投票锁存汇总上f只是在原累加函数上多写一个数组桶。客户端则持有每个确认级别上的 stake 原始分布 total_stake可以按自己的风险偏好换算出确认度百分比而非依赖节点给出的单一标签。关注点文件设计文档本文主体docs/src/implemented-proposals/commitment.md客户端确认度三级定义docs/src/consensus/commitments.mdBlockCommitment/BlockCommitmentCache/CommitmentSlotsruntime/src/commitment.rs聚合服务f的真实落地core/src/commitment_service.rs被复用的共识计算collect_vote_lockoutscore/src/consensus.rsgetBlockCommitmentRPCrpc/src/rpc.rsMAX_LOCKOUT_HISTORY 31sdk/program/src/vote/state/mod.rs【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考