
Solana 综合计算费用提案解析从签名计费到基于计算单元的全面费用模型【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana本篇技术指南深度解析 Solana本仓库官方设计提案《Comprehensive Compute Fees》的核心思路以计算单元Compute Unit为统一度量衡重构交易费用评估与区块调度体系。你将理解费用从仅按签名数计费演进为涵盖签名验证、写锁、指令数据、账户大小、计算预算、预编译程序六大维度的综合模型并看到该提案在cost-model、program-runtime、accounts-db、runtime等模块中的真实落地实现以及确定性费用deterministic fees机制的演进方向。提案背景为什么签名数不足以衡量验证者工作量Solana 最初的费用结构只基于交易中的签名数量number of signatures其设计意图本是用它来近似验证者处理这笔交易需要付出的工作量。但 提案原文 明确指出验证者实际承担的用户自定义工作远不止签名验证。处理一笔典型交易通常包括签名验证signature verification账户加锁account locking账户加载account loading指令处理instruction processing仅凭签名数量无法反映交易在指令复杂度、写账户数量、加载数据量上的真实开销也就无法为每笔交易究竟消耗了区块多少处理能力提供准确依据。提案总体方案把费用拆解为可度量的计算单元提案的解决方案并不直接规定原生代币SOL/lamports与每种费用类别的兑换单价而是设定评判标准并提供成本模型cost model可用的旋钮knobs——也就是把费用拆解为若干类计算单元成本相加即得到处理该笔交易的整体成本。有了这个总成本运行时runtime就可以收取更有代表性的费用做出更好的交易调度决策决定哪些交易进入同一批次/区块。费用的六大计算维度提案给出了费用可基于以下要素计算的完整清单#费用类别计费方式1签名数量每个签名固定费率2写锁数量每个可写账户固定费率3数据字节成本交易所有指令数据长度总和 × 每字节固定费率4账户大小无法预先得知先按最大账户大小10m预收实际大小确定后退还差额5计算预算默认 200k 单元可通过计算预算指令请求更高上限最高 1m 单元先按默认/请求值预收处理结束后按实际消耗退还差额只用多少付多少内置程序builtin固定成本SBF 程序运行时实测6预编译程序预编译程序执行计算密集型操作其工作量可基于指令数据数组解析预测按程序分别定价因在 bank 外部处理其计算成本不计入计算预算也不参与交易调度决策各组成部分固定成本的测定方法在 issue #19627 中描述见下文从提案到实现。成本模型Cost Model调度决策的核心输入提案定义的成本模型用于评估交易在槽内in-slot处理期间会给集群带来多少负载并据此决定如何把交易最佳地分批batch调度。成本模型的评判标准与费用标准几乎完全一致但排除签名和预编译程序。原因很关键这两项成本发生在交易被调度之前签名校验在入口处完成预编译程序在 bank 之外执行因此不会影响交易在槽内的处理时长也就无需参与调度决策。实现一CostModel 的六项成本核算提案在 cost-model/src/cost_model.rs 中完整落地核心入口是CostModel::calculate_cost返回TransactionCost。它依次核算签名成本普通签名、secp256k1 指令签名、ed25519 指令签名分别乘以固定单价SIGNATURE_COST、SECP256K1_VERIFY_COST、ED25519_VERIFY_COST见 cost_model.rs写锁成本WRITE_LOCK_UNITS × 可写账户数。有一个 feature 开关cost_model_requested_write_lock_cost开启后按请求的写锁数计费而非实际降级后的可写账户数这解决了系统程序等不可写账户被降级demote导致成本低估的问题对应测试见 cost_model.rs执行成本遍历指令内置程序查BUILT_IN_INSTRUCTION_COSTS表取固定值SBF 程序按DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT估算上限截断到MAX_COMPUTE_UNIT_LIMIT若交易显式设置了SetComputeUnitLimit则以其为执行成本若计算预算指令解析失败交易不会被执行成本按 0 计见 cost_model.rs数据字节成本指令数据总长度除以INSTRUCTION_DATA_BYTES_COST账户数据大小成本由系统程序的CreateAccount/Allocate等指令反序列化得到space字段估算见 cost_model.rs加载账户数据大小成本受 featureinclude_loaded_accounts_data_size_in_fee_calculation控制按加载上限与堆成本调用FeeStructure::calculate_memory_usage_cost计算。实现二单位换算常量与区块级限额提案中固定费率上限等参数在 cost-model/src/block_cost_limits.rs 中统一定义COMPUTE_UNIT_TO_US_RATIO 30集群平均计算单元 → 微秒换算率SIGNATURE_COST 30 × 24 720、SECP256K1_VERIFY_COST 30 × 223 6690、ED25519_VERIFY_COST 30 × 76 2280WRITE_LOCK_UNITS 30 × 10 300INSTRUCTION_DATA_BYTES_COST 140 / 30 4每 4 字节指令数据 ≈ 1 计算单元内置程序固定成本表BUILT_IN_INSTRUCTION_COSTS覆盖 stake、config、vote、system、compute-budget、address-lookup-table、bpf-loaderupgradeable/deprecated/默认、loader-v4其中 secp256k1 与 ed25519 预编译程序成本为 0其在 bank 之外执行区块级限额MAX_BLOCK_UNITS 48_000_000400ms 回放 × 30 × 并发 4、MAX_WRITABLE_ACCOUNT_UNITS 12_000_000、MAX_VOTE_UNITS 36_000_000为普通交易预留 25%、MAX_BLOCK_ACCOUNTS_DATA_SIZE_DELTA 100_000_000每区块账户数据增长上限均有const_assert_eq!静态断言校验。实现三CostTracker 的调度准入控制cost-model/src/cost_tracker.rs 把成本模型与区块调度连接起来。CostTracker维护cost_by_writable_accounts按可写账户记账、block_cost、vote_cost、account_data_size等状态提供would_fit(tx_cost)判断交易是否放得下当前区块依次检查投票限额、区块总额限、单账户成本上限、账户数据增量上限以及每个可写账户的链式累计成本是否超限防止过多交易写同一账户而降低并行度try_add通过检查后原子累加成本test_cost_tracker_try_add_is_atomic验证失败时状态回滚update_execution_cost交易执行完毕后用实际执行单元数修正估算值多退少补错误类型映射为对应TransactionErrorWouldExceedMaxBlockCostLimit、WouldExceedMaxVoteCostLimit、WouldExceedMaxAccountCostLimit、WouldExceedAccountDataBlockLimit等。在 core/src/banking_stage 中CostModel::calculate_cost被调度控制器scheduler_controller.rs、QoS 服务qos_service.rs以及按账户转发批次逻辑forward_packet_batches_by_accounts.rs大量调用——这正是提案所说的更佳的交易调度决策。实现四简单投票交易的静态成本投票交易vote transaction结构固定1~2 个签名、2 个写锁、1 条投票指令、加载账户数据不足一页因此 transaction_cost.rs 将其成本静态定为SIMPLE_VOTE_USAGE_COST 3428并以内联断言约束其等于投票指令默认单元 1 个签名成本 2 个写锁成本 8 单元加载成本。测试test_vote_transaction_cost验证投票交易成本恒为 3428而同一笔交易若按普通交易计算则为 20535。计算预算可请求的交易级 CU 上限与堆大小提案明确指出可通过预编译的ComputeBudget程序请求更高的交易级计算预算上限与程序堆大小且请求的提升会反映在交易费用中。计算预算指令集在 sdk/src/compute_budget.rs 中定义了ComputeBudgetInstruction枚举程序 ID 为ComputeBudget111111111111111111111111111111RequestHeapFrame(u32)请求交易级程序堆区大小必须为 1024 的倍数作用于交易内每个程序及所有 CPI 调用SetComputeUnitLimit(u32)设置交易允许消耗的计算单元上限SetComputeUnitPrice(u64)以微 lamports为单位设置计算单元单价支付更高交易费以获得更高优先级SetLoadedAccountsDataSizeLimit(u32)设置交易可加载的账户数据总大小上限。对应便捷构造方法request_heap_frame、set_compute_unit_limit、set_compute_unit_price、set_loaded_accounts_data_size_limit均以 borsh 序列化。预算的解析与上限program-runtime/src/compute_budget_processor.rs 是预算处理核心DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT 200_000每条非计算预算指令的默认预算提案中的默认 200k 单元即指每条指令的默认与交易内指令数相乘得交易预算MAX_COMPUTE_UNIT_LIMIT 1_400_000交易级计算单元上限提案中的 1m 上限在实现中演进为 1.4mMAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES 64MiB单交易可加载账户数据上限MAX_HEAP_FRAME_BYTES 256KiB堆帧最大值sanitize_requested_heap_size校验 1024 倍数与上下界process_compute_budget_instructions在交易清洗sanitizing阶段解析预算指令重复指令返回DuplicateInstruction错误非法数据返回InvalidInstructionData处理失败则交易直接丢弃、不会执行。预算如何映射为费用预算上限最终通过ComputeBudgetLimits → FeeBudgetLimits见 compute_budget_processor.rs进入费用计算其中优先级费用由 program-runtime/src/prioritization_fee.rs 计算compute_unit_price × compute_unit_limit微 lamports向上取整为 lamports。费用结构定义在 sdk/src/fee.rs 的FeeStructurelamports_per_signature默认 0.000005 SOL、lamports_per_write_lock、compute_fee_bins按 CU 上限分档定价的费用仓calculate_fee_details汇总签名费、写锁费、计算费按loaded_accounts_data_size_cost compute_unit_limit落入对应FeeBin再加上优先级费用得到FeeDetailscalculate_memory_usage_cost按 32KiB 一页ACCOUNT_DATA_COST_PAGE_SIZE× 堆成本DEFAULT_HEAP_COST 8计费测试fee.rs验证了不足一页按一页计的取整行为。缓存账户大小避免按最大值预收的浪费提案提出账户大小无法预先获知先按最大账户大小10m预收、结算后退差会带来不必要的资金占用与复杂度因此规划了缓存账户大小并使用缓存值替代最大值的优化对应 issue #20511。其后续实现思路是让验证者复用账户数据缓存见 accounts-db 中的缓存与哈希基础设施来提前获知实际账户大小从而让成本估算更贴近真实负载。预编译程序失败的费用处理预编译程序secp256k1、ed25519在 bank 外部执行其失败是否收费、按何标准收费属于提案规划中的独立议题对应 issue #20481。从当前实现看block_cost_limits.rs 将两个预编译程序的执行成本记为 0但其指令签名验证成本SECP256K1_VERIFY_COST/ED25519_VERIFY_COST仍计入签名成本——签名验证发生在调度前无论程序执行成败都已发生这与提案签名成本不计入槽内调度的设计一致。费率治理Rate Governing的重新评估提案指出当前费率治理的问题按签名数治理会把费率压到下限因为每个槽内的实际签名数远低于目标签名数/槽。新的治理方向是不再用签名数而是由成本模型反馈其观察到的批次/队列负载。费用停留在目标费率仅当负载超过某个待定的阈值时才上调治理将作用于所有费用类别而非仅签名维度。从源码看旧机制的载体是 sdk/program/src/fee_calculator.rs 的FeeRateGovernortarget_lamports_per_signature默认 10_000、target_signatures_per_slot默认 50 × 每槽毫秒数、min/max_lamports_per_signature目标值的 50%~1000%、burn_percent默认 50% 费用销毁new_derived依据latest_signatures_per_slot / target_signatures_per_slot的比例以目标值的 5% 为步长平滑调整lamports_per_signature。这正是提案所批评的基于签名数的治理而基于CostTracker负载反馈的新治理机制在该提案框架下被设计为替代方案。确定性费用Deterministic Fees保留还是废除Solana 的费用目前基于给定 blockhash 是确定性的这一特性极大简化了客户端交互清空同时作为 payer 的账户时可预先算好费用并把剩余余额全部转出不用担心费用变化导致账户残留极小余额离线签名时payer 可根据 nonce 的 blockhash 保证将被收取的费用。确定性从何而来确定性通过两条路径实现Blockhash 队列包含最近约 2 分钟内的 blockhash 列表及各自对应的lamports_per_signature值。队列是快照的序列化成员之一因此bank hash 依赖它。实现见 accounts-db/src/blockhash_queue.rsHashAge结构体持有fee_calculatorget_lamports_per_signature按交易 blockhash 查表register_hash/genesis_hash在注册时写入当时的费率Nonce 账户用于离线签名的 nonce 账户在其账户数据中保存lamports_per_signature见 sdk/src/nonce_account.rs 的lamports_per_signature_of。两种情况下评估费用时都用交易的 blockhash 查出应使用的lamports_per_signature。演进的三重挑战FeeCalculator暴露给客户端它持有lamports_per_signature任何费用标准的演进都会因向后兼容backward-compatibility而变得困难。解法是弃用FeeCalculator对象新 API 改为传入 message、返回费用见 fee_calculator.rs 中calculate_fee已标记#[deprecated(since 1.9.0)]Blockhash 队列条目内嵌费用标准条目属于 bank hash 的一部分费用标准演进涉及快照/银行哈希的更多工作与风险Nonce 账户数据内嵌费用标准演进需要修改 nonce 账户数据结构与大小。两条解决路线路线 A废除确定性费用。客户端通过 RPC 请求当前费用估算实际费用在交易处理时评估费用变化受治理约束、随网络负载缓慢变化2 分钟窗口内差异很小。Nonce 账户不再存储费用标准改为存储费用上限fee cap——处理时评估的费用若超过上限则交易失败。此路线将费用标准完全移出 blockhash 队列与 nonce 账户二者的数据结构在未来费用标准演进时无需任何变更。路线 B保留确定性费用。客户端通过 RPC 计算当前费用并传入一个与之关联的 blockhash。Blockhash 队列与 nonce 账户改用版本化但内部化的Fee对象类似FeeCalculator每次费用标准演进时新增一个版本新产生的 blockhash 队列条目与新 nonce 账户使用新版本。从提案到实现一个仍在演进的架构综合来看这份提案并非一次性完成的重写而是分阶段、带 feature gate 的渐进式演进。从当前仓库源码结构可以梳理出以下落地事实成本模型模块独立成 crate cost-model以CostModel估成本CostTracker准入控制block_cost_limits常量/限额三个文件形成完整闭环调度侧接入banking stage 的 QoS 与调度控制器在把交易放入区块前都调用CostModel::calculate_cost并将结果交给CostTracker::try_add做多维度限额检查费用计算侧FeeStructuresdk/src/fee.rs已经包含每签名 每写锁 计算单元分档 优先级费的多元结构已远超旧的仅按签名 ×lamports_per_signature模型若干演进受 feature gate 控制include_loaded_accounts_data_size_in_fee_calculation#30657账户加载数据计入基础费用、cost_model_requested_write_lock_cost#34819成本模型按请求写锁计费、remove_rounding_in_fee_calculation#34982移除费用计算中的不必要取整均定义在 sdk/src/feature_set.rs测试通过开关 feature 验证新旧行为见 cost_model.rs。因此阅读本提案时应将其视为 Solana 费用体系演进的路线图其中六大费用维度 成本模型驱动调度 费率治理改革 确定性费用再设计的主体框架已落地而账户大小缓存替代最大值预收预编译失败费用负载反馈型费率治理等条目仍在推进中。进一步阅读提案原文docs/src/proposals/comprehensive-compute-fees.md成本模型核心实现cost-model/src/cost_model.rs、cost-model/src/cost_tracker.rs、cost-model/src/block_cost_limits.rs、cost-model/src/transaction_cost.rs计算预算指令与处理sdk/src/compute_budget.rs、program-runtime/src/compute_budget_processor.rs费用结构sdk/src/fee.rs、sdk/program/src/fee_calculator.rs、program-runtime/src/prioritization_fee.rs确定性费用的 blockhash 队列载体accounts-db/src/blockhash_queue.rs调度侧调用点示例core/src/banking_stage/transaction_scheduler/scheduler_controller.rs【免费下载链接】solanaWeb-Scale Blockchain for fast, secure, scalable, decentralized apps and marketplaces.项目地址: https://gitcode.com/GitHub_Trending/so/solana创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考