Skip to content

Commit 1ec7af2

Browse files
ryanschneiderlightclientfjl
authored
eth: Add eth_blobBaseFee RPC and blob fields to eth_feeHistory (#29140)
Co-authored-by: lightclient <[email protected]> Co-authored-by: Felix Lange <[email protected]>
1 parent c2dfe7a commit 1ec7af2

File tree

9 files changed

+167
-54
lines changed

9 files changed

+167
-54
lines changed

eth/api_backend.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/ethereum/go-ethereum/accounts"
2727
"github.com/ethereum/go-ethereum/common"
2828
"github.com/ethereum/go-ethereum/consensus"
29+
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
2930
"github.com/ethereum/go-ethereum/core"
3031
"github.com/ethereum/go-ethereum/core/bloombits"
3132
"github.com/ethereum/go-ethereum/core/rawdb"
@@ -361,10 +362,17 @@ func (b *EthAPIBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
361362
return b.gpo.SuggestTipCap(ctx)
362363
}
363364

364-
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) {
365+
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) {
365366
return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles)
366367
}
367368

369+
func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int {
370+
if excess := b.CurrentHeader().ExcessBlobGas; excess != nil {
371+
return eip4844.CalcBlobFee(*excess)
372+
}
373+
return nil
374+
}
375+
368376
func (b *EthAPIBackend) ChainDb() ethdb.Database {
369377
return b.eth.ChainDb()
370378
}

eth/gasprice/feehistory.go

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ import (
2828

2929
"github.com/ethereum/go-ethereum/common"
3030
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
31+
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
3132
"github.com/ethereum/go-ethereum/core/types"
3233
"github.com/ethereum/go-ethereum/log"
34+
"github.com/ethereum/go-ethereum/params"
3335
"github.com/ethereum/go-ethereum/rpc"
3436
)
3537

@@ -63,9 +65,11 @@ type cacheKey struct {
6365

6466
// processedFees contains the results of a processed block.
6567
type processedFees struct {
66-
reward []*big.Int
67-
baseFee, nextBaseFee *big.Int
68-
gasUsedRatio float64
68+
reward []*big.Int
69+
baseFee, nextBaseFee *big.Int
70+
gasUsedRatio float64
71+
blobGasUsedRatio float64
72+
blobBaseFee, nextBlobBaseFee *big.Int
6973
}
7074

7175
// txGasAndReward is sorted in ascending order based on reward
@@ -78,16 +82,31 @@ type txGasAndReward struct {
7882
// the block field filled in, retrieves the block from the backend if not present yet and
7983
// fills in the rest of the fields.
8084
func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
81-
chainconfig := oracle.backend.ChainConfig()
85+
config := oracle.backend.ChainConfig()
86+
87+
// Fill in base fee and next base fee.
8288
if bf.results.baseFee = bf.header.BaseFee; bf.results.baseFee == nil {
8389
bf.results.baseFee = new(big.Int)
8490
}
85-
if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
86-
bf.results.nextBaseFee = eip1559.CalcBaseFee(chainconfig, bf.header)
91+
if config.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) {
92+
bf.results.nextBaseFee = eip1559.CalcBaseFee(config, bf.header)
8793
} else {
8894
bf.results.nextBaseFee = new(big.Int)
8995
}
96+
// Fill in blob base fee and next blob base fee.
97+
if excessBlobGas := bf.header.ExcessBlobGas; excessBlobGas != nil {
98+
bf.results.blobBaseFee = eip4844.CalcBlobFee(*excessBlobGas)
99+
bf.results.nextBlobBaseFee = eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*excessBlobGas, *bf.header.BlobGasUsed))
100+
} else {
101+
bf.results.blobBaseFee = new(big.Int)
102+
bf.results.nextBlobBaseFee = new(big.Int)
103+
}
104+
// Compute gas used ratio for normal and blob gas.
90105
bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
106+
if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil {
107+
bf.results.blobGasUsedRatio = float64(*blobGasUsed) / params.MaxBlobGasPerBlock
108+
}
109+
91110
if len(percentiles) == 0 {
92111
// rewards were not requested, return null
93112
return
@@ -203,17 +222,19 @@ func (oracle *Oracle) resolveBlockRange(ctx context.Context, reqEnd rpc.BlockNum
203222
// or blocks older than a certain age (specified in maxHistory). The first block of the
204223
// actually processed range is returned to avoid ambiguity when parts of the requested range
205224
// are not available or when the head has changed during processing this request.
206-
// Three arrays are returned based on the processed blocks:
225+
// Five arrays are returned based on the processed blocks:
207226
// - reward: the requested percentiles of effective priority fees per gas of transactions in each
208227
// block, sorted in ascending order and weighted by gas used.
209228
// - baseFee: base fee per gas in the given block
210229
// - gasUsedRatio: gasUsed/gasLimit in the given block
230+
// - blobBaseFee: the blob base fee per gas in the given block
231+
// - blobGasUsedRatio: blobGasUsed/blobGasLimit in the given block
211232
//
212-
// Note: baseFee includes the next block after the newest of the returned range, because this
213-
// value can be derived from the newest block.
214-
func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedLastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) {
233+
// Note: baseFee and blobBaseFee both include the next block after the newest of the returned range,
234+
// because this value can be derived from the newest block.
235+
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) {
215236
if blocks < 1 {
216-
return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
237+
return common.Big0, nil, nil, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks
217238
}
218239
maxFeeHistory := oracle.maxHeaderHistory
219240
if len(rewardPercentiles) != 0 {
@@ -225,10 +246,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
225246
}
226247
for i, p := range rewardPercentiles {
227248
if p < 0 || p > 100 {
228-
return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
249+
return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p)
229250
}
230251
if i > 0 && p <= rewardPercentiles[i-1] {
231-
return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
252+
return common.Big0, nil, nil, nil, nil, nil, fmt.Errorf("%w: #%d:%f >= #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p)
232253
}
233254
}
234255
var (
@@ -238,7 +259,7 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
238259
)
239260
pendingBlock, pendingReceipts, lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks)
240261
if err != nil || blocks == 0 {
241-
return common.Big0, nil, nil, nil, err
262+
return common.Big0, nil, nil, nil, nil, nil, err
242263
}
243264
oldestBlock := lastBlock + 1 - blocks
244265

@@ -295,19 +316,22 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
295316
}()
296317
}
297318
var (
298-
reward = make([][]*big.Int, blocks)
299-
baseFee = make([]*big.Int, blocks+1)
300-
gasUsedRatio = make([]float64, blocks)
301-
firstMissing = blocks
319+
reward = make([][]*big.Int, blocks)
320+
baseFee = make([]*big.Int, blocks+1)
321+
gasUsedRatio = make([]float64, blocks)
322+
blobGasUsedRatio = make([]float64, blocks)
323+
blobBaseFee = make([]*big.Int, blocks+1)
324+
firstMissing = blocks
302325
)
303326
for ; blocks > 0; blocks-- {
304327
fees := <-results
305328
if fees.err != nil {
306-
return common.Big0, nil, nil, nil, fees.err
329+
return common.Big0, nil, nil, nil, nil, nil, fees.err
307330
}
308331
i := fees.blockNumber - oldestBlock
309332
if fees.results.baseFee != nil {
310333
reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio
334+
blobGasUsedRatio[i], blobBaseFee[i], blobBaseFee[i+1] = fees.results.blobGasUsedRatio, fees.results.blobBaseFee, fees.results.nextBlobBaseFee
311335
} else {
312336
// getting no block and no error means we are requesting into the future (might happen because of a reorg)
313337
if i < firstMissing {
@@ -316,13 +340,14 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL
316340
}
317341
}
318342
if firstMissing == 0 {
319-
return common.Big0, nil, nil, nil, nil
343+
return common.Big0, nil, nil, nil, nil, nil, nil
320344
}
321345
if len(rewardPercentiles) != 0 {
322346
reward = reward[:firstMissing]
323347
} else {
324348
reward = nil
325349
}
326350
baseFee, gasUsedRatio = baseFee[:firstMissing+1], gasUsedRatio[:firstMissing]
327-
return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil
351+
blobBaseFee, blobGasUsedRatio = blobBaseFee[:firstMissing+1], blobGasUsedRatio[:firstMissing]
352+
return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, blobBaseFee, blobGasUsedRatio, nil
328353
}

eth/gasprice/feehistory_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ func TestFeeHistory(t *testing.T) {
5858
MaxHeaderHistory: c.maxHeader,
5959
MaxBlockHistory: c.maxBlock,
6060
}
61-
backend := newTestBackend(t, big.NewInt(16), c.pending)
61+
backend := newTestBackend(t, big.NewInt(16), big.NewInt(28), c.pending)
6262
oracle := NewOracle(backend, config)
6363

64-
first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
64+
first, reward, baseFee, ratio, blobBaseFee, blobRatio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent)
6565
backend.teardown()
6666
expReward := c.expCount
6767
if len(c.percent) == 0 {
@@ -84,6 +84,12 @@ func TestFeeHistory(t *testing.T) {
8484
if len(ratio) != c.expCount {
8585
t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio))
8686
}
87+
if len(blobRatio) != c.expCount {
88+
t.Fatalf("Test case %d: blobGasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(blobRatio))
89+
}
90+
if len(blobBaseFee) != len(baseFee) {
91+
t.Fatalf("Test case %d: blobBaseFee array length mismatch, want %d, got %d", i, len(baseFee), len(blobBaseFee))
92+
}
8793
if err != c.expErr && !errors.Is(err, c.expErr) {
8894
t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err)
8995
}

eth/gasprice/gasprice_test.go

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,26 @@ package gasprice
1818

1919
import (
2020
"context"
21+
"crypto/sha256"
22+
"fmt"
2123
"math"
2224
"math/big"
2325
"testing"
2426

2527
"github.com/ethereum/go-ethereum/common"
28+
"github.com/ethereum/go-ethereum/consensus"
29+
"github.com/ethereum/go-ethereum/consensus/beacon"
2630
"github.com/ethereum/go-ethereum/consensus/ethash"
2731
"github.com/ethereum/go-ethereum/core"
28-
"github.com/ethereum/go-ethereum/core/rawdb"
2932
"github.com/ethereum/go-ethereum/core/state"
3033
"github.com/ethereum/go-ethereum/core/types"
3134
"github.com/ethereum/go-ethereum/core/vm"
3235
"github.com/ethereum/go-ethereum/crypto"
36+
"github.com/ethereum/go-ethereum/crypto/kzg4844"
3337
"github.com/ethereum/go-ethereum/event"
3438
"github.com/ethereum/go-ethereum/params"
3539
"github.com/ethereum/go-ethereum/rpc"
40+
"github.com/holiman/uint256"
3641
)
3742

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

122127
// newTestBackend creates a test backend. OBS: don't forget to invoke tearDown
123128
// after use, otherwise the blockchain instance will mem-leak via goroutines.
124-
func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBackend {
129+
func newTestBackend(t *testing.T, londonBlock *big.Int, cancunBlock *big.Int, pending bool) *testBackend {
130+
if londonBlock != nil && cancunBlock != nil && londonBlock.Cmp(cancunBlock) == 1 {
131+
panic("cannot define test backend with cancun before london")
132+
}
125133
var (
126134
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
127135
addr = crypto.PubkeyToAddress(key.PublicKey)
@@ -131,15 +139,27 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
131139
Alloc: types.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}},
132140
}
133141
signer = types.LatestSigner(gspec.Config)
142+
143+
// Compute empty blob hash.
144+
emptyBlob = kzg4844.Blob{}
145+
emptyBlobCommit, _ = kzg4844.BlobToCommitment(&emptyBlob)
146+
emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
134147
)
135148
config.LondonBlock = londonBlock
136149
config.ArrowGlacierBlock = londonBlock
137150
config.GrayGlacierBlock = londonBlock
138-
config.TerminalTotalDifficulty = common.Big0
139-
engine := ethash.NewFaker()
151+
var engine consensus.Engine = beacon.New(ethash.NewFaker())
152+
td := params.GenesisDifficulty.Uint64()
153+
154+
if cancunBlock != nil {
155+
ts := gspec.Timestamp + cancunBlock.Uint64()*10 // fixed 10 sec block time in blockgen
156+
config.ShanghaiTime = &ts
157+
config.CancunTime = &ts
158+
signer = types.LatestSigner(gspec.Config)
159+
}
140160

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

145165
var txdata types.TxData
@@ -164,15 +184,42 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke
164184
}
165185
}
166186
b.AddTx(types.MustSignNewTx(key, signer, txdata))
187+
188+
if cancunBlock != nil && b.Number().Cmp(cancunBlock) >= 0 {
189+
b.SetPoS()
190+
191+
// put more blobs in each new block
192+
for j := 0; j < i && j < 6; j++ {
193+
blobTx := &types.BlobTx{
194+
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
195+
Nonce: b.TxNonce(addr),
196+
To: common.Address{},
197+
Gas: 30000,
198+
GasFeeCap: uint256.NewInt(100 * params.GWei),
199+
GasTipCap: uint256.NewInt(uint64(i+1) * params.GWei),
200+
Data: []byte{},
201+
BlobFeeCap: uint256.NewInt(1),
202+
BlobHashes: []common.Hash{emptyBlobVHash},
203+
Value: uint256.NewInt(100),
204+
Sidecar: nil,
205+
}
206+
b.AddTx(types.MustSignNewTx(key, signer, blobTx))
207+
}
208+
}
209+
td += b.Difficulty().Uint64()
167210
})
168211
// Construct testing chain
169-
chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil)
212+
gspec.Config.TerminalTotalDifficulty = new(big.Int).SetUint64(td)
213+
chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil)
170214
if err != nil {
171215
t.Fatalf("Failed to create local chain, %v", err)
172216
}
173-
chain.InsertChain(blocks)
217+
if i, err := chain.InsertChain(blocks); err != nil {
218+
panic(fmt.Errorf("error inserting block %d: %w", i, err))
219+
}
174220
chain.SetFinalized(chain.GetBlockByNumber(25).Header())
175221
chain.SetSafe(chain.GetBlockByNumber(25).Header())
222+
176223
return &testBackend{chain: chain, pending: pending}
177224
}
178225

@@ -201,7 +248,7 @@ func TestSuggestTipCap(t *testing.T) {
201248
{big.NewInt(33), big.NewInt(params.GWei * int64(30))}, // Fork point in the future
202249
}
203250
for _, c := range cases {
204-
backend := newTestBackend(t, c.fork, false)
251+
backend := newTestBackend(t, c.fork, nil, false)
205252
oracle := NewOracle(backend, config)
206253

207254
// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G

internal/ethapi/api.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import (
2626
"time"
2727

2828
"github.com/davecgh/go-spew/spew"
29+
"github.com/holiman/uint256"
30+
"github.com/tyler-smith/go-bip39"
31+
2932
"github.com/ethereum/go-ethereum/accounts"
3033
"github.com/ethereum/go-ethereum/accounts/keystore"
3134
"github.com/ethereum/go-ethereum/accounts/scwallet"
@@ -48,8 +51,6 @@ import (
4851
"github.com/ethereum/go-ethereum/rlp"
4952
"github.com/ethereum/go-ethereum/rpc"
5053
"github.com/ethereum/go-ethereum/trie"
51-
"github.com/holiman/uint256"
52-
"github.com/tyler-smith/go-bip39"
5354
)
5455

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

9293
type feeHistoryResult struct {
93-
OldestBlock *hexutil.Big `json:"oldestBlock"`
94-
Reward [][]*hexutil.Big `json:"reward,omitempty"`
95-
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
96-
GasUsedRatio []float64 `json:"gasUsedRatio"`
94+
OldestBlock *hexutil.Big `json:"oldestBlock"`
95+
Reward [][]*hexutil.Big `json:"reward,omitempty"`
96+
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
97+
GasUsedRatio []float64 `json:"gasUsedRatio"`
98+
BlobBaseFee []*hexutil.Big `json:"baseFeePerBlobGas,omitempty"`
99+
BlobGasUsedRatio []float64 `json:"blobGasUsedRatio,omitempty"`
97100
}
98101

99102
// FeeHistory returns the fee market history.
100103
func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
101-
oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
104+
oldest, reward, baseFee, gasUsed, blobBaseFee, blobGasUsed, err := s.b.FeeHistory(ctx, uint64(blockCount), lastBlock, rewardPercentiles)
102105
if err != nil {
103106
return nil, err
104107
}
@@ -121,9 +124,23 @@ func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecim
121124
results.BaseFee[i] = (*hexutil.Big)(v)
122125
}
123126
}
127+
if blobBaseFee != nil {
128+
results.BlobBaseFee = make([]*hexutil.Big, len(blobBaseFee))
129+
for i, v := range blobBaseFee {
130+
results.BlobBaseFee[i] = (*hexutil.Big)(v)
131+
}
132+
}
133+
if blobGasUsed != nil {
134+
results.BlobGasUsedRatio = blobGasUsed
135+
}
124136
return results, nil
125137
}
126138

139+
// BlobBaseFee returns the base fee for blob gas at the current head.
140+
func (s *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
141+
return (*hexutil.Big)(s.b.BlobBaseFee(ctx))
142+
}
143+
127144
// Syncing returns false in case the node is currently not syncing with the network. It can be up-to-date or has not
128145
// yet received the latest block headers from its pears. In case it is synchronizing:
129146
// - startingBlock: block number this node started to synchronize from

0 commit comments

Comments
 (0)