package backend import ( "fmt" "math/big" "sort" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" rpctypes "github.com/tharsis/ethermint/rpc/ethereum/types" evmtypes "github.com/tharsis/ethermint/x/evm/types" ) type ( txGasAndReward struct { gasUsed uint64 reward *big.Int } sortGasAndReward []txGasAndReward ) func (s sortGasAndReward) Len() int { return len(s) } func (s sortGasAndReward) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s sortGasAndReward) Less(i, j int) bool { return s[i].reward.Cmp(s[j].reward) < 0 } // output: targetOneFeeHistory func (e *EVMBackend) processBlock( tendermintBlock *tmrpctypes.ResultBlock, ethBlock *map[string]interface{}, rewardPercentiles []float64, tendermintBlockResult *tmrpctypes.ResultBlockResults, targetOneFeeHistory *rpctypes.OneFeeHistory, ) error { blockHeight := tendermintBlock.Block.Height blockBaseFee, err := e.BaseFee(blockHeight) if err != nil { return err } // set basefee targetOneFeeHistory.BaseFee = blockBaseFee // set gas used ratio gasLimitUint64, ok := (*ethBlock)["gasLimit"].(hexutil.Uint64) if !ok { return fmt.Errorf("invalid gas limit type: %T", (*ethBlock)["gasLimit"]) } gasUsedBig, ok := (*ethBlock)["gasUsed"].(*hexutil.Big) if !ok { return fmt.Errorf("invalid gas used type: %T", (*ethBlock)["gasUsed"]) } gasusedfloat, _ := new(big.Float).SetInt(gasUsedBig.ToInt()).Float64() if gasLimitUint64 <= 0 { return fmt.Errorf("gasLimit of block height %d should be bigger than 0 , current gaslimit %d", blockHeight, gasLimitUint64) } gasUsedRatio := gasusedfloat / float64(gasLimitUint64) blockGasUsed := gasusedfloat targetOneFeeHistory.GasUsedRatio = gasUsedRatio rewardCount := len(rewardPercentiles) targetOneFeeHistory.Reward = make([]*big.Int, rewardCount) for i := 0; i < rewardCount; i++ { targetOneFeeHistory.Reward[i] = big.NewInt(0) } // check tendermintTxs tendermintTxs := tendermintBlock.Block.Txs tendermintTxResults := tendermintBlockResult.TxsResults tendermintTxCount := len(tendermintTxs) var sorter sortGasAndReward for i := 0; i < tendermintTxCount; i++ { eachTendermintTx := tendermintTxs[i] eachTendermintTxResult := tendermintTxResults[i] tx, err := e.clientCtx.TxConfig.TxDecoder()(eachTendermintTx) if err != nil { e.logger.Debug("failed to decode transaction in block", "height", blockHeight, "error", err.Error()) continue } txGasUsed := uint64(eachTendermintTxResult.GasUsed) for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { continue } tx := ethMsg.AsTransaction() reward := tx.EffectiveGasTipValue(blockBaseFee) if reward == nil { reward = big.NewInt(0) } sorter = append(sorter, txGasAndReward{gasUsed: txGasUsed, reward: reward}) } } // return an all zero row if there are no transactions to gather data from ethTxCount := len(sorter) if ethTxCount == 0 { return nil } sort.Sort(sorter) var txIndex int sumGasUsed := sorter[0].gasUsed for i, p := range rewardPercentiles { thresholdGasUsed := uint64(blockGasUsed * p / 100) for sumGasUsed < thresholdGasUsed && txIndex < ethTxCount-1 { txIndex++ sumGasUsed += sorter[txIndex].gasUsed } targetOneFeeHistory.Reward[i] = sorter[txIndex].reward } return nil } // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. func (e *EVMBackend) FeeHistory( userBlockCount rpc.DecimalOrHex, // number blocks to fetch, maximum is 100 lastBlock rpc.BlockNumber, // the block to start search , to oldest rewardPercentiles []float64, // percentiles to fetch reward ) (*rpctypes.FeeHistoryResult, error) { blockEnd := int64(lastBlock) if blockEnd <= 0 { blockNumber, err := e.BlockNumber() if err != nil { return nil, err } blockEnd = int64(blockNumber) } userBlockCountInt := int64(userBlockCount) maxBlockCount := int64(e.cfg.JSONRPC.FeeHistoryCap) if userBlockCountInt > maxBlockCount { return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", userBlockCountInt, maxBlockCount) } blockStart := blockEnd - userBlockCountInt if blockStart < 0 { blockStart = 0 } blockCount := blockEnd - blockStart oldestBlock := (*hexutil.Big)(big.NewInt(blockStart)) // prepare space reward := make([][]*hexutil.Big, blockCount) rewardCount := len(rewardPercentiles) for i := 0; i < int(blockCount); i++ { reward[i] = make([]*hexutil.Big, rewardCount) } thisBaseFee := make([]*hexutil.Big, blockCount) thisGasUsedRatio := make([]float64, blockCount) // rewards should only be calculated if reward percentiles were included calculateRewards := rewardCount != 0 // fetch block for blockID := blockStart; blockID < blockEnd; blockID++ { index := int32(blockID - blockStart) // eth block ethBlock, err := e.GetBlockByNumber(rpctypes.BlockNumber(blockID), true) if ethBlock == nil { return nil, err } // tendermint block tendermintblock, err := e.GetTendermintBlockByNumber(rpctypes.BlockNumber(blockID)) if tendermintblock == nil { return nil, err } // tendermint block result tendermintBlockResult, err := e.clientCtx.Client.BlockResults(e.ctx, &tendermintblock.Block.Height) if tendermintBlockResult == nil { e.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error()) return nil, err } oneFeeHistory := rpctypes.OneFeeHistory{} err = e.processBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory) if err != nil { return nil, err } // copy thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee) thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio if calculateRewards { for j := 0; j < rewardCount; j++ { reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j]) if reward[index][j] == nil { reward[index][j] = (*hexutil.Big)(big.NewInt(0)) } } } } feeHistory := rpctypes.FeeHistoryResult{ OldestBlock: oldestBlock, BaseFee: thisBaseFee, GasUsedRatio: thisGasUsedRatio, } if calculateRewards { feeHistory.Reward = reward } return &feeHistory, nil }