Flow 动态交易手续费
翻译自 Flow 官方文档动态交易费
本指南将解释为什么交易费用很重要,如何计算交易费用,以及如何在实现中处理费用。具体来说,它展示了如何估计交易成本,如何设置成本限制,以及如何优化 Cadence 代码以尽可能降低交易成本。
本指南最后将介绍如何教育用户有关费用以及如何了解更多有关交易费用实施的信息。
注:交易成本的实施基于社区参与的 FLIP 进程。这项工作目前正在进行中。在文末了解更多部分以参与该进程。
理解交易费用的必要
动态交易费用对于根据对网络的影响确保公平定价至关重要。例如,更繁重的操作将需要更多的资源来处理和传播交易。然而,普通操作将保持合理的价格。
收费会降低网络上的恶意行为(如垃圾交易)的可行性,从而提高网络的整体安全性。
独特的 Flow 结构以高吞吐量为目标。这使得系统更容易松弛,因此短期高使用需求可以被更优雅地处理。
理解费用结构
费用根据三个部分计算:执行费、打包费和网络激增系数。
打包和执行费用可以表示为打包或执行工作以及相关乘数,以反映打包和执行工作的成本。最终交易费用计算如下所示:
inclusionFee = inclusionEffort * inclusionEffortCost; executionFee = executionEffort * executionEffortCost; totalFee = (inclusionFee + executionFee) * surgeFactor;
注:如果你想了解有关成本函数的更多信息,请参阅 FLIP 753
执行消耗
交易的执行工作由交易所采用的代码路径及其执行的操作决定。具有相关执行工作成本的操作可以分为四大类:
- 一般 Cadence 代码行数、循环或函数调用的执行
- 从存储器读取数据,按读取字节计费
- 将数据写入存储器,按写入的字节计费
- 帐户创建
费用概述
为了让你更好地了解费用范围,考虑到当前的
executionEffortCost
和inclusionEffortCost
参数,以下是一些常见的交易类型及其相关的执行成本:费用对比
交易类型
估计费用(FLOW)
相对FT转移成本
打包费用
交易的打包工作表示以下所需的工作:
- 将交易打包在块中
- 在节点之间传输交易信息
- 验证交易签名
现在,打包工作始终为 1.0,打包工作成本固定为
0.000001
。注:在不执行交易代码的情况下,始终可以计算打包费用。
未来,打包成本将受到交易字节大小和所需签名数量的影响。
注:可变打包成本的变更将在[即将发布的 SPORK 之一]中更新(https://docs.onflow.org/node-operation/upcoming-sporks/).
网络激增值
未来,由于需要处理的交易量增加或处理交易的能力下降而导致网络繁忙时,将应用网络激增值。目前,网络流量固定为
1.0
。存储费用
存储费用的实施方式与交易费用不同。请参阅[在 FLOW 中存储数据指南](https://docs.onflow.org/concepts/storage/#storage-capacity 了解更多详细信息。总之,存储费用是与在链上存储数据相关的成本。
预估交易费用
成本估算分两步进行。首先,你需要通过 emulator 或 testnet 收集执行工作。其次,你可以使用一个 JavaScript 或 Go-FCL SDK,使用交易的执行努力来计算最终费用。
了解执行工作
通过运行交易并查看发出的事件详细信息,可以最好地确定执行工作。
使用 Flow 模拟器
你可以[使用 Flow CLI 启动模拟器](https://docs.onflow.org/emulator/#running-the-emulator-with-the-flow-cli)。运行交易并查看发出的事件:
0|emulator | time="2022-04-06T17:13:22-07:00" level=info msg="⭐ Transaction executed" computationUsed=3 txID=a782c2210c0c1f2a6637b20604d37353346bd5389005e4bff6ec7bcf507fac06
你应该看到
ComputeUsed
字段。记下该值,将在下一步中使用它。在 testnet
交易完成后,可以使用类似于Flowscan的区块浏览器查看交易详细信息和发出的事件。对于 Flowscan,你可以打开有问题的交易,并从
FlowFees
中查找FeesDeducted
事件, 合约:
在右侧的事件数据中,你将看到一组表示[FeeParameters]的字段(https://github.com/onflow/flow-core-contracts/blob/master/contracts/FlowFees.cdc#L58):
- surgeFactor 激增系数
- inclusionEffort 打包消耗
- executionEffort 执行消耗
记下列表中的最后一个值
executionEffort
值。将在下一步中使用它。计算最终成本
可以分别在 mainnet/testnet 上使用以下 FCL 脚本计算交易的成本。
在 mainnet 上
import FlowFees from 0xf919ee77447b7497 pub fun main( inclusionEffort: UFix64, executionEffort: UFix64 ): UFix64 { return FlowFees.computeFees(inclusionEffort: inclusionEffort, executionEffort: executionEffort) }
在 testnet
import FlowFees from 0x912d5440f7e3769e pub fun main( inclusionEffort: UFix64, executionEffort: UFix64 ): UFix64 { return FlowFees.computeFees(inclusionEffort: inclusionEffort, executionEffort: executionEffort) }
配置执行限制
FCL SDK 允许你为每个交易设置执行工作量限制。根据上一步中确定的执行工作量限制,你应该设置合理的最大值,以避免意外行为并保护你的用户。最终交易费用根据实际执行工作量计算,直至达到该最大值。
注:请记住,限制并非针对用户必须支付的最终费用。限制具体针对执行工作。 设定一个不太高或太低的限值很重要。如果设置得太高,付款人需要在发送交易之前在其帐户中有更多的资金。如果过低,执行可能会失败,所有状态更改都会被删除。
使用 FCL JS SDK
你需要为
mutate
函数设置 limit
参数,例如:import * as fcl from "@onflow/fcl" const transactionId = await fcl.mutate({ cadence: ` transaction { execute { log("Hello from execute") } } `, proposer: fcl.currentUser, payer: fcl.currentUser, limit: 100 }) const transaction = await fcl.tx(transactionId).onceSealed(); console.log(transaction;)
使用 FCL Go SDK
你需要调用
SetGasLimit
方法来设置费用限制,例如:import ( "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/crypto" ) var ( myAddress flow.Address myAccountKey flow.AccountKey myPrivateKey crypto.PrivateKey ) tx := flow.NewTransaction(). SetScript([]byte("transaction { execute { log(\\"Hello, World!\\") } }")). SetGasLimit(100). SetProposalKey(myAddress, myAccountKey.Index, myAccountKey.SequenceNumber). SetPayer(myAddress)
优化 Cadence 代码以减少工作量
一些优化可以减少交易的执行时间。下面列出了一些做法。此列表并非详尽无遗,而是示例性的。
限制函数调用
无论何时进行函数调用,请确保这些都是绝对必需的。在某些情况下,你可以检查先决条件并避免额外调用:
for obj in sampleList { /// 检查该执行是否是必要的 if obj.id != nil { functionCall(obj) } }
限制循环和迭代
每当你想要迭代列表时,请确保有必要迭代所有元素,而不是子集。避免循环随着时间的推移而过大。尽可能限制循环。
// 迭代长列表可能代价高昂 pub fun sum(list: [Int]): Int { var total = 0 var i = 0 // 如果列表变得太大,这可能无法顺利执行 while i < list.length { total = total + list[i] } return total } // 考虑将交易(和脚本)设计为可以将工作“分块”成更小的部分 pub fun partialSum(list: [Int], start: Int, end: Int): Int { var partialTotal = 0 var i = start while i < end { partialTotal = partialTotal + list[i] } return partialTotal }
了解函数调用的影响
某些功能需要比其他功能更多的执行工作。你应该仔细检查所进行的函数调用以及它们涉及的执行。
// 注意调用许多其他函数的函数 // (或自我递归调用)可能要消耗很多费用 pub fun fib(_ x: Int): Int { if x == 1 || x== 0 { return x } // + 2 function calls each recursion return fib(x-1) + fib(x-2) } // 考虑使用单个语句内联函数,以降低成本 pub fun add(_ a: Int, _ b: Int): Int { // 单条语句,内联 return a + b }
避免过度加载和保存的操作
避免昂贵的装载和存储操作以及[借用参考](https://docs.onflow.org/cadence/design-patterns/#avoid-excessive-load-and-save-storage-operations-prefer-in-place-mutations)参考如下如:
transaction { prepare(acct: AuthAccount) { // 借用对已存储 vault 的引用,比从存储中删除 vault 的操作成本低得多 let vault <- acct.borrow<&ExampleToken.Vault>(from: /storage/exampleToken) let burnVault <- vault.withdraw(amount: 10) destroy burnVault // 不需要“保存”,因为我们只使用了引用 } }
注:如果请求的资源不存在,则不收取读取费用。
限制每笔交易创建的帐户
创建帐户和添加密钥与成本相关。尝试仅在必要时创建帐户和密钥。
执行交易前检查用户余额
你应该确保用户的余额足以支付可能的最高费用。对于同质化代币的转账,除了可能的最高费用外,你还需要支付转账金额。
用户教育
钱包将处理最终交易成本的表示,但你仍然可以通过在应用程序中对其进行教育来促进用户体验。
如果你的用户使用的是非托管钱包,他们可能需要支付交易费用,并希望了解费用。这里有一些建议。
解释交易费用可能因网络使用情况而异
建议信息:“收费提高了网络的安全性。收费灵活,可根据对网络的影响确保公平定价。”
说明等待网络峰值消退是一种选择
不可避免地,网络激增将导致更高的费用。如果用户希望在网络使用激增时提交交易,则应考虑稍后发送交易以降低成本。
解释钱包可能因资金不足而不允许交易
如果动态费用增加到可能的最高水平,则用户的资金可能不足以执行交易。让用户知道,他们应该要么增加资金,要么在网络不那么繁忙的时候尝试。
如何了解更多信息
有几个地方可以了解有关交易费用的更多信息:
Note: If you have thoughts on the implementation of transaction fees on Flow, you can leave feedback on this forum post.
注:如果你对 Flow 交易费的实施有想法,你可以在此论坛帖子上留下反馈.
FAQs
为什么即使交易失败也要收取费用
广播和验证交易需要执行,因此可以适当扣除成本。
哪些执行成本被视为高于平均水平
执行成本没有平均值。根据实现的逻辑,每个功能都会有很大的不同。你应该查看优化最佳实践,以确定是否可以降低成本。
像Ledger这样的硬件钱包是否支持分段收费
对
最低的执行成本是多少
最低执行成本为 1。这意味着你的交易包括一个没有读取或写入任何日期的函数调用或循环。
我能否确定在没有实际付款的情况下,mainnet上的交易成本是多少
你可以在双向过程中估计成本:1)确定交易(emulator 或 testnet)的执行成本,2)使用FCL SDK 方法计算最终交易费用。
testnet费用与mainnet费用的准确性如何
最终费用由网络上的激增系数决定。测试网的激增系数将不同于主网的激增系数,因此你需要预测主网和测试网估计值之间的变化。
我使用Blocto,我还没有支付任何费用。为什么会这样
这是因为 Blocto 是交易的付款人。非保管钱包可能会让用户支付交易费用。此外,如果应用程序选择,它们可以补贴交易费用。