Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth: Add eth_blobBaseFee RPC and blob fields to eth_feeHistory #29140

Merged
merged 11 commits into from
Apr 22, 2024
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 @@ -361,10 +362,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
67 changes: 46 additions & 21 deletions eth/gasprice/feehistory.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import (

"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"
)

Expand Down Expand Up @@ -63,9 +65,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 +82,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))
lightclient marked this conversation as resolved.
Show resolved Hide resolved
} 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,17 +222,19 @@ 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) {
ryanschneider marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand All @@ -225,10 +246,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
}
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 +259,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 +316,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 +340,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]
ryanschneider marked this conversation as resolved.
Show resolved Hide resolved
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
}
10 changes: 8 additions & 2 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)
backend := newTestBackend(t, big.NewInt(16), big.NewInt(28), c.pending)
oracle := NewOracle(backend, config)

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
63 changes: 55 additions & 8 deletions eth/gasprice/gasprice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,26 @@ package gasprice

import (
"context"
"crypto/sha256"
"fmt"
"math"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
)

const testHead = 32
Expand Down Expand Up @@ -121,7 +126,10 @@ func (b *testBackend) teardown() {

// newTestBackend creates a test backend. OBS: don't forget to invoke tearDown
// after use, otherwise the blockchain instance will mem-leak via goroutines.
func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend {
func newTestBackend(t *testing.T, londonBlock *big.Int, cancunBlock *big.Int, pending bool) *testBackend {
if londonBlock != nil && cancunBlock != nil && londonBlock.Cmp(cancunBlock) == 1 {
panic("cannot define test backend with cancun before london")
}
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)
Expand All @@ -131,15 +139,27 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
Alloc: types.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}},
}
signer = types.LatestSigner(gspec.Config)

// Compute empty blob hash.
emptyBlob = kzg4844.Blob{}
emptyBlobCommit, _ = kzg4844.BlobToCommitment(&emptyBlob)
emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
)
config.LondonBlock = londonBlock
config.ArrowGlacierBlock = londonBlock
config.GrayGlacierBlock = londonBlock
config.TerminalTotalDifficulty = common.Big0
engine := ethash.NewFaker()
var engine consensus.Engine = beacon.New(ethash.NewFaker())
td := params.GenesisDifficulty.Uint64()

if cancunBlock != nil {
ts := gspec.Timestamp + cancunBlock.Uint64()*10 // fixed 10 sec block time in blockgen
config.ShanghaiTime = &ts
config.CancunTime = &ts
signer = types.LatestSigner(gspec.Config)
}

// Generate testing blocks
_, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, testHead+1, func(i int, b *core.BlockGen) {
db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, testHead+1, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{1})

var txdata types.TxData
Expand All @@ -164,15 +184,42 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
}
}
b.AddTx(types.MustSignNewTx(key, signer, txdata))

if cancunBlock != nil && b.Number().Cmp(cancunBlock) >= 0 {
b.SetPoS()

// put more blobs in each new block
for j := 0; j < i && j < 6; j++ {
blobTx := &types.BlobTx{
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
Nonce: b.TxNonce(addr),
To: common.Address{},
Gas: 30000,
GasFeeCap: uint256.NewInt(100 * params.GWei),
GasTipCap: uint256.NewInt(uint64(i+1) * params.GWei),
Data: []byte{},
BlobFeeCap: uint256.NewInt(1),
BlobHashes: []common.Hash{emptyBlobVHash},
Value: uint256.NewInt(100),
Sidecar: nil,
}
b.AddTx(types.MustSignNewTx(key, signer, blobTx))
}
}
td += b.Difficulty().Uint64()
})
// Construct testing chain
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil)
gspec.Config.TerminalTotalDifficulty = new(big.Int).SetUint64(td)
chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("Failed to create local chain, %v", err)
}
chain.InsertChain(blocks)
if i, err := chain.InsertChain(blocks); err != nil {
panic(fmt.Errorf("error inserting block %d: %w", i, err))
}
chain.SetFinalized(chain.GetBlockByNumber(25).Header())
chain.SetSafe(chain.GetBlockByNumber(25).Header())

return &testBackend{chain: chain, pending: pending}
}

Expand Down Expand Up @@ -201,7 +248,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, false)
backend := newTestBackend(t, c.fork, nil, false)
oracle := NewOracle(backend, config)

// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
Expand Down
31 changes: 24 additions & 7 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import (
"time"

"github.com/davecgh/go-spew/spew"
"github.com/holiman/uint256"
"github.com/tyler-smith/go-bip39"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
Expand All @@ -48,8 +51,6 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
"github.com/tyler-smith/go-bip39"
)

// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is
Expand Down Expand Up @@ -90,15 +91,17 @@ func (s *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, e
}

type feeHistoryResult struct {
OldestBlock *hexutil.Big `json:"oldestBlock"`
Reward [][]*hexutil.Big `json:"reward,omitempty"`
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
GasUsedRatio []float64 `json:"gasUsedRatio"`
OldestBlock *hexutil.Big `json:"oldestBlock"`
Reward [][]*hexutil.Big `json:"reward,omitempty"`
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
GasUsedRatio []float64 `json:"gasUsedRatio"`
BlobBaseFee []*hexutil.Big `json:"baseFeePerBlobGas,omitempty"`
BlobGasUsedRatio []float64 `json:"blobGasUsedRatio,omitempty"`
}

// FeeHistory returns the fee market history.
func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
oldest, reward, baseFee, gasUsed, blobBaseFee, blobGasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
if err != nil {
return nil, err
}
Expand All @@ -121,9 +124,23 @@ func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecim
results.BaseFee[i] = (*hexutil.Big)(v)
}
}
if blobBaseFee != nil {
results.BlobBaseFee = make([]*hexutil.Big, len(blobBaseFee))
for i, v := range blobBaseFee {
results.BlobBaseFee[i] = (*hexutil.Big)(v)
}
}
if blobGasUsed != nil {
results.BlobGasUsedRatio = blobGasUsed
}
return results, nil
}

// BlobBaseFee returns the base fee for blob gas at the current head.
func (s *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
return (*hexutil.Big)(s.b.BlobBaseFee(ctx))
}

// 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 synchronize from
Expand Down
Loading
Loading