前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Cosmos 普通交易手续费调

Cosmos 普通交易手续费调

作者头像
潇洒
发布2023-10-23 14:54:19
3190
发布2023-10-23 14:54:19
举报
文章被收录于专栏:石头岛石头岛

背景

分析 cosmos 的交易手续费的实现细节,以了解其实现方式用于TRON的手续费模型的实现参考。 在 cosmos 中,gas 用于跟踪执行期间的资源消耗。普通交易消耗的也是 gasgas 通常在对存储进行读取写入时使用,但如果需要执行昂贵的计算,也可以使用。

重点关注的两件事情:

  1. 如果计算、校验,即交易做了哪些操作,是否合法
  2. 每个操作的收费是如何定价的,包括:读取、存储、计算。

tx 会产生所有状态读取/写入、签名验证以及与 tx 大小成比例的成本的 gas 成本。运营商在启动节点时会设定最低 gas 价格。

需要消耗 gas的交易类型

每个交易在执行过程中都会消耗一定数量的Gas,该Gas用于跟踪执行过程中的资源消耗。 在Cosmos SDK应用程序中,交易可以是发送消息(Message)的操作,例如

  1. 发送代币
  2. 执行智能合约

当执行这些消息时,相关的Gas会被消耗,并且可能会生成相应的费用(Fees)。

请注意,Gas的消耗和费用的生成通常由应用程序开发者定义和管理,可以根据具体的应用逻辑和需求进行设置。

Cosmos SDK提供了Gas计量器(GasMeter)(主要就是通过个是来记录gas消耗)和相关的方法来追踪Gas的消耗和管理费用的生成。开发者可以在交易的执行逻辑中使用Gas计量器来测量Gas的消耗,并根据消耗的Gas数量来计算相应的费用。

因此,Gas的消耗和费用的生成是与交易(Transaction)密切相关的,并由应用程序开发者根据具体需求进行定义和管理。

交易收费

收费公式:fees = gas * gas-prices,交易费用按共识计算的确切gas价格收取。

收费有两个主要目的:

  1. 确保块不会消耗太多资源
  2. 防止用户发起垃圾交易

普通交易的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

代码语言:javascript
复制
key = WriteCostPerByte * len(key)
value = WriteCostPerByte * len(value)

2.签名收费

普通交易按照签名后的字节长度进行计费,每笔交易的gas有上限。

计算公式:

总消耗 = 原始交易byte大小 + 签名数据大小 * 每个字节的 Gas 消耗值 ConsumeGas = byte + TxSizeCostPerByte * cost params.TxSizeCostPerByte 就是用来定义每个字节的额外 Gas 消耗值。通过将交易的大小乘以该值,可以得到交易大小对应的额外 Gas 消耗。

3.读取收费

对数据读取的gas消耗需要计算 key 和 value 的大小,如下:

总消耗 = keyGas + valueGas

代码语言:javascript
复制
keyGas = ReadCostPerByte * len(key)
valueGas = ReadCostPerByte * len(value)

4.gas price

gas price 是动态的变动的,有三种方式:

  1. 提案进行修改,很少情况会通过这种方式修改
  2. 前一个区块负载进行调整
  3. 前一个区块负载以更高的速度进行调整

实现部分分析

gas 的消耗有两个功能跟踪:

  1. Main Gas Meter 主gas表 作用:用于跟踪每一笔交易的执行消耗。
  2. Block Gas Meter 作用:用于跟踪每一个区块的gas消耗。

Cosmos 通过抽像 Meter 数据结构,对gas的消耗进行跟踪。

1.Main Gas Meter 交易gas跟踪

作用:用于跟踪每一笔交易的执行消耗。

在 Cosmos SDK 中,gas是简单的别名,由名为GasMeter 结构的一个字段uint64

代码语言:javascript
复制
// GasMeter interface to track gas consumption
 type GasMeter interface {
 GasConsumed() Gas
 GasConsumedToLimit() Gas
 GasRemaining() Gas
 Limit() Gas
 ConsumeGas(amount Gas, descriptor string)
 RefundGas(amount Gas, descriptor string)
 IsPastLimit() bool
 IsOutOfGas() bool
 String() string
}
  • 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-ethereum StateDB接口。
  • IsPastLimit() 如果gas meter实例消耗的 gas 量严格高于限制, false 则返回 true
  • IsOutOfGas() 如果燃气表实例消耗的 gas 量高于或等于限制, false 则返回,否则返回 true

2.读/写 操作的gas消耗跟踪

Cosmos 中对读 和 写的操作,记录到 gasMeter 中,先操作后,再进行记录,每一笔交易的gas 都有上限,实现逻辑如下

  1. 进行数据库读写
  2. 计算所需要的gas值
  3. 注意 gs.gasConfig.ReadCostPerByte 是一个常量值,见上文
  4. keyvalue 都需要计算 gas
代码语言:javascript
复制
// Implements KVStore.
func (gs *Store) Get(key []byte) (value []byte) {
    gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc)
    // parent 是 types.KVStore,即数据库接口
    value = gs.parent.Get(key)

    // TODO overflow-safe math?
    // 对读的操作,记录到 gasMeter 中
    gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasReadPerByteDesc)
    gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc)
    return value
 }

// Implements KVStore.
func (gs *Store) Set(key, value []byte) {
    types.AssertValidKey(key)
    types.AssertValidValue(value)
    gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc)

    // TODO overflow-safe math?
    gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(key)), types.GasWritePerByteDesc)
    gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*types.Gas(len(value)), types.GasWritePerByteDesc)
    gs.parent.Set(key, value) 
}

3.签名gas消耗

对于签名部分,也是需要计算gas的消耗,总消耗 = 原始交易byte大小 + 签名数据大小 * 每个字节的 Gas 消耗值

x/auth/ante/basic.go

代码语言:javascript
复制
func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
    sigTx, ok := tx.(authsigning.SigVerifiableTx)
    if !ok {
        return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
    }
    params := cgts.ak.GetParams(ctx)
    // 计算交易长度
    // ctx: transaction 交易上下文
    // 注意,此处跟踪原始交易 byte 长度
    ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*storetypes.Gas(len(ctx.TxBytes())), "txSize")
    // simulate gas cost for signatures in simulate mode
    // 在模拟模式下模拟签名的gas成本
    if simulate {
        // in simulate mode, each element should be a nil signature
        // 在模拟模式下,每个元素都应是 nil 签名 
        sigs, err := sigTx.GetSignaturesV2()
        if err != nil {
            return ctx, err
        } n := len(sigs) signers, err := sigTx.GetSigners() if err != nil { return sdk.Context{}, err }
        for i, signer := range signers {
            // if signature is already filled in, no need to simulate gas cost
            // 如果签名已填写,则无需模拟gas成本
            if i < n && !isIncompleteSignature(sigs[i].Data) {
                continue
            }
            var pubkey cryptotypes.PubKey
            acc := cgts.ak.GetAccount(ctx, signer)

            // use placeholder simSecp256k1Pubkey
            if sig is nil if acc == nil || acc.GetPubKey() == nil {
                pubkey = simSecp256k1Pubkey 
            } else {
                pubkey = acc.GetPubKey()
            }
            // use stdsignature to mock the size of a full signature
            // 使用 stdsignature 模拟完整签名的大小
            simSig := legacytx.StdSignature{ //nolint:staticcheck // SA1019: legacytx.StdSignature is deprecated 
                Signature: simSecp256k1Sig[:],
                PubKey: pubkey,
            }
            sigBz := legacy.Cdc.MustMarshal(simSig)
            // cost 为签名长度
            cost := storetypes.Gas(len(sigBz) + 6)
            // If the pubkey is a multi-signature pubkey, then we estimate for the maximum
            // number of signers. 
            // 如果公开密钥是多签名公开密钥,那么我们将估计最大的签名者数量。 
            if _, ok := pubkey.(*multisig.LegacyAminoPubKey); ok {
                cost *= params.TxSigLimit
            } 
            // 此处记录 签名后的 gas 消耗
            ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize") 
        }
    }
    return next(ctx, tx, simulate)
}

总结

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-09-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 需要消耗 gas的交易类型
  • 交易收费
    • 普通交易的gas是如何计算的
      • 1.写入收费
        • 2.签名收费
          • 3.读取收费
            • 4.gas price
            • 实现部分分析
              • 1.Main Gas Meter 交易gas跟踪
                • 2.读/写 操作的gas消耗跟踪
                  • 3.签名gas消耗
                  • 总结
                  • 参考链接
                  相关产品与服务
                  对象存储
                  对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档