以太坊GasLimit的计算方法

作者: swapmem | 来源:发表于2019-05-21 10:01 被阅读11次

    静态Gas的计算

    以太坊黄皮书上说的gasLimit的计算方法:

    gasLimit = Gtransaction + Gtxdatanonzero × dataByteLength

    需要注意的是这只是静态的gas消耗,实际gas消耗还需要加上合约执行的开销。

    计算 IntrinsicGas的源码位置 core/state_transition.go

    // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
    func IntrinsicGas(data []byte, contractCreation, homestead bool) (uint64, error) {
        // Set the starting gas for the raw transaction
        var gas uint64
        if contractCreation && homestead {
            gas = params.TxGasContractCreation
        } else {
            gas = params.TxGas
        }
        // Bump the required gas by the amount of transactional data
        if len(data) > 0 {
            // Zero and non-zero bytes are priced differently
            var nz uint64
            for _, byt := range data {
                if byt != 0 {
                    nz++
                }
            }
            // Make sure we don't exceed uint64 for all data combinations
            if (math.MaxUint64-gas)/params.TxDataNonZeroGas < nz {
                return 0, vm.ErrOutOfGas
            }
            gas += nz * params.TxDataNonZeroGas
    
            z := uint64(len(data)) - nz
            if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
                return 0, vm.ErrOutOfGas
            }
            gas += z * params.TxDataZeroGas
        }
        return gas, nil
    }
    

    Gas预估

    相关源码位置:internal/ethapi/api.go

    func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
        // Binary search the gas requirement, as it may be higher than the amount used
        var (
            lo  uint64 = params.TxGas - 1
            hi  uint64
            cap uint64
        )
        if uint64(args.Gas) >= params.TxGas {
            hi = uint64(args.Gas)
        } else {
            // Retrieve the current pending block to act as the gas ceiling
            block, err := s.b.BlockByNumber(ctx, rpc.PendingBlockNumber)
            if err != nil {
                return 0, err
            }
            hi = block.GasLimit()
        }
        cap = hi
    
        // Create a helper to check if a gas allowance results in an executable transaction
        executable := func(gas uint64) bool {
            args.Gas = hexutil.Uint64(gas)
    
            _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, vm.Config{}, 0)
            if err != nil || failed {
                return false
            }
            return true
        }
        // Execute the binary search and hone in on an executable gas limit
        for lo+1 < hi {
            mid := (hi + lo) / 2
            if !executable(mid) {
                lo = mid
            } else {
                hi = mid
            }
        }
        // Reject the transaction as invalid if it still fails at the highest allowance
        if hi == cap {
            if !executable(hi) {
                return 0, fmt.Errorf("gas required exceeds allowance or always failing transaction")
            }
        }
        return hexutil.Uint64(hi), nil
    }
    

    EstimateGas采用二分查找法获取要评估交易的gas值。二分查找的下限是param.TxGas, 如果args参数指定Gas大于param.Gas,那么二分查找的上限就是args.Gas,否则以当前pending块的block gas limit(后面简称BGL)作为二分查找的上限。doCall函数模拟智能合约的执行,经过多次尝试找到智能合约能够成功运行的最佳gas值。

    由于二分查找的上限和BGL有关,而BGL和不是固定不变的,因此每次gas评估的结果不一定都是相同的,可能每个区块周期就会变动一次。

    在实际进行gas评估的时候,可能会出现类似下面的错误

    {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"gas required exceeds allowance or always failing transaction"}}
    

    该错误出现的最可能是合约执行中出错。

    参考链接

    How do you calculate gas limit for transaction with data in Ethereum?

    相关文章

      网友评论

        本文标题:以太坊GasLimit的计算方法

        本文链接:https://www.haomeiwen.com/subject/wukdzqtx.html