6.6 KiB
原文路径:https://github.com/cosmos/cosmos-sdk/blob/master/docs/basics/gas-fees.md
Gas and Fees
必备阅读 {hide}
- 一个SDK程序的剖析 {prereq}
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使用两种不同的 GasMeter
, main gas meter和 block gas meter.
主 Gas Meter
ctx.GasMeter()
是应用程序的主 GasMeter
,主 GasMeter
通过BeginBlock
中的setDeliverState
进行初始化,然后跟踪导致状态转换的执行序列中的gas
消耗.也即是说它的更新由BeginBlock
, DeliverTx
和EndBlock
进行操作.主 GasMeter
必须在 AnteHandler
中设置为0,以便它能获取每个transaction的Gas消耗
gas
消耗可以手工完成,模块开发者通常在 BeginBlocker
, EndBlocker
或者 handler
上执行,但大多数情况下,只要对储存区进行了读写,它就会自动完成.这种自动消耗的逻辑在GasKv
中完成.
块 Gas Meter
ctx.BlockGasMeter()
是跟踪每个区块gas
消耗并保证它没有超过限制的GasMeter
.每当BeginBlock
被调用的时候一个新的 BlockGasMeter
实例将会被创建. BlockGasMeter
的gas
是有限的,每个块的gas
限制应该在应用程序的共识参数中定义,Cosmos SDK应用程序使用Tendermint提供的默认共识参数:
+++ f323c80cb3/types/params.go (L65-L72)
当通过DeliverTx
处理新的transaction的时候,BlockGasMeter
的当前值会被校验是否超过上限,如果超过上限,DeliverTx
直接返回,由于BeginBlock
会消耗gas
,这种情况可能会在第一个transaction
到来时发生,如果没有发生这种情况,transaction
将会被正常的执行.在DeliverTx
的最后,ctx.BlockGasMeter()
会追踪gas
消耗并将它增加到处理transaction
的gas
消耗中.
ctx.BlockGasMeter().ConsumeGas(
ctx.GasMeter().GasConsumedToLimit(),
"block gas meter",
)
AnteHandler
AnteHandler
是一个特殊的处理程序,它在CheckTx
和 DeliverTx
期间为每一个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
被调用的时候都会执行anteHandler
,anteHandler
运行之后ctx
将会被设置为newCtx
)
如上所述,anteHandler
返回transaction
执行期间所能消耗的最大的gas
数量,称之为GasWanted
.最后实际gas
消耗数量记为GasUsed
,因此我们必须使GasUsed =< GasWanted
.当返回时GasWanted
和 GasUsed
都会被中继到共识引擎中.