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

Commit

Permalink
rpc: eth_feeHistory (#734)
Browse files Browse the repository at this point in the history
* Problem: missing json rpc of eth_feeHistory #685

add oracle backend

space ready

structure ok

refactoring

return feehistory

data flow ok

basefee

set gas used ratio

computing reward

add testing

add gas used

prepare data

fill reward

increase coin

fixing api

add mac

add launch

gas used ratio ok

print element

reward workes

reward working

fix panic

value correct

remove debugging log

tidy up

tidy up

remove oracle

tidy up

fix handler crash

add unit test

tidy up

add limit check

reformat

fix lint

fix lint

fix lint

fix lint

Update rpc/ethereum/backend/feebackend.go

thanks

Co-authored-by: Federico Kunze Küllmer <[email protected]>

Update rpc/ethereum/backend/feebackend.go

Co-authored-by: Federico Kunze Küllmer <[email protected]>

Update rpc/ethereum/backend/feebackend.go

thanks

Co-authored-by: Federico Kunze Küllmer <[email protected]>

Update rpc/ethereum/backend/feebackend.go

Co-authored-by: Federico Kunze Küllmer <[email protected]>

fix compile error

split lines

remove temporary string conversion

return error if gaslimit is 0

move OneFeeHistory to types

add comment

only err check

Update rpc/ethereum/backend/feebackend.go

Co-authored-by: Federico Kunze Küllmer <[email protected]>

Update rpc/ethereum/backend/feebackend.go

Co-authored-by: Federico Kunze Küllmer <[email protected]>

tidy up

add feehistory-cap

* Apply suggestions from code review

* changelog

Co-authored-by: Federico Kunze Küllmer <[email protected]>
Co-authored-by: Federico Kunze Küllmer <[email protected]>
  • Loading branch information
3 people authored Nov 17, 2021
1 parent b7e8dd8 commit 392d1dd
Show file tree
Hide file tree
Showing 13 changed files with 287 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (evm) [tharsis#662](https:/tharsis/ethermint/pull/662) Disable basefee for non london blocks
* (cmd) [tharsis#712](https:/tharsis/ethermint/pull/712) add tx cli to build evm transaction
* (rpc) [tharsis#733](https:/tharsis/ethermint/pull/733) add JSON_RPC endpoint `personal_unpair`
* (rpc) [tharsis#734](https:/tharsis/ethermint/pull/734) add JSON_RPC endpoint `eth_feeHistory`
* (rpc) [tharsis#740](https:/tharsis/ethermint/pull/740) add JSON_RPC endpoint `personal_initializeWallet`
* (rpc) [tharsis#743](https:/tharsis/ethermint/pull/743) add JSON_RPC endpoint `debug_traceBlockByHash`
* (rpc) [tharsis#748](https:/tharsis/ethermint/pull/748) add JSON_RPC endpoint `personal_listWallets`
Expand Down
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) RPCFeeHistoryCap() int32 {
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
198 changes: 198 additions & 0 deletions rpc/ethereum/backend/feebackend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
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()

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(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
}
}
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) {
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])
}
}

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 the gas limit for each block
}
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

0 comments on commit 392d1dd

Please sign in to comment.