Skip to content

Commit

Permalink
Import blob fee API from [email protected] (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
ironbeer authored Nov 15, 2024
1 parent 8d236e6 commit 407fdb6
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 64 deletions.
10 changes: 9 additions & 1 deletion eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb"
Expand Down Expand Up @@ -381,10 +382,17 @@ func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
return b.gpo.SuggestTipCap(ctx)
}

func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) {
func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, baseFeePerBlobGas []*big.Int, blobGasUsedRatio []float64, err error) {
return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
}

func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int {
if excess := b.CurrentHeader().ExcessBlobGas; excess != nil {
return eip4844.CalcBlobFee(*excess)
}
return nil
}

func (b *EthAPIBackend) ChainDb() ethdb.Database {
return b.eth.ChainDb()
}
Expand Down
6 changes: 1 addition & 5 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
}

gpoParams := config.GPO
if gpoParams.Default == nil {
gpoParams.Default = config.Miner.GasPrice
}
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, config.GPO, config.Miner.GasPrice)

// Setup DNS discovery iterators.
dnsclient := dnsdisc.NewClient(dnsdisc.Config{})
Expand Down
74 changes: 52 additions & 22 deletions eth/gasprice/feehistory.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ import (
"fmt"
"math"
"math/big"
"slices"
"sync/atomic"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"golang.org/x/exp/slices"
)

var (
Expand All @@ -42,6 +44,8 @@ const (
// maxBlockFetchers is the max number of goroutines to spin up to pull blocks
// for the fee history calculation (mostly relevant for LES).
maxBlockFetchers = 4
// maxQueryLimit is the max number of requested percentiles.
maxQueryLimit = 100
)

// blockFees represents a single block for processing
Expand All @@ -63,9 +67,11 @@ type cacheKey struct {

// processedFees contains the results of a processed block.
type processedFees struct {
reward []*big.Int
baseFee, nextBaseFee *big.Int
gasUsedRatio float64
reward []*big.Int
baseFee, nextBaseFee *big.Int
gasUsedRatio float64
blobGasUsedRatio float64
blobBaseFee, nextBlobBaseFee *big.Int
}

// txGasAndReward is sorted in ascending order based on reward
Expand All @@ -78,16 +84,31 @@ type txGasAndReward struct {
// 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()
config := oracle.backend.ChainConfig()

// Fill in base fee and next base fee.
if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil {
bf.results.baseFee = new(big.Int)
}
if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
bf.results.nextBaseFee = eip1559.CalcBaseFee(chainconfig, bf.header)
if config.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
bf.results.nextBaseFee = eip1559.CalcBaseFee(config, bf.header)
} else {
bf.results.nextBaseFee = new(big.Int)
}
// Fill in blob base fee and next blob base fee.
if excessBlobGas := bf.header.ExcessBlobGas; excessBlobGas != nil {
bf.results.blobBaseFee = eip4844.CalcBlobFee(*excessBlobGas)
bf.results.nextBlobBaseFee = eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*excessBlobGas, *bf.header.BlobGasUsed))
} else {
bf.results.blobBaseFee = new(big.Int)
bf.results.nextBlobBaseFee = new(big.Int)
}
// Compute gas used ratio for normal and blob gas.
bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil {
bf.results.blobGasUsedRatio = float64(*blobGasUsed) / params.MaxBlobGasPerBlock
}

if len(percentiles) == 0 {
// rewards were not requested, return null
return
Expand Down Expand Up @@ -203,32 +224,37 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNum
// 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:
// Five 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
// - blobBaseFee: the blob base fee per gas in the given block
// - blobGasUsedRatio: blobGasUsed/blobGasLimit 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, blocks uint64, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
// Note: baseFee and blobBaseFee both include 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, blocks uint64, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error) {
if blocks < 1 {
return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
return common.Big0, nil, nil, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
}
maxFeeHistory := oracle.maxHeaderHistory
if len(rewardPercentiles) != 0 {
maxFeeHistory = oracle.maxBlockHistory
}
if len(rewardPercentiles) > maxQueryLimit {
return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: over the query limit %d", errInvalidPercentile, maxQueryLimit)
}
if blocks > maxFeeHistory {
log.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory)
blocks = maxFeeHistory
}
for i, p := range rewardPercentiles {
if p < 0 || p > 100 {
return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
}
if i > 0 && p <= rewardPercentiles[i-1] {
return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
}
}
var (
Expand All @@ -238,7 +264,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
)
pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks)
if err != nil || blocks == 0 {
return common.Big0, nil, nil, nil, err
return common.Big0, nil, nil, nil, nil, nil, err
}
oldestBlock := lastBlock + 1 - blocks

Expand Down Expand Up @@ -295,19 +321,22 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
}()
}
var (
reward = make([][]*big.Int, blocks)
baseFee = make([]*big.Int, blocks+1)
gasUsedRatio = make([]float64, blocks)
firstMissing = blocks
reward = make([][]*big.Int, blocks)
baseFee = make([]*big.Int, blocks+1)
gasUsedRatio = make([]float64, blocks)
blobGasUsedRatio = make([]float64, blocks)
blobBaseFee = make([]*big.Int, blocks+1)
firstMissing = blocks
)
for ; blocks > 0; blocks-- {
fees := <-results
if fees.err != nil {
return common.Big0, nil, nil, nil, fees.err
return common.Big0, nil, nil, nil, nil, nil, fees.err
}
i := fees.blockNumber - oldestBlock
if fees.results.baseFee != nil {
reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio
blobGasUsedRatio[i], blobBaseFee[i], blobBaseFee[i+1] = fees.results.blobGasUsedRatio, fees.results.blobBaseFee, fees.results.nextBlobBaseFee
} else {
// getting no block and no error means we are requesting into the future (might happen because of a reorg)
if i < firstMissing {
Expand All @@ -316,13 +345,14 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
}
}
if firstMissing == 0 {
return common.Big0, nil, nil, nil, nil
return common.Big0, nil, nil, nil, nil, nil, nil
}
if len(rewardPercentiles) != 0 {
reward = reward[:firstMissing]
} else {
reward = nil
}
baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing]
return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil
blobBaseFee, blobGasUsedRatio = blobBaseFee[:firstMissing+1], blobGasUsedRatio[:firstMissing]
return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, blobBaseFee, blobGasUsedRatio, nil
}
12 changes: 9 additions & 3 deletions eth/gasprice/feehistory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ func TestFeeHistory(t *testing.T) {
MaxHeaderHistory: c.maxHeader,
MaxBlockHistory: c.maxBlock,
}
backend := newTestBackend(t, big.NewInt(16), c.pending)
oracle := NewOracle(backend, config)
backend := newTestBackend(t, big.NewInt(16), big.NewInt(28), c.pending)
oracle := NewOracle(backend, config, nil)

first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
first, reward, baseFee, ratio, blobBaseFee, blobRatio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
backend.teardown()
expReward := c.expCount
if len(c.percent) == 0 {
Expand All @@ -84,6 +84,12 @@ func TestFeeHistory(t *testing.T) {
if len(ratio) != c.expCount {
t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio))
}
if len(blobRatio) != c.expCount {
t.Fatalf("Test case %d: blobGasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(blobRatio))
}
if len(blobBaseFee) != len(baseFee) {
t.Fatalf("Test case %d: blobBaseFee array length mismatch, want %d, got %d", i, len(baseFee), len(blobBaseFee))
}
if err != c.expErr && !errors.Is(err, c.expErr) {
t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err)
}
Expand Down
10 changes: 6 additions & 4 deletions eth/gasprice/gasprice.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package gasprice
import (
"context"
"math/big"
"slices"
"sync"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -29,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"golang.org/x/exp/slices"
)

const sampleNumber = 3 // Number of transactions sampled in a block
Expand All @@ -44,7 +44,6 @@ type Config struct {
Percentile int
MaxHeaderHistory uint64
MaxBlockHistory uint64
Default *big.Int `toml:",omitempty"`
MaxPrice *big.Int `toml:",omitempty"`
IgnorePrice *big.Int `toml:",omitempty"`
}
Expand Down Expand Up @@ -78,7 +77,7 @@ type Oracle struct {

// NewOracle returns a new gasprice oracle which can recommend suitable
// gasprice for newly created transaction.
func NewOracle(backend OracleBackend, params Config) *Oracle {
func NewOracle(backend OracleBackend, params Config, startPrice *big.Int) *Oracle {
blocks := params.Blocks
if blocks < 1 {
blocks = 1
Expand Down Expand Up @@ -114,6 +113,9 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
maxBlockHistory = 1
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
}
if startPrice == nil {
startPrice = new(big.Int)
}

cache := lru.NewCache[cacheKey, processedFees](2048)
headEvent := make(chan core.ChainHeadEvent, 1)
Expand All @@ -130,7 +132,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {

return &Oracle{
backend: backend,
lastPrice: params.Default,
lastPrice: startPrice,
maxPrice: maxPrice,
ignorePrice: ignorePrice,
checkBlocks: blocks,
Expand Down
Loading

0 comments on commit 407fdb6

Please sign in to comment.