diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index cc5af5625cc43aa2664c002aabbbfbcd490b814d..fdeb7031c64e5c9999663e35e59e299501434c28 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -1302,8 +1302,7 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
 	// If we are running the light client, apply another group
 	// settings for gas oracle.
 	if light {
-		cfg.Blocks = ethconfig.LightClientGPO.Blocks
-		cfg.Percentile = ethconfig.LightClientGPO.Percentile
+		*cfg = ethconfig.LightClientGPO
 	}
 	if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
 		cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name)
diff --git a/eth/api_backend.go b/eth/api_backend.go
index e6810f2a9bedd9f1981a7904d2862ddc77007a33..37c5d8a09f000aeb84c851b3d7fe55d59159d989 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -133,6 +133,10 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
 	return nil, errors.New("invalid arguments; neither block nor hash specified")
 }
 
+func (b *EthAPIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
+	return b.eth.miner.PendingBlockAndReceipts()
+}
+
 func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
 	// Pending state is only known by the miner
 	if number == rpc.PendingBlockNumber {
@@ -279,6 +283,10 @@ func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
 	return b.gpo.SuggestTipCap(ctx)
 }
 
+func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
+	return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
+}
+
 func (b *EthAPIBackend) ChainDb() ethdb.Database {
 	return b.eth.ChainDb()
 }
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 349c8da6cc2338c6ee09f8253e20b4b745361724..d971badafe548782367d7e8cf85d6b57ed10cb5b 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -41,18 +41,22 @@ import (
 
 // FullNodeGPO contains default gasprice oracle settings for full node.
 var FullNodeGPO = gasprice.Config{
-	Blocks:      20,
-	Percentile:  60,
-	MaxPrice:    gasprice.DefaultMaxPrice,
-	IgnorePrice: gasprice.DefaultIgnorePrice,
+	Blocks:           20,
+	Percentile:       60,
+	MaxHeaderHistory: 0,
+	MaxBlockHistory:  0,
+	MaxPrice:         gasprice.DefaultMaxPrice,
+	IgnorePrice:      gasprice.DefaultIgnorePrice,
 }
 
 // LightClientGPO contains default gasprice oracle settings for light client.
 var LightClientGPO = gasprice.Config{
-	Blocks:      2,
-	Percentile:  60,
-	MaxPrice:    gasprice.DefaultMaxPrice,
-	IgnorePrice: gasprice.DefaultIgnorePrice,
+	Blocks:           2,
+	Percentile:       60,
+	MaxHeaderHistory: 300,
+	MaxBlockHistory:  5,
+	MaxPrice:         gasprice.DefaultMaxPrice,
+	IgnorePrice:      gasprice.DefaultIgnorePrice,
 }
 
 // Defaults contains default settings for use on the Ethereum main net.
diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go
new file mode 100644
index 0000000000000000000000000000000000000000..84ec8e088993f2a78b10a99c2e971fab00fe87ec
--- /dev/null
+++ b/eth/gasprice/feehistory.go
@@ -0,0 +1,293 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package gasprice
+
+import (
+	"context"
+	"errors"
+	"math/big"
+	"sort"
+	"sync/atomic"
+
+	"github.com/ethereum/go-ethereum/consensus/misc"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rpc"
+)
+
+var (
+	errInvalidPercentiles = errors.New("Invalid reward percentiles")
+	errRequestBeyondHead  = errors.New("Request beyond head block")
+)
+
+const maxBlockCount = 1024 // number of blocks retrievable with a single query
+
+// blockFees represents a single block for processing
+type blockFees struct {
+	// set by the caller
+	blockNumber rpc.BlockNumber
+	header      *types.Header
+	block       *types.Block // only set if reward percentiles are requested
+	receipts    types.Receipts
+	// filled by processBlock
+	reward               []*big.Int
+	baseFee, nextBaseFee *big.Int
+	gasUsedRatio         float64
+	err                  error
+}
+
+// txGasAndReward is sorted in ascending order based on reward
+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
+}
+
+// processBlock takes a blockFees structure with the blockNumber, the header and optionally
+// the block field filled in, retrieves the block from the backend if not present yet and
+// fills in the rest of the fields.
+func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
+	chainconfig := oracle.backend.ChainConfig()
+	if bf.baseFee = bf.header.BaseFee; bf.baseFee == nil {
+		bf.baseFee = new(big.Int)
+	}
+	if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
+		bf.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header)
+	} else {
+		bf.nextBaseFee = new(big.Int)
+	}
+	bf.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
+	if len(percentiles) == 0 {
+		// rewards were not requested, return null
+		return
+	}
+	if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) {
+		log.Error("Block or receipts are missing while reward percentiles are requested")
+		return
+	}
+
+	bf.reward = make([]*big.Int, len(percentiles))
+	if len(bf.block.Transactions()) == 0 {
+		// return an all zero row if there are no transactions to gather data from
+		for i := range bf.reward {
+			bf.reward[i] = new(big.Int)
+		}
+		return
+	}
+
+	sorter := make(sortGasAndReward, len(bf.block.Transactions()))
+	for i, tx := range bf.block.Transactions() {
+		reward, _ := tx.EffectiveGasTip(bf.block.BaseFee())
+		sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward}
+	}
+	sort.Sort(sorter)
+
+	var txIndex int
+	sumGasUsed := sorter[0].gasUsed
+
+	for i, p := range percentiles {
+		thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100)
+		for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 {
+			txIndex++
+			sumGasUsed += sorter[txIndex].gasUsed
+		}
+		bf.reward[i] = sorter[txIndex].reward
+	}
+}
+
+// resolveBlockRange resolves the specified block range to absolute block numbers while also
+// enforcing backend specific limitations. The pending block and corresponding receipts are
+// also returned if requested and available.
+// Note: an error is only returned if retrieving the head header has failed. If there are no
+// retrievable blocks in the specified range then zero block count is returned with no error.
+func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlockNumber rpc.BlockNumber, blockCount, maxHistory int) (*types.Block, types.Receipts, rpc.BlockNumber, int, error) {
+	var (
+		headBlockNumber rpc.BlockNumber
+		pendingBlock    *types.Block
+		pendingReceipts types.Receipts
+	)
+
+	// query either pending block or head header and set headBlockNumber
+	if lastBlockNumber == rpc.PendingBlockNumber {
+		if pendingBlock, pendingReceipts = oracle.backend.PendingBlockAndReceipts(); pendingBlock != nil {
+			lastBlockNumber = rpc.BlockNumber(pendingBlock.NumberU64())
+			headBlockNumber = lastBlockNumber - 1
+		} else {
+			// pending block not supported by backend, process until latest block
+			lastBlockNumber = rpc.LatestBlockNumber
+			blockCount--
+			if blockCount == 0 {
+				return nil, nil, 0, 0, nil
+			}
+		}
+	}
+	if pendingBlock == nil {
+		// if pending block is not fetched then we retrieve the head header to get the head block number
+		if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil {
+			headBlockNumber = rpc.BlockNumber(latestHeader.Number.Uint64())
+		} else {
+			return nil, nil, 0, 0, err
+		}
+	}
+	if lastBlockNumber == rpc.LatestBlockNumber {
+		lastBlockNumber = headBlockNumber
+	} else if pendingBlock == nil && lastBlockNumber > headBlockNumber {
+		return nil, nil, 0, 0, errRequestBeyondHead
+	}
+	if maxHistory != 0 {
+		// limit retrieval to the given number of latest blocks
+		if tooOldCount := int64(headBlockNumber) - int64(maxHistory) - int64(lastBlockNumber) + int64(blockCount); tooOldCount > 0 {
+			// tooOldCount is the number of requested blocks that are too old to be served
+			if int64(blockCount) > tooOldCount {
+				blockCount -= int(tooOldCount)
+			} else {
+				return nil, nil, 0, 0, nil
+			}
+		}
+	}
+	// ensure not trying to retrieve before genesis
+	if rpc.BlockNumber(blockCount) > lastBlockNumber+1 {
+		blockCount = int(lastBlockNumber + 1)
+	}
+	return pendingBlock, pendingReceipts, lastBlockNumber, blockCount, nil
+}
+
+// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
+// The range can be specified either with absolute block numbers or ending with the latest
+// or pending block. Backends may or may not support gathering data from the pending block
+// or blocks older than a certain age (specified in maxHistory). The first block of the
+// actually processed range is returned to avoid ambiguity when parts of the requested range
+// are not available or when the head has changed during processing this request.
+// Three arrays are returned based on the processed blocks:
+// - reward: the requested percentiles of effective priority fees per gas of transactions in each
+//   block, sorted in ascending order and weighted by gas used.
+// - baseFee: base fee per gas in the given block
+// - gasUsedRatio: gasUsed/gasLimit in the given block
+// Note: baseFee includes the next block after the newest of the returned range, because this
+// value can be derived from the newest block.
+func (oracle *Oracle) FeeHistory(ctx context.Context, blockCount int, lastBlockNumber rpc.BlockNumber, rewardPercentiles []float64) (firstBlockNumber rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
+	if blockCount < 1 {
+		// returning with no data and no error means there are no retrievable blocks
+		return
+	}
+	if blockCount > maxBlockCount {
+		blockCount = maxBlockCount
+	}
+	for i, p := range rewardPercentiles {
+		if p < 0 || p > 100 || (i > 0 && p < rewardPercentiles[i-1]) {
+			return 0, nil, nil, nil, errInvalidPercentiles
+		}
+	}
+
+	processBlocks := len(rewardPercentiles) != 0
+	// limit retrieval to maxHistory if set
+	var maxHistory int
+	if processBlocks {
+		maxHistory = oracle.maxBlockHistory
+	} else {
+		maxHistory = oracle.maxHeaderHistory
+	}
+
+	var (
+		pendingBlock    *types.Block
+		pendingReceipts types.Receipts
+	)
+	if pendingBlock, pendingReceipts, lastBlockNumber, blockCount, err = oracle.resolveBlockRange(ctx, lastBlockNumber, blockCount, maxHistory); err != nil || blockCount == 0 {
+		return
+	}
+	firstBlockNumber = lastBlockNumber + 1 - rpc.BlockNumber(blockCount)
+
+	processNext := int64(firstBlockNumber)
+	resultCh := make(chan *blockFees, blockCount)
+	threadCount := 4
+	if blockCount < threadCount {
+		threadCount = blockCount
+	}
+	for i := 0; i < threadCount; i++ {
+		go func() {
+			for {
+				blockNumber := rpc.BlockNumber(atomic.AddInt64(&processNext, 1) - 1)
+				if blockNumber > lastBlockNumber {
+					return
+				}
+
+				bf := &blockFees{blockNumber: blockNumber}
+				if pendingBlock != nil && blockNumber >= rpc.BlockNumber(pendingBlock.NumberU64()) {
+					bf.block, bf.receipts = pendingBlock, pendingReceipts
+				} else {
+					if processBlocks {
+						bf.block, bf.err = oracle.backend.BlockByNumber(ctx, blockNumber)
+						if bf.block != nil {
+							bf.receipts, bf.err = oracle.backend.GetReceipts(ctx, bf.block.Hash())
+						}
+					} else {
+						bf.header, bf.err = oracle.backend.HeaderByNumber(ctx, blockNumber)
+					}
+				}
+				if bf.block != nil {
+					bf.header = bf.block.Header()
+				}
+				if bf.header != nil {
+					oracle.processBlock(bf, rewardPercentiles)
+				}
+				// send to resultCh even if empty to guarantee that blockCount items are sent in total
+				resultCh <- bf
+			}
+		}()
+	}
+
+	reward = make([][]*big.Int, blockCount)
+	baseFee = make([]*big.Int, blockCount+1)
+	gasUsedRatio = make([]float64, blockCount)
+	firstMissing := blockCount
+
+	for ; blockCount > 0; blockCount-- {
+		bf := <-resultCh
+		if bf.err != nil {
+			return 0, nil, nil, nil, bf.err
+		}
+		i := int(bf.blockNumber - firstBlockNumber)
+		if bf.header != nil {
+			reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = bf.reward, bf.baseFee, bf.nextBaseFee, bf.gasUsedRatio
+		} else {
+			// getting no block and no error means we are requesting into the future (might happen because of a reorg)
+			if i < firstMissing {
+				firstMissing = i
+			}
+		}
+	}
+	if firstMissing == 0 {
+		return 0, nil, nil, nil, nil
+	}
+	if processBlocks {
+		reward = reward[:firstMissing]
+	} else {
+		reward = nil
+	}
+	baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing]
+	return
+}
diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..191e2f0ce6cfe6d6a3feb26bc790a968a52ba202
--- /dev/null
+++ b/eth/gasprice/feehistory_test.go
@@ -0,0 +1,88 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package gasprice
+
+import (
+	"context"
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/rpc"
+)
+
+func TestFeeHistory(t *testing.T) {
+	var cases = []struct {
+		pending             bool
+		maxHeader, maxBlock int
+		count               int
+		last                rpc.BlockNumber
+		percent             []float64
+		expFirst            rpc.BlockNumber
+		expCount            int
+		expErr              error
+	}{
+		{false, 0, 0, 10, 30, nil, 21, 10, nil},
+		{false, 0, 0, 10, 30, []float64{0, 10}, 21, 10, nil},
+		{false, 0, 0, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentiles},
+		{false, 0, 0, 1000000000, 30, nil, 0, 31, nil},
+		{false, 0, 0, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil},
+		{false, 0, 0, 10, 40, nil, 0, 0, errRequestBeyondHead},
+		{true, 0, 0, 10, 40, nil, 0, 0, errRequestBeyondHead},
+		{false, 20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil},
+		{false, 20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil},
+		{false, 20, 2, 100, 32, []float64{0, 10}, 31, 2, nil},
+		{false, 0, 0, 1, rpc.PendingBlockNumber, nil, 0, 0, nil},
+		{false, 0, 0, 2, rpc.PendingBlockNumber, nil, 32, 1, nil},
+		{true, 0, 0, 2, rpc.PendingBlockNumber, nil, 32, 2, nil},
+		{true, 0, 0, 2, rpc.PendingBlockNumber, []float64{0, 10}, 32, 2, nil},
+	}
+	for i, c := range cases {
+		config := Config{
+			MaxHeaderHistory: c.maxHeader,
+			MaxBlockHistory:  c.maxBlock,
+		}
+		backend := newTestBackend(t, big.NewInt(16), c.pending)
+		oracle := NewOracle(backend, config)
+
+		first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
+
+		expReward := c.expCount
+		if len(c.percent) == 0 {
+			expReward = 0
+		}
+		expBaseFee := c.expCount
+		if expBaseFee != 0 {
+			expBaseFee++
+		}
+
+		if first != c.expFirst {
+			t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first)
+		}
+		if len(reward) != expReward {
+			t.Fatalf("Test case %d: reward array length mismatch, want %d, got %d", i, expReward, len(reward))
+		}
+		if len(baseFee) != expBaseFee {
+			t.Fatalf("Test case %d: baseFee array length mismatch, want %d, got %d", i, expBaseFee, len(baseFee))
+		}
+		if len(ratio) != c.expCount {
+			t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio))
+		}
+		if err != c.expErr {
+			t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err)
+		}
+	}
+}
diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go
index f2c4def1410ac4a1155dbbc906eb2b4f3512e9f7..407eeaa2899cc399f7dd57b9726ddc8d60515dc0 100644
--- a/eth/gasprice/gasprice.go
+++ b/eth/gasprice/gasprice.go
@@ -37,17 +37,21 @@ var (
 )
 
 type Config struct {
-	Blocks      int
-	Percentile  int
-	Default     *big.Int `toml:",omitempty"`
-	MaxPrice    *big.Int `toml:",omitempty"`
-	IgnorePrice *big.Int `toml:",omitempty"`
+	Blocks           int
+	Percentile       int
+	MaxHeaderHistory int
+	MaxBlockHistory  int
+	Default          *big.Int `toml:",omitempty"`
+	MaxPrice         *big.Int `toml:",omitempty"`
+	IgnorePrice      *big.Int `toml:",omitempty"`
 }
 
 // OracleBackend includes all necessary background APIs for oracle.
 type OracleBackend interface {
 	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
 	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
+	GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
+	PendingBlockAndReceipts() (*types.Block, types.Receipts)
 	ChainConfig() *params.ChainConfig
 }
 
@@ -62,8 +66,8 @@ type Oracle struct {
 	cacheLock   sync.RWMutex
 	fetchLock   sync.Mutex
 
-	checkBlocks int
-	percentile  int
+	checkBlocks, percentile           int
+	maxHeaderHistory, maxBlockHistory int
 }
 
 // NewOracle returns a new gasprice oracle which can recommend suitable
@@ -96,12 +100,14 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
 		log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice)
 	}
 	return &Oracle{
-		backend:     backend,
-		lastPrice:   params.Default,
-		maxPrice:    maxPrice,
-		ignorePrice: ignorePrice,
-		checkBlocks: blocks,
-		percentile:  percent,
+		backend:          backend,
+		lastPrice:        params.Default,
+		maxPrice:         maxPrice,
+		ignorePrice:      ignorePrice,
+		checkBlocks:      blocks,
+		percentile:       percent,
+		maxHeaderHistory: params.MaxHeaderHistory,
+		maxBlockHistory:  params.MaxBlockHistory,
 	}
 }
 
@@ -111,36 +117,36 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
 // Note, for legacy transactions and the legacy eth_gasPrice RPC call, it will be
 // necessary to add the basefee to the returned number to fall back to the legacy
 // behavior.
-func (gpo *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
-	head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
+func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
+	head, _ := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
 	headHash := head.Hash()
 
 	// If the latest gasprice is still available, return it.
-	gpo.cacheLock.RLock()
-	lastHead, lastPrice := gpo.lastHead, gpo.lastPrice
-	gpo.cacheLock.RUnlock()
+	oracle.cacheLock.RLock()
+	lastHead, lastPrice := oracle.lastHead, oracle.lastPrice
+	oracle.cacheLock.RUnlock()
 	if headHash == lastHead {
 		return new(big.Int).Set(lastPrice), nil
 	}
-	gpo.fetchLock.Lock()
-	defer gpo.fetchLock.Unlock()
+	oracle.fetchLock.Lock()
+	defer oracle.fetchLock.Unlock()
 
 	// Try checking the cache again, maybe the last fetch fetched what we need
-	gpo.cacheLock.RLock()
-	lastHead, lastPrice = gpo.lastHead, gpo.lastPrice
-	gpo.cacheLock.RUnlock()
+	oracle.cacheLock.RLock()
+	lastHead, lastPrice = oracle.lastHead, oracle.lastPrice
+	oracle.cacheLock.RUnlock()
 	if headHash == lastHead {
 		return new(big.Int).Set(lastPrice), nil
 	}
 	var (
 		sent, exp int
 		number    = head.Number.Uint64()
-		result    = make(chan results, gpo.checkBlocks)
+		result    = make(chan results, oracle.checkBlocks)
 		quit      = make(chan struct{})
 		results   []*big.Int
 	)
-	for sent < gpo.checkBlocks && number > 0 {
-		go gpo.getBlockValues(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit)
+	for sent < oracle.checkBlocks && number > 0 {
+		go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
 		sent++
 		exp++
 		number--
@@ -162,8 +168,8 @@ func (gpo *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
 		// Besides, in order to collect enough data for sampling, if nothing
 		// meaningful returned, try to query more blocks. But the maximum
 		// is 2*checkBlocks.
-		if len(res.values) == 1 && len(results)+1+exp < gpo.checkBlocks*2 && number > 0 {
-			go gpo.getBlockValues(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit)
+		if len(res.values) == 1 && len(results)+1+exp < oracle.checkBlocks*2 && number > 0 {
+			go oracle.getBlockValues(ctx, types.MakeSigner(oracle.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, oracle.ignorePrice, result, quit)
 			sent++
 			exp++
 			number--
@@ -173,15 +179,15 @@ func (gpo *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) {
 	price := lastPrice
 	if len(results) > 0 {
 		sort.Sort(bigIntArray(results))
-		price = results[(len(results)-1)*gpo.percentile/100]
+		price = results[(len(results)-1)*oracle.percentile/100]
 	}
-	if price.Cmp(gpo.maxPrice) > 0 {
-		price = new(big.Int).Set(gpo.maxPrice)
+	if price.Cmp(oracle.maxPrice) > 0 {
+		price = new(big.Int).Set(oracle.maxPrice)
 	}
-	gpo.cacheLock.Lock()
-	gpo.lastHead = headHash
-	gpo.lastPrice = price
-	gpo.cacheLock.Unlock()
+	oracle.cacheLock.Lock()
+	oracle.lastHead = headHash
+	oracle.lastPrice = price
+	oracle.cacheLock.Unlock()
 
 	return new(big.Int).Set(price), nil
 }
@@ -219,8 +225,8 @@ func (s *txSorter) Less(i, j int) bool {
 // and sends it to the result channel. If the block is empty or all transactions
 // are sent by the miner itself(it doesn't make any sense to include this kind of
 // transaction prices for sampling), nil gasprice is returned.
-func (gpo *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
-	block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
+func (oracle *Oracle) getBlockValues(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan results, quit chan struct{}) {
+	block, err := oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
 	if block == nil {
 		select {
 		case result <- results{nil, err}:
diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go
index f86449c5a5d07b025c00a900a8a073ecd5781f71..dea8fea95ae3dc0aa8edd46efc4a7135892f7075 100644
--- a/eth/gasprice/gasprice_test.go
+++ b/eth/gasprice/gasprice_test.go
@@ -33,29 +33,64 @@ import (
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
+const testHead = 32
+
 type testBackend struct {
-	chain *core.BlockChain
+	chain   *core.BlockChain
+	pending bool // pending block available
 }
 
 func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
+	if number > testHead {
+		return nil, nil
+	}
 	if number == rpc.LatestBlockNumber {
-		return b.chain.CurrentBlock().Header(), nil
+		number = testHead
+	}
+	if number == rpc.PendingBlockNumber {
+		if b.pending {
+			number = testHead + 1
+		} else {
+			return nil, nil
+		}
 	}
 	return b.chain.GetHeaderByNumber(uint64(number)), nil
 }
 
 func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
+	if number > testHead {
+		return nil, nil
+	}
 	if number == rpc.LatestBlockNumber {
-		return b.chain.CurrentBlock(), nil
+		number = testHead
+	}
+	if number == rpc.PendingBlockNumber {
+		if b.pending {
+			number = testHead + 1
+		} else {
+			return nil, nil
+		}
 	}
 	return b.chain.GetBlockByNumber(uint64(number)), nil
 }
 
+func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
+	return b.chain.GetReceiptsByHash(hash), nil
+}
+
+func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
+	if b.pending {
+		block := b.chain.GetBlockByNumber(testHead + 1)
+		return block, b.chain.GetReceiptsByHash(block.Hash())
+	}
+	return nil, nil
+}
+
 func (b *testBackend) ChainConfig() *params.ChainConfig {
 	return b.chain.Config()
 }
 
-func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend {
+func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend {
 	var (
 		key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
 		addr   = crypto.PubkeyToAddress(key.PublicKey)
@@ -76,7 +111,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend {
 	genesis, _ := gspec.Commit(db)
 
 	// Generate testing blocks
-	blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, 32, func(i int, b *core.BlockGen) {
+	blocks, _ := core.GenerateChain(gspec.Config, genesis, engine, db, testHead+1, func(i int, b *core.BlockGen) {
 		b.SetCoinbase(common.Address{1})
 
 		var tx *types.Transaction
@@ -116,7 +151,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int) *testBackend {
 		t.Fatalf("Failed to create local chain, %v", err)
 	}
 	chain.InsertChain(blocks)
-	return &testBackend{chain: chain}
+	return &testBackend{chain: chain, pending: pending}
 }
 
 func (b *testBackend) CurrentHeader() *types.Header {
@@ -144,7 +179,7 @@ func TestSuggestTipCap(t *testing.T) {
 		{big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future
 	}
 	for _, c := range cases {
-		backend := newTestBackend(t, c.fork)
+		backend := newTestBackend(t, c.fork, false)
 		oracle := NewOracle(backend, config)
 
 		// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 8cbb01fbba0879d1382b6449042c481ea1412c03..913cc0b974f9c0ab38df23fe70b7fca10a93b15b 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -80,6 +80,40 @@ func (s *PublicEthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.
 	return (*hexutil.Big)(tipcap), err
 }
 
+type feeHistoryResults struct {
+	FirstBlock   rpc.BlockNumber
+	Reward       [][]*hexutil.Big
+	BaseFee      []*hexutil.Big
+	GasUsedRatio []float64
+}
+
+func (s *PublicEthereumAPI) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (feeHistoryResults, error) {
+	firstBlock, reward, baseFee, gasUsedRatio, err := s.b.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
+	if err != nil {
+		return feeHistoryResults{}, err
+	}
+	results := feeHistoryResults{
+		FirstBlock:   firstBlock,
+		GasUsedRatio: gasUsedRatio,
+	}
+	if reward != nil {
+		results.Reward = make([][]*hexutil.Big, len(reward))
+		for j, w := range reward {
+			results.Reward[j] = make([]*hexutil.Big, len(w))
+			for i, v := range w {
+				results.Reward[j][i] = (*hexutil.Big)(v)
+			}
+		}
+	}
+	if baseFee != nil {
+		results.BaseFee = make([]*hexutil.Big, len(baseFee))
+		for i, v := range baseFee {
+			results.BaseFee[i] = (*hexutil.Big)(v)
+		}
+	}
+	return results, nil
+}
+
 // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
 // yet received the latest block headers from its pears. In case it is synchronizing:
 // - startingBlock: block number this node started to synchronise from
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index a0e2cf0892a77e8d13df98d32e99d5ac01f60624..90cfa852afd9c7e4217b626676427bad62e04218 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -42,6 +42,7 @@ type Backend interface {
 	// General Ethereum API
 	Downloader() *downloader.Downloader
 	SuggestGasTipCap(ctx context.Context) (*big.Int, error)
+	FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (rpc.BlockNumber, [][]*big.Int, []*big.Int, []float64, error)
 	ChainDb() ethdb.Database
 	AccountManager() *accounts.Manager
 	ExtRPCEnabled() bool
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index 8072a3dab910cfacee9fde681874d03a01f141d0..a7b326692186edc794063089bbb91d1a66398790 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -581,6 +581,12 @@ web3._extend({
 			params: 2,
 			inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter],
 		}),
+		new web3._extend.Method({
+			name: 'feeHistory',
+			call: 'eth_feeHistory',
+			params: 3,
+			inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter, null]
+		}),
 	],
 	properties: [
 		new web3._extend.Property({
diff --git a/les/api_backend.go b/les/api_backend.go
index a6ad7a38e7d7472dc8b2cc317d3d1dc536e4e1aa..2081ef64b1dd5e5e7fde95f9557a59f37468217e 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -60,7 +60,10 @@ func (b *LesApiBackend) SetHead(number uint64) {
 }
 
 func (b *LesApiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
-	if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber {
+	if number == rpc.PendingBlockNumber {
+		return nil, nil
+	}
+	if number == rpc.LatestBlockNumber {
 		return b.eth.blockchain.CurrentHeader(), nil
 	}
 	return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(number))
@@ -122,6 +125,10 @@ func (b *LesApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
 	return nil, errors.New("invalid arguments; neither block nor hash specified")
 }
 
+func (b *LesApiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
+	return nil, nil
+}
+
 func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
 	header, err := b.HeaderByNumber(ctx, number)
 	if err != nil {
@@ -255,6 +262,10 @@ func (b *LesApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
 	return b.gpo.SuggestTipCap(ctx)
 }
 
+func (b *LesApiBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock rpc.BlockNumber, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
+	return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
+}
+
 func (b *LesApiBackend) ChainDb() ethdb.Database {
 	return b.eth.chainDb
 }
diff --git a/miner/miner.go b/miner/miner.go
index 00c3d0cb5cfc9f0e51962f84265f4a09397a7718..7143679269512f78192834a07e11e2ce5ce4c516 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -194,6 +194,11 @@ func (miner *Miner) PendingBlock() *types.Block {
 	return miner.worker.pendingBlock()
 }
 
+// PendingBlockAndReceipts returns the currently pending block and corresponding receipts.
+func (miner *Miner) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
+	return miner.worker.pendingBlockAndReceipts()
+}
+
 func (miner *Miner) SetEtherbase(addr common.Address) {
 	miner.coinbase = addr
 	miner.worker.setEtherbase(addr)
diff --git a/miner/worker.go b/miner/worker.go
index b0b676ad020e387a92d6a63ad26eae33a1281823..c88963827e49197fb5b5b3c1397479f03f41e27c 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -162,9 +162,10 @@ type worker struct {
 	pendingMu    sync.RWMutex
 	pendingTasks map[common.Hash]*task
 
-	snapshotMu    sync.RWMutex // The lock used to protect the block snapshot and state snapshot
-	snapshotBlock *types.Block
-	snapshotState *state.StateDB
+	snapshotMu       sync.RWMutex // The lock used to protect the block snapshot and state snapshot
+	snapshotBlock    *types.Block
+	snapshotReceipts types.Receipts
+	snapshotState    *state.StateDB
 
 	// atomic status counters
 	running int32 // The indicator whether the consensus engine is running or not.
@@ -284,6 +285,14 @@ func (w *worker) pendingBlock() *types.Block {
 	return w.snapshotBlock
 }
 
+// pendingBlockAndReceipts returns pending block and corresponding receipts.
+func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) {
+	// return a snapshot to avoid contention on currentMu mutex
+	w.snapshotMu.RLock()
+	defer w.snapshotMu.RUnlock()
+	return w.snapshotBlock, w.snapshotReceipts
+}
+
 // start sets the running status as 1 and triggers new work submitting.
 func (w *worker) start() {
 	atomic.StoreInt32(&w.running, 1)
@@ -730,6 +739,7 @@ func (w *worker) updateSnapshot() {
 		w.current.receipts,
 		trie.NewStackTrie(nil),
 	)
+	w.snapshotReceipts = w.current.receipts
 	w.snapshotState = w.current.state.Copy()
 }