eth/gasprice: implement feeHistory API (#23033)
* eth/gasprice: implement feeHistory API * eth/gasprice: factored out resolveBlockRange * eth/gasprice: add sanity check for missing block * eth/gasprice: fetch actual gas used from receipts * miner, eth/gasprice: add PendingBlockAndReceipts * internal/ethapi: use hexutil.Big * eth/gasprice: return error when requesting beyond head block * eth/gasprice: fixed tests and return errors correctly * eth/gasprice: rename receiver name * eth/gasprice: return directly if blockCount == 0 Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
This commit is contained in:
@@ -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}:
|
||||
|
Reference in New Issue
Block a user