949674e511
* fix(rpc): align fee history (#1611) * update nix * add next fee in fee history * fix test * add change doc * height + 1 for next fee * cross check baseFeePerGas len * Update tests/integration_tests/test_fee_history.py Co-authored-by: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com> * fix oldestBlock & align earliest input as eth * update doc * update nix * isort test_fee_history.py * fix test * align rpc res as eth * add cross check * add baseFeePerGas len check * add oldestBlock check Co-authored-by: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com> Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> (cherry picked from commit 47fdfd3d8318358c2578b610bc35bc8c1ebb131e) # Conflicts: # CHANGELOG.md * address merge conflicts Co-authored-by: mmsqe <mavis@crypto.com> Co-authored-by: MalteHerrmann <malte@evmos.org> Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
283 lines
9.1 KiB
Go
283 lines
9.1 KiB
Go
// Copyright 2021 Evmos Foundation
|
|
// This file is part of Evmos' Ethermint library.
|
|
//
|
|
// The Ethermint library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The Ethermint library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE
|
|
package backend
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
|
ethermint "github.com/evmos/ethermint/types"
|
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
|
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
|
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
)
|
|
|
|
// ChainID is the EIP-155 replay-protection chain id for the current ethereum chain config.
|
|
func (b *Backend) ChainID() (*hexutil.Big, error) {
|
|
eip155ChainID, err := ethermint.ParseChainID(b.clientCtx.ChainID)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
|
|
bn, err := b.BlockNumber()
|
|
if err != nil {
|
|
b.logger.Debug("failed to fetch latest block number", "error", err.Error())
|
|
return (*hexutil.Big)(eip155ChainID), nil
|
|
}
|
|
|
|
if config := b.ChainConfig(); config.IsEIP155(new(big.Int).SetUint64(uint64(bn))) {
|
|
return (*hexutil.Big)(config.ChainID), nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block")
|
|
}
|
|
|
|
// ChainConfig returns the latest ethereum chain configuration
|
|
func (b *Backend) ChainConfig() *params.ChainConfig {
|
|
params, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return params.Params.ChainConfig.EthereumConfig(b.chainID)
|
|
}
|
|
|
|
// GlobalMinGasPrice returns MinGasPrice param from FeeMarket
|
|
func (b *Backend) GlobalMinGasPrice() (sdk.Dec, error) {
|
|
res, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{})
|
|
if err != nil {
|
|
return sdk.ZeroDec(), err
|
|
}
|
|
return res.Params.MinGasPrice, nil
|
|
}
|
|
|
|
// BaseFee returns the base fee tracked by the Fee Market module.
|
|
// If the base fee is not enabled globally, the query returns nil.
|
|
// If the London hard fork is not activated at the current height, the query will
|
|
// return nil.
|
|
func (b *Backend) BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error) {
|
|
// return BaseFee if London hard fork is activated and feemarket is enabled
|
|
res, err := b.queryClient.BaseFee(rpctypes.ContextWithHeight(blockRes.Height), &evmtypes.QueryBaseFeeRequest{})
|
|
if err != nil || res.BaseFee == nil {
|
|
// we can't tell if it's london HF not enabled or the state is pruned,
|
|
// in either case, we'll fallback to parsing from begin blocker event,
|
|
// faster to iterate reversely
|
|
for i := len(blockRes.BeginBlockEvents) - 1; i >= 0; i-- {
|
|
evt := blockRes.BeginBlockEvents[i]
|
|
if evt.Type == feemarkettypes.EventTypeFeeMarket && len(evt.Attributes) > 0 {
|
|
baseFee, err := strconv.ParseInt(string(evt.Attributes[0].Value), 10, 64)
|
|
if err == nil {
|
|
return big.NewInt(baseFee), nil
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if res.BaseFee == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return res.BaseFee.BigInt(), nil
|
|
}
|
|
|
|
// CurrentHeader returns the latest block header
|
|
func (b *Backend) CurrentHeader() *ethtypes.Header {
|
|
header, _ := b.HeaderByNumber(rpctypes.EthLatestBlockNumber)
|
|
return header
|
|
}
|
|
|
|
// PendingTransactions returns the transactions that are in the transaction pool
|
|
// and have a from address that is one of the accounts this node manages.
|
|
func (b *Backend) PendingTransactions() ([]*sdk.Tx, error) {
|
|
res, err := b.clientCtx.Client.UnconfirmedTxs(b.ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make([]*sdk.Tx, 0, len(res.Txs))
|
|
for _, txBz := range res.Txs {
|
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, &tx)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetCoinbase is the address that staking rewards will be send to (alias for Etherbase).
|
|
func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
|
|
node, err := b.clientCtx.GetNode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
status, err := node.Status(b.ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &evmtypes.QueryValidatorAccountRequest{
|
|
ConsAddress: sdk.ConsAddress(status.ValidatorInfo.Address).String(),
|
|
}
|
|
|
|
res, err := b.queryClient.ValidatorAccount(b.ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
address, _ := sdk.AccAddressFromBech32(res.AccountAddress)
|
|
return address, nil
|
|
}
|
|
|
|
// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
|
|
func (b *Backend) 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 := b.BlockNumber()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blockEnd = int64(blockNumber)
|
|
}
|
|
|
|
blocks := int64(userBlockCount)
|
|
maxBlockCount := int64(b.cfg.JSONRPC.FeeHistoryCap)
|
|
if blocks > maxBlockCount {
|
|
return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", blocks, maxBlockCount)
|
|
}
|
|
|
|
if blockEnd+1 < blocks {
|
|
blocks = blockEnd + 1
|
|
}
|
|
// Ensure not trying to retrieve before genesis.
|
|
blockStart := blockEnd + 1 - blocks
|
|
oldestBlock := (*hexutil.Big)(big.NewInt(blockStart))
|
|
|
|
// prepare space
|
|
reward := make([][]*hexutil.Big, blocks)
|
|
rewardCount := len(rewardPercentiles)
|
|
for i := 0; i < int(blocks); i++ {
|
|
reward[i] = make([]*hexutil.Big, rewardCount)
|
|
}
|
|
|
|
thisBaseFee := make([]*hexutil.Big, blocks+1)
|
|
thisGasUsedRatio := make([]float64, blocks)
|
|
|
|
// 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)
|
|
// tendermint block
|
|
tendermintblock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(blockID))
|
|
if tendermintblock == nil {
|
|
return nil, err
|
|
}
|
|
|
|
// eth block
|
|
ethBlock, err := b.GetBlockByNumber(rpctypes.BlockNumber(blockID), true)
|
|
if ethBlock == nil {
|
|
return nil, err
|
|
}
|
|
|
|
// tendermint block result
|
|
tendermintBlockResult, err := b.TendermintBlockResultByNumber(&tendermintblock.Block.Height)
|
|
if tendermintBlockResult == nil {
|
|
b.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
oneFeeHistory := rpctypes.OneFeeHistory{}
|
|
err = b.processBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// copy
|
|
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
|
|
thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee)
|
|
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
|
|
}
|
|
|
|
// SuggestGasTipCap returns the suggested tip cap
|
|
// Although we don't support tx prioritization yet, but we return a positive value to help client to
|
|
// mitigate the base fee changes.
|
|
func (b *Backend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) {
|
|
if baseFee == nil {
|
|
// london hardfork not enabled or feemarket not enabled
|
|
return big.NewInt(0), nil
|
|
}
|
|
|
|
params, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// calculate the maximum base fee delta in current block, assuming all block gas limit is consumed
|
|
// ```
|
|
// GasTarget = GasLimit / ElasticityMultiplier
|
|
// Delta = BaseFee * (GasUsed - GasTarget) / GasTarget / Denominator
|
|
// ```
|
|
// The delta is at maximum when `GasUsed` is equal to `GasLimit`, which is:
|
|
// ```
|
|
// MaxDelta = BaseFee * (GasLimit - GasLimit / ElasticityMultiplier) / (GasLimit / ElasticityMultiplier) / Denominator
|
|
// = BaseFee * (ElasticityMultiplier - 1) / Denominator
|
|
// ```
|
|
maxDelta := baseFee.Int64() * (int64(params.Params.ElasticityMultiplier) - 1) / int64(params.Params.BaseFeeChangeDenominator)
|
|
if maxDelta < 0 {
|
|
// impossible if the parameter validation passed.
|
|
maxDelta = 0
|
|
}
|
|
return big.NewInt(maxDelta), nil
|
|
}
|