Skip to content

Commit

Permalink
Merge pull request #253 from m-Peter/add-eth-fee-history-endpoint
Browse files Browse the repository at this point in the history
Add a first implementation for `eth_feeHistory` JSON-RPC API endpoint
  • Loading branch information
sideninja authored Jun 10, 2024
2 parents ff54643 + 0244943 commit e5b6691
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 30 deletions.
115 changes: 87 additions & 28 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
storageErrs "github.com/onflow/flow-evm-gateway/storage/errors"
)

const maxFeeHistoryBlockCount = 1024

func SupportedAPIs(
blockChainAPI *BlockChainAPI,
streamAPI *StreamAPI,
Expand Down Expand Up @@ -625,7 +627,7 @@ func (b *BlockChainAPI) EstimateGas(
tx, err := encodeTxFromArgs(args)
if err != nil {
b.logger.Error().Err(err).Msg("failed to encode transaction for gas estimate")
return hexutil.Uint64(defaultGasLimit), nil // return default gas limit
return hexutil.Uint64(blockGasLimit), nil // return block gas limit
}

// Default address in case user does not provide one
Expand Down Expand Up @@ -722,15 +724,16 @@ func (b *BlockChainAPI) prepareBlockResponse(
}

blockResponse := &Block{
Hash: h,
Number: hexutil.Uint64(block.Height),
ParentHash: block.ParentBlockHash,
ReceiptsRoot: block.ReceiptRoot,
Transactions: block.TransactionHashes,
Uncles: []common.Hash{},
GasLimit: hexutil.Uint64(15_000_000),
Nonce: types.BlockNonce{0x1},
Timestamp: hexutil.Uint64(block.Timestamp),
Hash: h,
Number: hexutil.Uint64(block.Height),
ParentHash: block.ParentBlockHash,
ReceiptsRoot: block.ReceiptRoot,
Transactions: block.TransactionHashes,
Uncles: []common.Hash{},
GasLimit: hexutil.Uint64(blockGasLimit),
Nonce: types.BlockNonce{0x1},
Timestamp: hexutil.Uint64(block.Timestamp),
BaseFeePerGas: hexutil.Big(*big.NewInt(0)),
}

// todo remove after previewnet, temp fix to mock some of the timestamps
Expand Down Expand Up @@ -789,6 +792,80 @@ func (b *BlockChainAPI) getBlockNumber(blockNumberOrHash *rpc.BlockNumberOrHash)
return 0, fmt.Errorf("invalid arguments; neither block nor hash specified")
}

// FeeHistory returns transaction base fee per gas and effective priority fee
// per gas for the requested/supported block range.
// blockCount: Requested range of blocks. Clients will return less than the
// requested range if not all blocks are available.
// lastBlock: Highest block of the requested range.
// rewardPercentiles: A monotonically increasing list of percentile values.
// For each block in the requested range, the transactions will be sorted in
// ascending order by effective tip per gas and the coresponding effective tip
// for the percentile will be determined, accounting for gas consumed.
func (b *BlockChainAPI) FeeHistory(
ctx context.Context,
blockCount math.HexOrDecimal64,
lastBlock rpc.BlockNumber,
rewardPercentiles []float64,
) (*FeeHistoryResult, error) {
if blockCount > maxFeeHistoryBlockCount {
return nil, fmt.Errorf("block count has to be between 1 and 1024")
}

lastBlockNumber := uint64(lastBlock)
var err error
if lastBlock < 0 {
// From the special block tags, we only support "latest".
lastBlockNumber, err = b.blocks.LatestEVMHeight()
if err != nil {
return nil, fmt.Errorf("could not fetch latest EVM block number")
}
}

var oldestBlock *hexutil.Big
baseFees := []*hexutil.Big{}
rewards := [][]*hexutil.Big{}
gasUsedRatios := []float64{}

maxCount := uint64(blockCount)
if maxCount > lastBlockNumber {
maxCount = lastBlockNumber
}

blockRewards := make([]*hexutil.Big, len(rewardPercentiles))
for i := range rewardPercentiles {
blockRewards[i] = (*hexutil.Big)(b.config.GasPrice)
}

for i := maxCount; i >= uint64(1); i-- {
// If the requested block count is 5, and the last block number
// is 20, then we need the blocks [16, 17, 18, 19, 20] in this
// specific order. The first block we fetch is 20 - 5 + 1 = 16.
blockHeight := lastBlockNumber - i + 1
block, err := b.blocks.GetByHeight(blockHeight)
if err != nil {
continue
}

if i == maxCount {
oldestBlock = (*hexutil.Big)(big.NewInt(int64(block.Height)))
}

baseFees = append(baseFees, (*hexutil.Big)(big.NewInt(0)))

rewards = append(rewards, blockRewards)

gasUsedRatio := float64(block.TotalGasUsed) / float64(blockGasLimit)
gasUsedRatios = append(gasUsedRatios, gasUsedRatio)
}

return &FeeHistoryResult{
OldestBlock: oldestBlock,
Reward: rewards,
BaseFee: baseFees,
GasUsedRatio: gasUsedRatios,
}, nil
}

/* ====================================================================================================================
NOT SUPPORTED SECTION
Expand Down Expand Up @@ -903,24 +980,6 @@ func (b *BlockChainAPI) CreateAccessList(
return nil, errs.ErrNotSupported
}

// FeeHistory returns transaction base fee per gas and effective priority fee
// per gas for the requested/supported block range.
// blockCount: Requested range of blocks. Clients will return less than the
// requested range if not all blocks are available.
// lastBlock: Highest block of the requested range.
// rewardPercentiles: A monotonically increasing list of percentile values.
// For each block in the requested range, the transactions will be sorted in
// ascending order by effective tip per gas and the coresponding effective tip
// for the percentile will be determined, accounting for gas consumed.
func (b *BlockChainAPI) FeeHistory(
ctx context.Context,
blockCount math.HexOrDecimal64,
lastBlock rpc.BlockNumber,
rewardPercentiles []float64,
) (*FeeHistoryResult, error) {
return nil, errs.ErrNotSupported
}

// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
func (b *BlockChainAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) {
return nil, errs.ErrNotSupported
Expand Down
4 changes: 2 additions & 2 deletions api/encode_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/onflow/go-ethereum/core/types"
)

const defaultGasLimit uint64 = 15_000_000
const blockGasLimit uint64 = 15_000_000

// encodeTxFromArgs will create a transaction from the given arguments.
// The resulting unsigned transaction is only supposed to be used through
Expand All @@ -23,7 +23,7 @@ func encodeTxFromArgs(args TransactionArgs) ([]byte, error) {

// provide a high enough gas for the tx to be able to execute,
// capped by the gas set in transaction args.
gasLimit := defaultGasLimit
gasLimit := blockGasLimit
if args.Gas != nil {
gasLimit = uint64(*args.Gas)
}
Expand Down
1 change: 1 addition & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ type Block struct {
Transactions interface{} `json:"transactions"`
Uncles []common.Hash `json:"uncles"`
MixHash common.Hash `json:"mixHash"`
BaseFeePerGas hexutil.Big `json:"baseFeePerGas"`
}

type SyncStatus struct {
Expand Down
15 changes: 15 additions & 0 deletions tests/web3js/eth_non_interactive_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const web3Utils = require('web3-utils')
const { assert } = require('chai')
const conf = require('./config')
const helpers = require('./helpers')
const web3 = conf.web3

it('get chain ID', async () => {
Expand Down Expand Up @@ -194,3 +195,17 @@ it('can make batch requests', async () => {
assert.equal(error.innerError[0].message, 'batch too large')
}
})

it('get fee history', async () => {
let response = await web3.eth.getFeeHistory(10, 'latest', [20])

assert.deepEqual(
response,
{
oldestBlock: 1n,
reward: [['0x0'], ['0x0'], ['0x0']], // gas price is always 0 during testing
baseFeePerGas: [0n, 0n, 0n],
gasUsedRatio: [0.04684, 0.0014036666666666666, 0.0014]
}
)
})

0 comments on commit e5b6691

Please sign in to comment.