From 7305023635f5961287a32f792189d93313272ff4 Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 6 Mar 2024 07:29:05 -0700 Subject: [PATCH] eth/gasprice: fix blob base fee ordering, simplify tests --- eth/gasprice/feehistory.go | 59 +++++++++--------- eth/gasprice/feehistory_test.go | 2 +- eth/gasprice/gasprice_test.go | 106 ++++++++++---------------------- 3 files changed, 64 insertions(+), 103 deletions(-) diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index f098cc5d13..ab9badf176 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -51,11 +51,10 @@ const ( // blockFees represents a single block for processing type blockFees struct { // set by the caller - blockNumber uint64 - header *types.Header - parentHeader *types.Header - block *types.Block // only set if reward percentiles are requested - receipts types.Receipts + blockNumber uint64 + header *types.Header + block *types.Block // only set if reward percentiles are requested + receipts types.Receipts // filled by processBlock results processedFees err error @@ -85,34 +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) - bf.results.blobGasUsedRatio = 0.0 - bf.results.blobBaseFee = new(big.Int) + if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil { + bf.results.blobGasUsedRatio = float64(*blobGasUsed) / params.MaxBlobGasPerBlock + } - getBlobBaseFee := func(h *types.Header) *big.Int { - if h != nil && chainconfig.IsCancun(h.Number, h.Time) { - if excessBlobGas := h.ExcessBlobGas; excessBlobGas != nil { - return eip4844.CalcBlobFee(*excessBlobGas) - } - } - return new(big.Int) - } - bf.results.blobBaseFee = getBlobBaseFee(bf.parentHeader) - bf.results.nextBlobBaseFee = getBlobBaseFee(bf.header) - if bf.header != nil && chainconfig.IsCancun(bf.header.Number, bf.header.Time) { - 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 @@ -153,6 +149,15 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { } } +// getBlobBaseFee is a helper function which computes and returns the blob base +// fee if excessBlobGas exists. +func getBlobBaseFee(h *types.Header) *big.Int { + if excessBlobGas := h.ExcessBlobGas; excessBlobGas != nil { + return eip4844.CalcBlobFee(*excessBlobGas) + } + return new(big.Int) +} + // 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. @@ -290,7 +295,6 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL if pendingBlock != nil && blockNumber >= pendingBlock.NumberU64() { fees.block, fees.receipts = pendingBlock, pendingReceipts fees.header = fees.block.Header() - fees.parentHeader, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(pendingBlock.NumberU64()-1)) oracle.processBlock(fees, rewardPercentiles) results <- fees } else { @@ -310,9 +314,6 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks uint64, unresolvedL fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) } if fees.header != nil && fees.err == nil { - if blockNumber > 0 { - fees.parentHeader, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber-1)) - } oracle.processBlock(fees, rewardPercentiles) if fees.err == nil { oracle.historyCache.Add(cacheKey, fees.results) diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go index 42a2290b17..3d426db46f 100644 --- a/eth/gasprice/feehistory_test.go +++ b/eth/gasprice/feehistory_test.go @@ -58,7 +58,7 @@ func TestFeeHistory(t *testing.T) { MaxHeaderHistory: c.maxHeader, MaxBlockHistory: c.maxBlock, } - backend := newTestBackend(t, big.NewInt(16), c.pending, true) + backend := newTestBackend(t, big.NewInt(16), big.NewInt(28), c.pending) oracle := NewOracle(backend, config) first, reward, baseFee, ratio, blobBaseFee, blobRatio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index d1cf0095bd..cbf2ce4c49 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -18,7 +18,6 @@ package gasprice import ( "context" - "crypto/ecdsa" "crypto/sha256" "fmt" "math" @@ -32,7 +31,6 @@ import ( "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" @@ -129,7 +127,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, addCancunBlocks 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) @@ -138,32 +139,29 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool, addCancunB Config: &config, Alloc: types.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, } - signer = types.LatestSigner(gspec.Config) - blobSigners = [6]struct { - key *ecdsa.PrivateKey - addr common.Address - }{} + 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 var engine consensus.Engine = beacon.New(ethash.NewFaker()) - td := int(params.GenesisDifficulty.Uint64()) + td := params.GenesisDifficulty.Uint64() - if addCancunBlocks { - for i := range blobSigners { - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - blobSigners[i].key = key - blobSigners[i].addr = addr - gspec.Alloc[addr] = types.Account{Balance: big.NewInt(math.MaxInt64)} - } + 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 - genDb, 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}) - td += int(b.Difficulty().Uint64()) var txdata types.TxData if londonBlock != nil && b.Number().Cmp(londonBlock) >= 0 { @@ -187,61 +185,15 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool, addCancunB } } b.AddTx(types.MustSignNewTx(key, signer, txdata)) - }) - // Construct testing chain - db := rawdb.NewMemoryDatabase() - merger := consensus.NewMerger(db) - 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) - } - 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()) - if addCancunBlocks { - if err := chain.SetHead(26); err != nil { - panic(err) - } - - head := chain.GetBlockByNumber(26) - ts := head.Time() - config.ShanghaiTime = &ts - config.CancunTime = &ts - ttd := chain.GetTd(head.Hash(), head.NumberU64()) - config.TerminalTotalDifficulty = ttd - signer = types.LatestSigner(gspec.Config) - - merger.ReachTTD() - merger.FinalizePoS() - - var ( - emptyBlob = kzg4844.Blob{} - emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) - emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) - ) - - postBlocks, _ := core.GenerateChain(gspec.Config, head, engine, genDb, 6+1, func(i int, b *core.BlockGen) { + if cancunBlock != nil && b.Number().Cmp(cancunBlock) >= 0 { b.SetPoS() - txdata := &types.DynamicFeeTx{ - ChainID: gspec.Config.ChainID, - Nonce: b.TxNonce(addr), - To: &common.Address{}, - Gas: 30000, - GasFeeCap: big.NewInt(100 * params.GWei), - GasTipCap: big.NewInt(int64(i+1) * params.GWei), - Data: []byte{}, - } - b.AddTx(types.MustSignNewTx(key, signer, txdata)) - // 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(blobSigners[j].addr), + Nonce: b.TxNonce(addr), To: common.Address{}, Gas: 30000, GasFeeCap: uint256.NewInt(100 * params.GWei), @@ -252,14 +204,22 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool, addCancunB Value: uint256.NewInt(100), Sidecar: nil, } - b.AddTx(types.MustSignNewTx(blobSigners[j].key, signer, blobTx)) + b.AddTx(types.MustSignNewTx(key, signer, blobTx)) } - }) - - if i, err := chain.InsertChain(postBlocks); err != nil { - panic(fmt.Errorf("error inserting block %d: %w", i, err)) } + td += b.Difficulty().Uint64() + }) + // Construct testing chain + 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) } + 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} } @@ -289,7 +249,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, false) + backend := newTestBackend(t, c.fork, nil, false) oracle := NewOracle(backend, config) // The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G