cosmos-sdk/docs/cn/basics/gas-fees.md

6.9 KiB
Raw Blame History

原文路径:https://github.com/cosmos/cosmos-sdk/blob/master/docs/basics/gas-fees.md

Gas and Fees

必备阅读 {hide}

Gas and Fees的介绍

在 Cosmos SDK 中,gas是一种特殊的单位,用于跟踪执行期间的资源消耗。每当对储存进行读写操作的时候会消耗gas,如果要进行比较复杂的计算的话也会消耗gas。它主要有两个目的:

  • 确保区块不会消耗太多资源而且能顺利完成。这个默认在 SDK 的 block gas meter 中保证
  • 防止来自终端用户的垃圾消息和滥用。为此,通常会为 message 执行期间的消耗进行定价,并产生 fee(fees = gas * gas-prices)。fees 通常必须由 message 的发送者来支付。请注意SDK 并没有强制执行对 gas 定价,因为可能会有其他的方法来防止垃圾消息(例如带宽方案)。尽管如此,大多数应用程序仍然会使用fee 方式来防止垃圾消息。这个机制通过 AnteHandler 来完成.

Gas Meter

在 Cosmos SDK 中 gas 是一种简单的 uint64 类型,被称之为 gas meter 的对象进行管理Gas meters 实现了 GasMeter 接口

+++ 7d7821b9af/store/types/gas.go (L31-L39)

这里:

  • GasConsumed() 返回 GasMeter 实例中消耗的 gas 的数量

  • GasConsumedToLimit() 返回 GasMeter 实例消耗的 gas 数量,如果达到上限的话就返回上限

  • Limit() 返回 GasMeter 实例的上限,如果是 0 则表示对 gas 的数量没有限制

  • ConsumeGas(amount Gas, descriptor string) 消耗提供的 gas,如果 gas 溢出了,使用 descriptor 内容进行报错,如果 gas 并不是无限的,则超过限制就会报错。

  • IsPastLimit() 如果 gas 消耗超过了 GasMeter 的限制则返回 true,其它返回 false

  • IsOutOfGas() 如果 gas 消耗大于或等于了 GasMeter 的限制则返回 true,其它返回 false

    GasMeter 通常保存在 ctx 中,gas 消耗的方式如下:

ctx.GasMeter().ConsumeGas(amount, "description")

通常Cosmos SDK 使用两种不同的 GasMetermain gas meterblock gas meter

主 Gas Meter

ctx.GasMeter() 是应用程序的主 GasMeter,主 GasMeter 通过 BeginBlock 中的 setDeliverState 进行初始化,然后跟踪导致状态转换的执行序列中的 gas 消耗。也即是说它的更新由 BeginBlockDeliverTxEndBlock 进行操作。主 GasMeter 必须在 AnteHandler设置为 0,以便它能获取每个 transaction 的 Gas 消耗

gas消耗可以手工完成,模块开发者通常在 BeginBlocker,EndBlocker 或者 handler 上执行,但大多数情况下,只要对储存区进行了读写,它就会自动完成。这种自动消耗的逻辑在GasKv中完成.

块 Gas Meter

ctx.BlockGasMeter() 是跟踪每个区块 gas 消耗并保证它没有超过限制的 GasMeter。每当 BeginBlock 被调用的时候一个新的 BlockGasMeter 实例将会被创建。BlockGasMetergas 是有限的,每个块的 gas 限制应该在应用程序的共识参数中定义Cosmos SDK 应用程序使用 Tendermint 提供的默认共识参数:

+++ f323c80cb3/types/params.go (L65-L72)

当通过 DeliverTx 处理新的 transaction 的时候,BlockGasMeter 的当前值会被校验是否超过上限,如果超过上限,DeliverTx 直接返回,由于 BeginBlock 会消耗 gas,这种情况可能会在第一个 transaction 到来时发生,如果没有发生这种情况,transaction将会被正常的执行。在 DeliverTx 的最后,ctx.BlockGasMeter() 会追踪 gas 消耗并将它增加到处理 transactiongas 消耗中.

ctx.BlockGasMeter().ConsumeGas(
    ctx.GasMeter().GasConsumedToLimit(),
    "block gas meter",
)

AnteHandler

AnteHandler 是一个特殊的处理程序,它在 CheckTxDeliverTx 期间为每一个 transaction 的每个 message 处理之前执行。AnteHandler 相比 handler 有不同的签名:

// AnteHandler authenticates transactions, before their internal messages are handled.
// If newCtx.IsZero(), ctx is used instead.
type AnteHandler func(ctx Context, tx Tx, simulate bool) (newCtx Context, result Result, abort bool)

AnteHandler 不是在核心 SDK 中实现的,而是在每一个模块中实现的,这使开发者可以使用适合其程序需求的AnteHandler版本,也就是说当前大多数应用程序都使用 auth module 中定义的默认实现。下面是 AnteHandler 在普通 Cosmos SDK 程序中的作用:

  • 验证事务的类型正确。事务类型在实现 anteHandler 的模块中定义,它们遵循事务接口:

    +++ 7d7821b9af/types/tx_msg.go (L33-L41)

    这使开发人员可以使用各种类型的应用程序进行交易。 在默认的 auth 模块中,标准事务类型为 StdTx

    +++ 7d7821b9af/x/auth/types/stdtx.go (L22-L29)

  • 验证交易中包含的每个 message 的签名,每个 message 应该由一个或多个发送者签名,这些签名必须在 anteHandler 中进行验证.

  • CheckTx 期间,验证 transaction 提供的 gas prices 是否大于本地配置 min-gas-prices(提醒一下,gas-prices 可以从以下等式中扣除fees = gas * gas-prices)min-gas-prices 是每个独立节点的本地配置,在CheckTx期间用于丢弃未提供最低费用的交易。这确保了内存池不会被垃圾交易填充.

  • 设置 newCtx.GasMeter 到 0限制为GasWanted这一步骤非常重要,因为它不仅确保交易不会消耗无限的天然气,而且还会在每个 DeliverTx 重置 ctx.GasMeter(每次 DeliverTx 被调用的时候都会执行 anteHandleranteHandler 运行之后 ctx 将会被设置为 newCtx)

如上所述,anteHandler 返回 transaction 执行期间所能消耗的最大的 gas 数量,称之为 GasWanted。最后实际 gas 消耗数量记为 GasUsed,因此我们必须使 GasUsed =< GasWanted。当返回时 GasWantedGasUsed 都会被中继到共识引擎中.