资讯详情

Solana 分区通胀奖励分发(Partitioned Inflationary Rewards Distribution)设计解析:从 epoch 边界奖励到计算/入账两阶段架构

📅 2026/9/15 1:25:03 | 华诺云谱 👁 阅读
Solana 分区通胀奖励分发(Partitioned Inflationary Rewards Distribution)设计解析:从 epoch 边界奖励到计算/入账两阶段架构
Solana 分区通胀奖励分发Partitioned Inflationary Rewards Distribution设计解析从 epoch 边界奖励到计算/入账两阶段架构【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana导读本文基于 Solana 仓库中的设计提案 partitioned-inflationary-rewards-distribution.md系统讲解 Solana 如何解决 epoch 边界上质押stake奖励计算与入账带来的性能瓶颈将原本在 epoch 边界一次性完成的计算 入账拆分为奖励计算阶段calculating interval与奖励入账阶段credit interval并引入确定性分区、请求签名去重、奖励区间读写限制等机制。读完本文你将理解该方案的分区划分算法、关键区间参数N 与 M的选取依据、六大工程挑战及对应规避策略并能在当前仓库的 runtime/src/bank.rs 与 accounts-db/src/partitioned_rewards.rs 中找到该提案的落地实现。一、问题背景epoch 边界上的奖励计算正在拖慢网络Solana 的通货膨胀奖励inflation rewards在每个 epoch 边界的第一批区块统一结算验证节点需要在 epoch 起始区块计算所有质押账户的应得奖励并把奖励写入对应的 stake 账户。提案文档明确指出这一逻辑随着质押账户数量增长而急剧恶化当质押账户规模达到55 万550K时单次奖励计算耗时已经超过 10 秒长时间的计算会拖慢网络出块节奏并在 epoch 边界诱发大量分叉forks进一步恶化局面。由此提案的核心目标是把算奖励与发奖励这两个动作从 epoch 边界的单点时间窗口里拆出来让网络不必在边界上一次性承受全部计算与写入压力。从源码结构看该方案最终落地为仓库中的分区奖励代码路径partitioned rewards code path受 feature flagenable_partitioned_epoch_reward控制见 runtime/src/bank.rs 中的is_partitioned_rewards_feature_enabled()fn is_partitioned_rewards_feature_enabled(self) - bool { self.feature_set .is_active(feature_set::enable_partitioned_epoch_reward::id()) }二、总体方案将奖励计算与入账解耦为两个阶段提案提出不再于 epoch 边界一次性完成全部奖励工作而是拆成两个彼此独立的阶段奖励计算阶段reward computation phase每个跨过 epoch 边界的区块bank 都会向一个独立的EpochRewardCalculationService发送奖励计算请求由后台服务执行计算奖励入账阶段reward credit phase当区块高度推进到计算阶段起始点之后的第N个区块时bank 开始向后台服务按请求签名查询奖励结果结果为accounts_pubkey - rewards的映射并在随后的M个区块内分批把奖励写入各 stake 账户。提案用如下分叉示意图说明多分叉场景下的去重需求N-1 -- N -- N1 \ \ N2其中N是新 epoch 的起始槽位。槽位N与槽位N2都跨过 epoch 边界且处于不同分叉因此会各产生一次计算请求。为避免相同输入被重复计算每个请求都带有一个请求签名signaturehash(epoch_number, hash(stake_accounts_data), hash(vote_accounts), hash(delegation_map))若槽位N与槽位N2之间没有质押/投票账户发生变化则两次请求的签名相同第二次请求会被直接丢弃该签名既是去重键也是后续查询奖励结果的凭证。从当前仓库实现看实际落地对后台异步服务做了更务实的收敛奖励计算默认在 epoch 边界后的第一个区块内同步完成见下文reward_calculation_num_blocks默认值为 1而奖励入账则确实按分区在后续多个区块中分批进行与提案分区入账的核心思想保持一致。三、两个关键区间计算区间、入账区间与奖励区间提案用区块高度精确定义了两个阶段对应的时间窗口区间名称区间范围含义计算区间calculating interval[epoch_start, epoch_start N]后台完成奖励计算保证区间结束时结果可用入账区间credit interval[epoch_start N 1, epoch_start N M]每块向1/M的账户写入奖励奖励区间rewarding interval[epoch_start, epoch_start N M]上述两区间的并集3.1 计算区间长度N的选取N必须足够大确保后台计算在计算区间结束时已完成、结果可用。提案给出两种选择固定值如N 100粗略等价于 50 秒Solana 每槽 400ms~500ms 量级100 槽约 50 秒动态函数N f(num_stake_accounts)随质押账户数量增长而扩展。3.2 入账区间长度M与账户分区算法入账区间的核心思想是把全部质押账户划分为M个分区partitions每个区块处理1/M的账户。分区必须同时满足两个看似矛盾的性质当前 epoch 内确定性同一 epoch 内所有验证节点算出的分区必须完全一致否则共识分裂跨 epoch 随机性不同 epoch 之间的分区边界必须变化避免同一批账户长期集中在同一区块被写入。提案给出的实现路径是将账户 pubkey 与若干 epoch 相关值epoch 编号、该 epoch 总奖励额、该 epoch 区块的 leader pubkey 等做哈希对哈希结果排序后均匀切分为M个 bin。M的基准建议是每块处理 5 万个账户M ceil(num_stake_accounts / 50,000)此外num_stake_accounts必须取自leader_schedule_epoch区块即该 epoch 的 leader schedule 所基于的区块而不是边界时刻的实时账户数——否则会出现边界前新交易在一个分叉上产生X个质押账户、另一分叉上产生Y个质押账户的分歧破坏共识。四、源码级印证分区奖励配置与分区入账的落地实现4.1PartitionedEpochRewardsConfig核心参数的真实默认值仓库中 accounts-db/src/partitioned_rewards.rs 定义了PartitionedEpochRewardsConfig直接对应提案中的N、M等概念并给出了生产默认值字段默认值说明reward_calculation_num_blocks1奖励计算阶段占用的区块数默认在 epoch 边界第一个区块同步完成计算随后才开始入账stake_account_stores_per_block4096每个入账区块最多写入的质押账户数test_enable_partitioned_rewardsfalse测试用强制启用分区奖励路径test_compare_partitioned_epoch_rewardsfalse测试用对比旧路径与新分区路径的计算结果其中stake_account_stores_per_block 4096的来历在源码注释中有明确说明目标是在每个 entry/tick 中写入 64 个奖励而一个区块至少包含 64 个 entry/tick因此一个区块可写入 64 × 64 4096 个奖励。该常量直接影响共识consensus因为它决定了奖励入账分布在多少个区块上。相比提案中每块 5 万账户的粗粒度建议生产实现选择了更细的4096粒度以降低单块负载这正体现了保留分区思想、按实测调优的演进。4.2 测试模式TestPartitionedEpochRewards同一文件还提供了四种测试模式便于在 feature 激活前后验证行为一致性None默认配置CompareResults先按旧路径正常分发再运行分区代码并比对结果set_test_compare_partitioned_epoch_rewardsForcePartitionedEpochRewardsInOneBlockreward_calculation_num_blocks 0、stake_account_stores_per_block u64::MAX即所有奖励在 epoch 第一个区块全部入账但完整跑一遍分区代码路径用于验证与旧逻辑共识一致PartitionedEpochRewardsConfigRewardBlocks自定义计算区块数与每块账户数用于压力/边界测试。4.3StakeReward数据结构分区奖励的单元在 accounts-db/src/stake_rewards.rs 中定义为pub struct StakeReward { pub stake_pubkey: Pubkey, pub stake_reward_info: RewardInfo, pub stake_account: AccountSharedData, }它同时实现了StorableAccountstrait使一批StakeReward可直接批量写入 accounts-db无需中间拷贝或构建临时 Vec。4.4 Bank 中的状态机与 sysvar在 runtime/src/bank.rs 中分区奖励的推进由以下核心构件支撑RewardInterval枚举L928-L934InsideInterval当前槽位于奖励分发区间内/OutsideInterval区间外由get_reward_interval()L1178-L1184依据epoch_reward_status判定EpochRewardStatus状态L631-L636Active(StartBlockHeightAndRewards)状态下记录起始区块高度与按分区切分好的stake_rewards_by_partitionbegin_partitioned_rewards()L1625-L1663epoch 边界调用的入口先执行calculate_rewards_and_distribute_vote_rewards完成计算并分发投票奖励随后设置epoch_reward_status为Active并创建EpochRewardssysvar记录(total_rewards, distributed_rewards, credit_end_exclusive)用于对外暴露未分发奖励的余额入账区块数上限get_reward_distribution_num_blocks()L1158-L1175把总入账区块数限制在epoch 总槽数的 10% 以内MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH 10防止奖励区间过长侵占正常交易空间。4.5 测试验证runtime/src/bank/tests.rs 中包含大量针对分区奖励的测试如第 12243、12295、12554、12668 行附近断言奖励区间内外get_reward_interval()返回InsideInterval/OutsideInterval的正确性并对快照序列化时EpochRewardStatus的状态保持做了专门校验可据此深入验证本文所述的各阶段行为。五、降低奖励区间开销计算预算压缩为避免奖励区间内的区块在计算 入账之外再背上额外负载提案建议压缩奖励区间内区块的计算预算compute budget上限为质押奖励的读取与写入预留出计算和读写容量。这一设计本质上是在网络吞吐与奖励结算之间做资源再分配牺牲奖励区间内一部分普通交易的可用预算换取奖励结算不拖垮出块节奏。六、六大挑战与规避策略挑战 1奖励区间内的质押账户读写由于奖励延迟入账用户在奖励区间内读取质押账户时拿到的余额不包含本 epoch 的奖励而在epoch_start N M区块统一入账后区间内对该账户的写入又会被覆盖。因此runtime 必须限制奖励区间内对质押账户的读写任何涉及质押账户的交易将返回新的执行错误stake rewards pending, account access is restricted常规 RPC 查询如getBalance仍返回账户当前 lamport 余额用户应预期奖励在奖励区间内某个时点入账。挑战 2奖励区间内的投票交易奖励区间内投票交易必须正常处理否则共识无法推进、根区块无法确认。但投票交易可能改变投票账户余额例如投票账户与区块奖励收款账户相同时会支付投票交易费若发生在 epoch 奖励入账之前这部分区块奖励会被陈旧的缓存值抹除。规避手段是强制要求vote_account与authorized_voterauthority 必须是不同账户。挑战 3奖励区间内的快照若在奖励区间内拍摄快照快照会缺失质押账户的奖励从这些快照直接重启的验证节点结果将是错误的除非从最近 epoch 边界重建奖励这会显著增加重启复杂度。首个实现版本采取的做法是奖励区间内强制不拍快照、不执行账户哈希计算——增量快照请求直接跳过全量快照请求重新排队、待到奖励区间结束后再处理。未来如有需要可再放开。挑战 4奖励区间内的 accounts-db 相关操作flush、clean、squash、shrink等 accounts-db 操作可能在奖励区间内把质押账户从缓存中换出evict导致后续在epoch_start N区块入账时变慢。首个实现为求简单保持 accounts-db 行为不变转而扩大入账区间M来吸收写回性能损耗未来再针对奖励区间内的 accounts-db 动作做专项调优。挑战 5总 epoch 资本化capitalization视图的变化此前总 epoch 资本化在每个 epoch 边界即可获得引入奖励区间后只有在奖励区间结束之后才能看到包含全部奖励的资本化总值。任何依赖总 epoch 资本化的第三方应用逻辑都必须等待奖励区间结束。挑战 6getInflationRewardRPC 与新增getRewardInterval改动前getInflationRewardJSON-RPC 只需抓取目标 epoch 的第一个区块查找目标质押账户的奖励条目即可返回。改动后调用逻辑需升级为先推导目标质押账户的入账区块credit block抓取该区块再查找奖励条目对奖励锁定期内lockout period即奖励尚未入账的查询需要返回更明确的错误信息让用户知道目标 epoch 的奖励仍在待发放状态新增 RPC 方法getRewardInterval用于查询当前 epoch 的奖励区间范围。在仓库中get_inflation_reward的 RPC 实现在 rpc/src/rpc.rs而当前 bank 是否处于奖励区间内的判定逻辑已由get_reward_interval()提供可作为getRewardInterval底层判定的实现依据。七、总结分区通胀奖励分发方案通过计算/入账两阶段解耦 确定性分区 请求签名去重三重设计把 epoch 边界上单点爆发的奖励计算压力摊平到后续多个区块同时用签名哈希去重解决多分叉下的重复计算问题用pubkey 哈希排序分 bin兼顾分区的 epoch 内确定性与跨 epoch 随机性。它付出的代价是奖励可见性延迟一个奖励区间并引入了对质押账户读写、投票账户权限、快照、accounts-db 操作、RPC 语义等多方面的约束——这六项挑战在提案中均有明确的规避策略并在当前仓库的 accounts-db/src/partitioned_rewards.rs、accounts-db/src/stake_rewards.rs、runtime/src/bank.rs 与 rpc/src/rpc.rs 中形成了可对照阅读的实现与测试。需要说明的是本文描述的是设计提案及其在仓库中的落地形态reward_calculation_num_blocks、stake_account_stores_per_block等参数的具体取值属于当前仓库默认值随 feature 激活与共识变更可能演进实际行为以链上 feature 状态与验证节点配置为准。【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。