Cosmos 普通交易手续费调研
背景
分析 cosmos 的交易手续费的实现细节,以了解其实现手续费模型用于实现参考。
在 cosmos 中,gas
用于跟踪执行期间的资源消耗。普通交易消耗的也是 gas
。gas
通常在对存储进行读取和写入时使用,但如果需要执行昂贵的计算,也可以使用。
重点关注的两件事情:
- 如果计算、校验,即交易做了哪些操作,是否合法
- 每个操作的收费是如何定价的,包括:读取、存储、计算。
tx 会产生所有状态读取/写入、签名验证以及与 tx 大小成比例的成本的 gas 成本。运营商在启动节点时会设定最低 gas 价格。
需要消耗 gas的交易类型
每个交易在执行过程中都会消耗一定数量的Gas,该Gas用于跟踪执行过程中的资源消耗。
在Cosmos SDK应用程序中,交易可以是发送消息(Message)的操作,例如
- 发送代币
- 执行智能合约
当执行这些消息时,相关的Gas会被消耗,并且可能会生成相应的费用(Fees)。
请注意,Gas的消耗和费用的生成通常由应用程序开发者定义和管理,可以根据具体的应用逻辑和需求进行设置。
Cosmos SDK提供了Gas计量器(GasMeter)(主要就是通过个是来记录gas消耗)和相关的方法来追踪Gas的消耗和管理费用的生成。开发者可以在交易的执行逻辑中使用Gas计量器来测量Gas的消耗,并根据消耗的Gas数量来计算相应的费用。
因此,Gas的消耗和费用的生成是与交易(Transaction)密切相关的,并由应用程序开发者根据具体需求进行定义和管理。
交易收费
收费公式:fees = gas * gas-prices
,交易费用按共识计算的确切gas价格收取。
收费有两个主要目的:
- 确保块不会消耗太多资源
- 防止用户发起垃圾交易
普通交易的gas是如何计算的
通过对交易的长度进行计算,最终确认这笔交易所需要gas。而当发送到节点的交易低于全节点本地设置的 min-gas-prices
,交易将直接被丢弃,这可确保 mempool 不会被垃圾交易塞满。
对于数据读、写的操作,可以通过根据需要设置每个gas的消耗,以下是Cosmos官方的默认设定:
操作 | 作用 | gas |
---|---|---|
HasCost | 检查是否存在kay的 Gas 消耗 | 1000 |
DeleteCost | 删除kay的 Gas 消耗 | 1000 |
ReadCostFlat | 读取操作的固定 Gas 消耗 | 1000 |
ReadCostPerByte | 每字节读取操作的额外 Gas 消耗 | 3 |
WriteCostFlat | 写入操作的固定 Gas 消耗 | 2000 |
WriteCostPerByte | 每字节写入操作的额外 Gas 消耗 | 30 |
IterNextCostFlat | 迭代器的下一个操作的固定 Gas 消耗 | 30 |
1.写入收费
对数据写入的gas消耗需要计算 key 和 value 的大小,如下:
总消耗 = keyGas + valueGas
1 | key = WriteCostPerByte * len(key) |
2.签名收费
普通交易按照签名后的字节长度进行计费,每笔交易的gas有上限。
计算公式:
总消耗
= 原始交易byte大小
+ 签名数据大小
* 每个字节的 Gas 消耗值
ConsumeGas
= byte
+ TxSizeCostPerByte
* cost
params.TxSizeCostPerByte
就是用来定义每个字节的额外 Gas 消耗值。通过将交易的大小乘以该值,可以得到交易大小对应的额外 Gas 消耗。
3.读取收费
对数据读取的gas消耗需要计算 key 和 value 的大小,如下:
总消耗 = keyGas + valueGas
1 | keyGas = ReadCostPerByte * len(key) |
4.gas price
gas price 是动态的变动的,有三种方式:
- 提案进行修改,很少情况会通过这种方式修改
- 前一个区块负载进行调整
- 前一个区块负载以更高的速度进行调整
实现部分分析
gas 的消耗有两个功能跟踪:
- Main Gas Meter 主gas表
作用:用于跟踪每一笔交易的执行消耗。 - Block Gas Meter
作用:用于跟踪每一个区块的gas消耗。
Cosmos 通过抽像 Meter 数据结构,对gas的消耗进行跟踪。
1.Main Gas Meter 交易gas跟踪
作用:用于跟踪每一笔交易的执行消耗。
在 Cosmos SDK 中,gas
是简单的别名,由名为GasMeter
结构的一个字段uint64
1 | // GasMeter interface to track gas consumption |
GasConsumed()
返回 gas meter实例消耗的gas量。GasConsumedToLimit()
返回 gas meter 实例消耗的gas量或达到限制(如果达到限制)。GasRemaining()
返回 gas mete 中剩余的gas。Limit()
返回gas meter实例的限制。0
如果燃气表是无限大的。ConsumeGas(amount Gas, descriptor string)
消耗提供的数量gas
。
如果溢出,gas
它会对descriptor
消息感到恐慌(panics)。
如果燃气表不是无限的,消耗超过限制,它会gas
恐慌(panics)。RefundGas()
从消耗的gas中扣除给定的量。此功能可以将gas退还到交易或区块 gas 池,以便EVM兼容链可以完全支持go-ethereumStateDB
接口。IsPastLimit()
如果gas meter实例消耗的 gas 量严格高于限制,false
则返回true
。IsOutOfGas()
如果燃气表实例消耗的 gas 量高于或等于限制,false
则返回,否则返回true
。
2.读/写 操作的gas消耗跟踪
Cosmos 中对读 和 写的操作,记录到 gasMeter 中,先操作后,再进行记录,每一笔交易的gas 都有上限,实现逻辑如下
- 进行数据库读写
- 计算所需要的gas值
- 注意
gs.gasConfig.ReadCostPerByte
是一个常量值,见上文 key
和value
都需要计算 gas
1 | // Implements KVStore. |
3.签名gas消耗
对于签名部分,也是需要计算gas的消耗,总消耗
= 原始交易byte大小
+ 签名数据大小
* 每个字节的 Gas 消耗值
x/auth/ante/basic.go
1 | func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { |
总结
Cosmos 对普通交易的处理,基于对交易长度 * 预设gas 的方式进行计算,其中的实现方式以抽出 Meter 记录表的方式,在每一步关键操作位置计算并记录gas消息,可以考虑借鉴Cosmos。
参考链接
transaction 生命周期:Transaction Lifecycle | Cosmos SDK
gas fee介绍:Gas and Fees | Cosmos SDK
Gas & Fees:x/auth | Cosmos SDK
GasKVStore:Store | Cosmos SDK