Solana区块链有几种不同类型的费用和成本,这些费用和成本是在使用无许可网络时产生的。
这些费用可以分为几种特定类型:
- 交易费用: 验证者处理交易/指令的费用
- 优先费用: 用于提升交易处理顺序的可选费用
- 租金: 保持数据在链上存储的保留余额
交易费用
在Solana区块链上的链上程序中处理逻辑(指令)所支付的小额费用被称为交易费用。
当每笔交易(包含一个或多个指令)通过网络发送时,它会由当前的验证者领导处理。
一旦被确认为全局状态交易,这笔交易费用将支付给网络,以支持Solana区块链的经济设计。
交易费用不同于账户数据存储押金(租金)。 交易费用用于在Solana网络上处理指令,而租金押金是保存在账户中的余额, 用于存储其数据在区块链上,并且可以被取回。
目前,Solana的基本交易费用设定为每个签名5000 lamports
。
在此基础费用之上,可以添加任何额外的优先费用。
为什么要支付交易费用?
交易费用在Solana 经济设计中提供了许多好处,主要包括:
- 为验证者网络补偿处理交易所需的CPU/GPU计算资源
- 通过引入实际成本来减少网络垃圾交易
- 通过每笔交易的协议捕获最低费用量,为网络提供长期经济稳定性
基本经济设计
许多区块链网络(包括比特币和以太坊)在短期内依赖于通胀性协议奖励来保障网络安全。 从长远来看,这些网络将越来越依赖交易费用来维持安全性。
Solana也是如此。具体来说:
- 每笔交易费用的固定比例(初始为50%)被烧毁(销毁),剩余部分则归当前处理交易的领导者。
- 一个预定的全球通胀率为分配给Solana验证者的奖励提供了来源。
费用收取
交易需要至少一个签署交易并可写的账户。
这些可写的签署账户首先被序列化,并且第一个账户总是用作“费用支付者”。
在处理任何交易指令之前,将从费用支付者账户余额中扣除交易费用。
如果费用支付者余额不足以支付交易费用,交易处理将停止,并导致交易失败。
如果余额足够,将扣除费用并开始执行交易的指令。 如果任何指令导致错误,交易处理将停止,并最终在Solana账本中记录为失败的交易。 这些失败交易的费用仍将被运行时收取。
如果任何指令返回错误或违反运行时限制,除交易费用扣除外,所有账户更改将被回滚。 这是因为验证者网络已经消耗了收集交易和开始初始处理的计算资源。
费用分配
交易费用部分被烧毁,剩余费用由生成包含相应交易的区块的验证者收取。
具体来说,50%
被烧毁,50%
分配给生成区块的验证者。
为什么烧毁一些费用?
如上所述,每笔交易费用的固定比例被烧毁(销毁)。 这旨在巩固SOL的经济价值,从而维持网络的安全性。 与完全烧毁交易费用的方案不同,领导者仍然有动力在其槽(创建区块的机会)中包含尽可能多的交易。
烧毁费用还可以通过在分叉选择中考虑被烧毁的费用,帮助防止恶意验证者审查交易。
攻击示例:
在具有恶意或审查领导者的历史证明(PoH
)分叉的情况下:
- 由于审查导致的费用损失,我们预期烧毁的总费用将少于一个可比较的诚实分叉
- 如果审查领导者要补偿这些损失的协议费用,他们将不得不自己替换烧毁的费用
- 从而可能减少最初进行审查的动机
计算交易费用
给定交易的完整费用根据两个主要部分计算:
- 每个签名的静态设定基本费用,以及
- 交易过程中使用的计算资源,以“计算单元”衡量
由于每笔交易可能需要不同数量的计算资源,每笔交易都有一个分配的最大计算单元数作为计算预算的一部分。
计算预算
为了防止滥用计算资源,每笔交易都有一个“计算预算”。这个预算指定了计算单元的细节,包括:
- 交易可能执行的不同类型操作的计算成本(每操作消耗的计算单元),
- 每笔交易可以消耗的最大计算单元数(计算单元限制),
- 以及交易必须遵守的操作界限(如账户数据大小限制)
当交易耗尽其全部计算预算(计算预算耗尽)或超过某个界限(如尝试超过最大调用堆栈深度或最大加载账户数据大小限制)时, 运行时会停止交易处理并返回错误,导致交易失败且没有状态更改(除交易费用收取外)。
账户数据大小限制
交易可以通过包含SetLoadedAccountsDataSizeLimit
指令指定其允许加载的最大账户数据 字节数(不超过运行时的绝对最大值)。
如果未提供SetLoadedAccountsDataSizeLimit
,
交易将默认使用运行时的MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES
值。
ComputeBudgetInstruction::set_loaded_accounts_data_size_limit
函数可用于创建此指令:
let instruction = ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(100_000);
计算单元
在交易中执行的所有链上操作都需要消耗不同数量的计算资源(计算成本)。 这些资源消耗的最小计量单位称为“计算单元”。
在交易处理过程中,每个指令执行时会逐步消耗计算单元(消耗预算)。 由于每个指令执行不同的逻辑(写入账户、CPI、执行系统调用等), 每个指令可能消耗不同数量的计算单元。
一个程序可以记录其计算使用的细节,包括剩余的计算预算。 请参阅程序调试以获取更多信息。 您还可以在此指南中找到更多关于优化计算使用的信息。
每笔交易都有一个计算单元限制,可以由运行时设置的默认限制或通过显式请求更高的限制。 超过其计算单元限制后,交易处理将停止,导致交易失败。
以下是一些常见的计算成本操作:
- 执行指令
- 在程序间传递数据
- 执行系统调用
- 使 用系统变量
- 使用
msg!
宏记录日志 - 记录公钥
- 创建程序地址(
PDA
) - 跨程序调用(
CPI
) - 加密操作
对于跨程序调用,被调用指令继承其父级的计算预算和限制。 如果被调用指令消耗了交易的剩余预算或超出了界限,整个调用链和顶层交易处理将停止。
您可以在Solana运行时的ComputeBudget
中找到有关所有消耗计算单元的操作的详细信息。
计算单元限制
每笔交易都有一个称为“计算单元限制”的最大计算单元数(CU
)。
每笔交易,Solana运行时的绝对最大计算单元限制为140万CU
,
并设定了每指令20万CU
的默认请求最大限制。
交易可以通过包含单个SetComputeUnitLimit
指令请求更具体和优化的计算单元限制。
可以是更高或更低的限制。但它永远不能请求高于每笔交易的绝对最大限制。
虽然交易的默认计算单元限制在大多数情况下适用于简单交易, 但它们通常不太优化(无论是对运行时还是用户)。 对于更复杂的交易,如调用执行多个CPI的程序,您可能需要为交易请求更高的计算单元限制。
请求交易的最优计算单元限制对于帮助您支付更少的交易费用和在网络上更好地调度交易至关重要。 钱包、dApp和其他服务应确保其计算单元请求是最优的,以提供最佳用户体验。
有关更多详细信息和最佳实践,请阅读此指南以请求最佳计算限制。
计算单元价格
当交易希望支付更高的费用以提升其处理优先级时,可以设置“计算单元价格”。 此价格与计算单元限制结合使用,将用于确定交易的优先费用。
默认情况下,没有设置计算单元价格,因此没有额外的优先费用。
优先费用
作为计算预算的一部分,运行时支持交易支付一种可选费用,称为“优先费用”。 支付此额外费用有助于提升交易相对于其他交易的优先级,从而实现更快的执行时间。
如何计算优先费用
交易的优先费用通过将其计算单元限制乘以计算单元价格(以微lamports
为单位)计算。
这些值可以通过包含以下计算预算指令一次性设置:
SetComputeUnitLimit
: 设置交易可以消耗的最大计算单元数SetComputeUnitPrice
: 设置交易愿意支付的额外费用,以提升其优先级
如果未提供SetComputeUnitLimit
指令,将使用默认的计算单元限制。
如果未提供SetComputeUnitPrice
指令,
交易将默认没有额外的提升费用和最低优先级(即没有优先费用)。
如何设置优先费用
交易的优先费用通过包含SetComputeUnitPrice
指令,
并可选地包含SetComputeUnitLimit
指令来设置。
运行时将使用这些值来计算优先费用,该费用将用于在区块内优先处理给定交易。
您可以通过Rust或@solana/web3.js
函数来制作这些指令。
每个指令可以像正常一样包含在交易中并发送到集群。另见下面的最佳实践。
与Solana交易中的其他指令不同,计算预算指令不需要任何账户。包含多个同类型指令的交易将失败。
交易只能包含每种类型的一个计算预算指令。
重复的指令类型将导致TransactionError::DuplicateInstruction
错误,并最终导致交易失败。
Rust
Rust solana-sdk
crate包括ComputeBudgetInstruction
内的函数,
以制作设置计算单元限制和计算单元价格的指令:
let instruction = ComputeBudgetInstruction::set_compute_unit_limit(300_000);
let instruction = ComputeBudgetInstruction::set_compute_unit_price(1);
Javascript
@solana/web3.js
库包括ComputeBudgetProgram
类内的函数,
以制作设置计算单元限制和计算单元价格的指令:
const instruction = ComputeBudgetProgram.setComputeUnitLimit({
units: 300_000,
});
const instruction = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 1,
});
优先费用最佳实践
以下是关于优先费用的一般最佳实践信息。 您还可以在此指南中找到更多详细信息,了解如何请求最佳计算,包括如何模拟交易以确定其大致计算使用量。
请求最少计算单元
交易应请求执行所需的最少计算单元,以最小化费用。还应注意,当请求的计算单元数超过实际消耗的计算单元时,费用不会调整。
获取最近的优先费用
在将交易发送到集群之前,您可以使用getRecentPrioritizationFees
RPC方法获取节点处理的最近区块内支付的优先费用列表。
然后,您可以使用这些数据估计交易的适当优先费用,以便(a
)更好地确保其被集群处理,以及(b
)最小化支付的费用。
租金
存入每个Solana账户以保持其关联数据在链上可用的费用称为“租金”。
此费用在每个账户的正常lamport
余额中保留,账户关闭时可被取回。
租金不同于交易费用。 租金是为了保持数据存储在Solana区块链上而“支付”的(保存在账户中),可以被取回。 而交易费用是为了在网络上处理指令而支付的。
所有账户都必须保持足够高的lamport
余额(相对于其分配的空间)以免除租金并保持在Solana区块链上。
任何试图将账户余额减少到低于其相应的免租金最低余额的交易将失败(除非余额正好减少到零)。
当账户所有者不再希望将此数据保留在链上并在全局状态中可用时,所有者可以关闭账户并取回租金押金。
这可以通过将账户的全部lamport
余额提现(转移)到另一个账户(即您的钱包)来实现。
通过将账户余额正好减少到0
,运行时将在垃圾收集中从网络中移除该账户及其关联数据。
租金率
Solana的租金率在网络范围内设定,主要基于运行时设定的“每字节每年lamport
”。
目前,租金率是一个静态金额,存储在Rent sysvar
中。
此租金率用于计算为账户分配的空间(即账户可以存储的数据量)在账户内保留的确切租金金额。 账户分配的空 间越多,保留的租金押金就越高。
免租金
账户必须保持高于其所需数据存储在链上的最低余额。这称为“免租金”,其余额称为“免租金最低余额”。
Solana上的新账户(和程序)必须初始化足够的lamports
以免除租金。
这并非一直如此。以前,运行时会定期自动从低于其免租金最低余额的账户中收取费用,
最终将这些账户减少到零余额并从全局状态中垃圾收集(除非手动补充)。
在创建新账户的过程中,您必须确保存入足够的lamports
以超过此最低余额。
低于此最低阈值的任何操作将导致交易失败。
每次减少账户余额时,运行时都会检查账户是否仍然高于此免租金最低余额。 除非将最终余额减少到正好为零(关闭账户),否则会导致账户余额低于免租金阈值的交易将失败。
账户要免租金的具体最低余额取决于区块链的当前租金率和账户要分配的存储空间(账户大小)。
因此,建议使用getMinimumBalanceForRentExemption
RPC端点计算给定账户大小的具体余额。
所需的租金押金金额也可以通过solana rent
CLI子命令估算:
solana rent 15000
# 输出
Rent per byte-year: 0.00000348 SOL
Rent per epoch: 0.000288276 SOL
Rent-exempt minimum: 0.10529088 SOL