laconicd-deprecated/rpc/backend/chain_info.go
mergify[bot] 949674e511
fix(rpc): align fee history (backport #1611) (#1620)
* 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>
2023-01-20 16:49:30 +01:00

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, &ethBlock, 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
}