Solana交易手续费机制深度分析
背景分析
Solana采用了计算单元(Compute Units)的概念来衡量和控制交易的资源使用。
SetComputeUnitPrice等指令 FeeCalc->>FeeCalc: 计算费用组成部分 Note over FeeCalc: 签名费用 = 签名数量 × lamports_per_signature
写锁费用 = 写锁数量 × lamports_per_write_lock
计算费用 = 根据compute_fee_bins确定
优先级费用 = 计算单元限制 × 单元价格 FeeCalc->>Banking: 返回总费用 Banking->>AccountLoader: 加载账户并验证费用支付能力 AccountLoader->>AccountLoader: 验证费用支付者账户 Note over AccountLoader: 检查账户余额是否足够支付费用
从支付者账户扣除费用 alt 费用支付成功 AccountLoader->>Banking: 返回已加载的交易 Banking->>Bank: 执行交易 Bank->>Bank: 处理交易逻辑 Bank->>Banking: 返回执行结果 Banking->>Validator: 收集费用 Note over Validator: 费用分配:
销毁费用 = 总费用 × burn_percent
验证者收益 = 总费用 - 销毁费用 Validator->>Client: 返回交易确认 else 费用支付失败 AccountLoader->>Banking: 返回InsufficientFundsForFee错误 Banking->>Client: 返回交易失败 end
- 交易提交阶段:客户端向TPU提交包含计算预算指令的交易
- 费用计算阶段:系统解析计算预算指令并计算各项费用
- 账户验证阶段:验证费用支付者账户并扣除费用
- 交易执行阶段:在Bank中执行交易逻辑
- 费用分配阶段:按照burn_percent分配费用给验证者和销毁
Solana手续费模型概述
在Solana中,交易手续费以lamports为单位计算,其中1 SOL = 10^9 lamports。 手续费结构包含多个组成部分:
- 基础签名费用 - 每个签名固定收费
- 写锁费用 - 对需要写入的账户收费
- 计算费用 - 基于计算单元使用量
- 优先级费用 - 用户可选的额外费用以提高交易优先级
计算单元(Compute Units)机制
计算预算指令
Solana提供了专门的计算预算指令来管理资源使用:
SetComputeUnitLimit
- 设置交易允许消耗的最大计算单元数SetComputeUnitPrice
- 设置每计算单元的价格(以微lamports为单位)
默认计算单元限制
系统设定了默认的计算单元限制:
- 单个指令默认限制:200,000 CU
- 交易最大限制:1,400,000 CU
手续费计算公式
基础费用计算
费用计算包含以下组成部分:
总费用 = 签名费用 + 写锁费用 + 计算费用 + 优先级费用
具体计算公式:
- 签名费用 = 签名数量 × 每签名lamports费用
- 写锁费用 = 写锁数量 × 每写锁lamports费用
- 计算费用 = 根据计算单元区间确定的固定费用
- 优先级费用 = 计算单元限制 × 计算单元价格(微lamports)
动态费用调整
Solana通过FeeRateGovernor实现动态费用调整:
动态调整公式:
- 最小费用 = 目标费用 ÷ 2
- 最大费用 = 目标费用 × 10
- 调整幅度 = 目标费用 ÷ 20(每次调整5%)
根据网络负载自动调整每签名的费用,调整范围为目标费用的50%-1000%。
不同操作的计算单元消耗
基础操作成本
Solana为各种操作预定义了计算单元消耗:
常见操作成本表:
- 日志记录:100 CU
- 创建程序地址:1,500 CU
- 程序调用:1,000 CU
- SHA256基础费用:85 CU
- secp256k1恢复:25,000 CU
加密操作成本
椭圆曲线操作成本:
- curve25519点验证:159-169 CU
- 点加法:473-521 CU
- 点乘法:2,177-2,208 CU
- MSM基础成本:2,273-2,303 CU
内存使用费用
账户数据加载按32KB页面计费:
内存费用计算公式:
内存费用 = ⌈加载数据大小 ÷ 32KB⌉ × 堆成本
其中:
- 页面大小 = 32KB (32,768字节)
- 默认堆成本 = 8个计算单元每页
计算预算处理实现
指令处理流程
系统在交易处理早期阶段解析计算预算指令:
处理步骤:
- 解析计算预算指令
- 确定计算单元限制(默认或用户设定)
- 确定计算单元价格(默认0或用户设定)
- 设置堆大小请求
- 设置账户数据大小限制
费用销毁机制
与Cosmos不同,Solana采用费用销毁机制:
费用分配公式:
销毁费用 = 总费用 × 销毁百分比 ÷ 100
未销毁费用 = 总费用 - 销毁费用
默认销毁50%的手续费,剩余部分分配给验证者。
优先级费用机制
用户可以通过设置计算单元价格来提高交易优先级:
优先级费用计算公式:
优先级费用 = 计算单元限制 × 计算单元价格(微lamports)÷ 1,000,000
实现获取Top90高费用交易的可能流程
基于现有架构,实现此功能需要以下步骤:
使用FeeStructure.calculate_fee() FeeAnalyzer->>FeeAnalyzer: 按费用降序排序 Note over FeeAnalyzer: 类似cluster_info_metrics中的
select_nth_unstable_by_key逻辑 FeeAnalyzer->>RPC: 返回前90笔高费用交易 RPC->>Client: 返回结果
技术实现要点
-
费用计算 :需要使用
calculate_fee
方法来准确计算每笔交易的总费用。 -
数据存储:交易的费用信息可以看到会被记录到rewards中,但需要额外的索引来支持按费用排序。
-
排序算法 :可以参考排序逻辑,使用
sort_by
来实现按费用降序排列。
当前限制
目前Solana的RPC接口主要提供基础的交易查询功能,如供应量查询。要实现top90高费用交易查询,需要:
- 扩展RPC接口
- 建立费用索引
- 实现高效的排序和分页机制
Solana的排序规则
1. 投票统计的Top排序
在gossip网络中,Solana使用排序逻辑来获取投票数最多的slot:
这里使用了select_nth_unstable_by_key
方法,按投票数量降序排序,只保留前10个最活跃的slot。
2. 质押权重排序
在修复权重系统中,Solana按质押权重对slot进行排序:
这个排序逻辑优先考虑更高的质押权重,相同权重时按slot编号升序排列。
3. 账户清理中的排序
在账户数据库清理过程中,使用并行排序来处理大量公钥:
根据是否为启动阶段,选择不同的排序策略 - 启动时使用并行排序,运行时使用线程池排序。
4. 存储收缩中的排序
在存储优化过程中,按存活率对存储条目进行排序:
这里按存活率升序排序,优先处理最稀疏的存储条目。
5. 程序缓存排序
在程序缓存管理中,按使用频率排序来决定卸载哪些程序:
使用sort_by_cached_key
按事务使用计数器排序,优先卸载使用频率最低的程序。
6. 银行状态排序
在银行状态管理中,对祖先slot进行排序:
这里使用稳定排序来维护祖先关系的一致性。
7. 投票状态排序
在投票状态处理中,对锁定期进行排序:
按slot编号对锁定期进行排序,确保投票状态的正确性。
Solana的排序逻辑主要特点:
- 性能优化 - 大量使用
unstable_sort
和并行排序 - 场景特化 - 不同场景使用不同的排序键和策略
- 内存效率 - 使用
select_nth_unstable
等方法避免完整排序 - 稳定性考虑 - 在需要保持相对顺序的场景使用稳定排序
总结
Solana的手续费机制在设计上更加精细化,通过计算单元精确衡量各种操作的资源消耗。与Cosmos的gas机制相比,Solana的特点包括:
- 预定义成本 - 大多数操作都有预定义的计算单元成本
- 分层计费 - 签名、写锁、计算分别计费
- 动态调整 - 根据网络负载自动调整基础费用
- 优先级机制 - 用户可通过额外费用提高交易优先级
- 费用销毁 - 部分费用被永久移除,有助于通胀控制