Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Problem: missing json rpc of eth_feeHistory #685 #734

Merged
merged 4 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion rpc/ethereum/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"

"google.golang.org/grpc"
Expand Down Expand Up @@ -42,6 +43,9 @@ import (
// Backend implements the functionality shared within namespaces.
// Implemented by EVMBackend.
type Backend interface {
// Fee API
FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*types.FeeHistoryResult, error)

// General Ethereum API
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
Expand Down Expand Up @@ -76,7 +80,6 @@ type Backend interface {
GetLogs(hash common.Hash) ([][]*ethtypes.Log, error)
GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error)
GetFilteredBlocks(from int64, to int64, filter [][]filters.BloomIV, filterAddresses bool) ([]int64, error)

ChainConfig() *params.ChainConfig
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock) []*evmtypes.MsgEthereumTx
Expand Down Expand Up @@ -889,6 +892,11 @@ func (e *EVMBackend) RPCFilterCap() int32 {
return e.cfg.JSONRPC.FilterCap
}

// RPCFeeHistoryCap is the limit for total number of blocks that can be fetched
func (e *EVMBackend) RPCFeeHistgoryCap() int32 {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
return e.cfg.JSONRPC.FeeHistoryCap
}

// RPCMinGasPrice returns the minimum gas price for a transaction obtained from
// the node config. If set value is 0, it will default to 20.

Expand Down
197 changes: 197 additions & 0 deletions rpc/ethereum/backend/feebackend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
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 gasused ratio
gasLimitUint64 := (*ethBlock)["gasLimit"].(hexutil.Uint64)
gasUsedBig := (*ethBlock)["gasUsed"].(*hexutil.Big)
gasusedfloat, _ := new(big.Float).SetInt(gasUsedBig.ToInt()).Float64()
var gasUsedRatio float64
if gasLimitUint64 > 0 {
gasUsedRatio = gasusedfloat / float64(gasLimitUint64)
} else {
return fmt.Errorf("gasLimit of block height %d should be bigger than 0 , current gaslimit %d", blockHeight, gasLimitUint64)
}
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
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(2000)
}

// check tendermintTxs
tendermintTxs := tendermintBlock.Block.Txs
tendermintTxResults := tendermintBlockResult.TxsResults
tendermintTxCount := len(tendermintTxs)
sorter := make(sortGasAndReward, tendermintTxCount)

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)
sorter[i] = txGasAndReward{gasUsed: txGasUsed, reward: reward}
break
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the purpose of this break here, are we only supporting 1 MsgEthereumTx message?

}
}
sort.Sort(sorter)

var txIndex int
sumGasUsed := uint64(0)
if len(sorter) > 0 {
sumGasUsed = sorter[0].gasUsed
}
for i, p := range rewardPercentiles {
thresholdGasUsed := uint64(blockGasUsed * p / 100)
for sumGasUsed < thresholdGasUsed && txIndex < tendermintTxCount-1 {
txIndex++
sumGasUsed += sorter[txIndex].gasUsed
}

chosenReward := big.NewInt(0)
if 0 <= txIndex && txIndex < len(sorter) {
chosenReward = sorter[txIndex].reward
}
targetOneFeeHistory.Reward[i] = chosenReward
}

return nil
}

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) {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
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)

// 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, &ethBlock, rewardPercentiles, tendermintBlockResult, &onefeehistory)
if err != nil {
return nil, err
}

// copy
thisBaseFee[index] = (*hexutil.Big)(onefeehistory.BaseFee)
thisGasUsedRatio[index] = onefeehistory.GasUsedRatio
for j := 0; j < rewardcount; j++ {
reward[index][j] = (*hexutil.Big)(onefeehistory.Reward[j])
}

fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}

feeHistory := rpctypes.FeeHistoryResult{
OldestBlock: oldestBlock,
Reward: reward,
BaseFee: thisBaseFee,
GasUsedRatio: thisGasUsedRatio,
}
return &feeHistory, nil
}
3 changes: 1 addition & 2 deletions rpc/ethereum/namespaces/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,7 @@ func (e *PublicAPI) MaxPriorityFeePerGas() (*hexutil.Big, error) {

func (e *PublicAPI) FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error) {
e.logger.Debug("eth_feeHistory")

return nil, fmt.Errorf("eth_feeHistory not implemented")
return e.backend.FeeHistory(blockCount, lastBlock, rewardPercentiles)
}

// Accounts returns the list of accounts available to this node.
Expand Down
8 changes: 8 additions & 0 deletions rpc/ethereum/types/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package types

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -79,3 +81,9 @@ type SignTransactionResult struct {
Raw hexutil.Bytes `json:"raw"`
Tx *ethtypes.Transaction `json:"tx"`
}

type OneFeeHistory struct {
BaseFee *big.Int /* base fee for each block */
Reward []*big.Int /* each element of the array will have the tip provided to miners for the percentile given */
GasUsedRatio float64 /* the ratio of gas used to gas limit for each block */
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}
42 changes: 26 additions & 16 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (

DefaultFilterCap int32 = 200

DefaultFeeHistoryCap int32 = 100

DefaultEVMTimeout = 5 * time.Second
// default 1.0 eth
DefaultTxFeeCap float64 = 1.0
Expand Down Expand Up @@ -72,6 +74,8 @@ type JSONRPCConfig struct {
TxFeeCap float64 `mapstructure:"txfee-cap"`
// FilterCap is the global cap for total number of filters that can be created.
FilterCap int32 `mapstructure:"filter-cap"`
// FeeHistoryCap is the global cap for total number of blocks that can be fetched
FeeHistoryCap int32 `mapstructure:"feehistory-cap"`
// Enable defines if the EVM RPC server should be enabled.
Enable bool `mapstructure:"enable"`
}
Expand Down Expand Up @@ -153,14 +157,15 @@ func GetDefaultAPINamespaces() []string {
// DefaultJSONRPCConfig returns an EVM config with the JSON-RPC API enabled by default
func DefaultJSONRPCConfig() *JSONRPCConfig {
return &JSONRPCConfig{
Enable: true,
API: GetDefaultAPINamespaces(),
Address: DefaultJSONRPCAddress,
WsAddress: DefaultJSONRPCWsAddress,
GasCap: DefaultGasCap,
EVMTimeout: DefaultEVMTimeout,
TxFeeCap: DefaultTxFeeCap,
FilterCap: DefaultFilterCap,
Enable: true,
API: GetDefaultAPINamespaces(),
Address: DefaultJSONRPCAddress,
WsAddress: DefaultJSONRPCWsAddress,
GasCap: DefaultGasCap,
EVMTimeout: DefaultEVMTimeout,
TxFeeCap: DefaultTxFeeCap,
FilterCap: DefaultFilterCap,
FeeHistoryCap: DefaultFeeHistoryCap,
}
}

Expand All @@ -174,6 +179,10 @@ func (c JSONRPCConfig) Validate() error {
return errors.New("JSON-RPC filter-cap cannot be negative")
}

if c.FeeHistoryCap <= 0 {
return errors.New("JSON-RPC feehistory-cap cannot be negative or 0")
}

if c.TxFeeCap < 0 {
return errors.New("JSON-RPC tx fee cap cannot be negative")
}
Expand Down Expand Up @@ -230,14 +239,15 @@ func GetConfig(v *viper.Viper) Config {
Tracer: v.GetString("evm.tracer"),
},
JSONRPC: JSONRPCConfig{
Enable: v.GetBool("json-rpc.enable"),
API: v.GetStringSlice("json-rpc.api"),
Address: v.GetString("json-rpc.address"),
WsAddress: v.GetString("json-rpc.ws-address"),
GasCap: v.GetUint64("json-rpc.gas-cap"),
FilterCap: v.GetInt32("json-rpc.filter-cap"),
TxFeeCap: v.GetFloat64("json-rpc.txfee-cap"),
EVMTimeout: v.GetDuration("json-rpc.evm-timeout"),
Enable: v.GetBool("json-rpc.enable"),
API: v.GetStringSlice("json-rpc.api"),
Address: v.GetString("json-rpc.address"),
WsAddress: v.GetString("json-rpc.ws-address"),
GasCap: v.GetUint64("json-rpc.gas-cap"),
FilterCap: v.GetInt32("json-rpc.filter-cap"),
FeeHistoryCap: v.GetInt32("json-rpc.feehistory-cap"),
TxFeeCap: v.GetFloat64("json-rpc.txfee-cap"),
EVMTimeout: v.GetDuration("json-rpc.evm-timeout"),
},
TLS: TLSConfig{
CertificatePath: v.GetString("tls.certificate-path"),
Expand Down
4 changes: 4 additions & 0 deletions server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ txfee-cap = {{ .JSONRPC.TxFeeCap }}
# FilterCap sets the global cap for total number of filters that can be created
filter-cap = {{ .JSONRPC.FilterCap }}

# FeeHistoryCap sets the global cap for total number of blocks that can be fetched
feehistory-cap = {{ .JSONRPC.FeeHistoryCap }}


###############################################################################
### TLS Configuration ###
###############################################################################
Expand Down
17 changes: 9 additions & 8 deletions server/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ const (

// JSON-RPC flags
const (
JSONRPCEnable = "json-rpc.enable"
JSONRPCAPI = "json-rpc.api"
JSONRPCAddress = "json-rpc.address"
JSONWsAddress = "json-rpc.ws-address"
JSONRPCGasCap = "json-rpc.gas-cap"
JSONRPCEVMTimeout = "json-rpc.evm-timeout"
JSONRPCTxFeeCap = "json-rpc.txfee-cap"
JSONRPCFilterCap = "json-rpc.filter-cap"
JSONRPCEnable = "json-rpc.enable"
JSONRPCAPI = "json-rpc.api"
JSONRPCAddress = "json-rpc.address"
JSONWsAddress = "json-rpc.ws-address"
JSONRPCGasCap = "json-rpc.gas-cap"
JSONRPCEVMTimeout = "json-rpc.evm-timeout"
JSONRPCTxFeeCap = "json-rpc.txfee-cap"
JSONRPCFilterCap = "json-rpc.filter-cap"
JSONRPFeeHistoryCap = "json-rpc.feehistory-cap"
)

// EVM flags
Expand Down
Loading