mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
I recently went on a longer flight and started profiling the geth block
production pipeline.
This PR contains a bunch of individual fixes split into separate
commits.
I can drop some if necessary.
Benchmarking is not super easy, the benchmark I wrote is a bit
non-deterministic.
I will try to write a better benchmark later
```
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/miner
cpu: Intel(R) Core(TM) Ultra 7 155U
│ /tmp/old.txt │ /tmp/new.txt │
│ sec/op │ sec/op vs base │
BuildPayload-14 141.5µ ± 3% 146.0µ ± 6% ~ (p=0.346 n=200)
│ /tmp/old.txt │ /tmp/new.txt │
│ B/op │ B/op vs base │
BuildPayload-14 188.2Ki ± 4% 177.4Ki ± 4% -5.71% (p=0.018 n=200)
│ /tmp/old.txt │ /tmp/new.txt │
│ allocs/op │ allocs/op vs base │
BuildPayload-14 2.703k ± 4% 2.453k ± 5% -9.25% (p=0.000 n=200)
```
230 lines
7.4 KiB
Go
230 lines
7.4 KiB
Go
// Copyright 2023 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package eip4844
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
var (
|
|
minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
|
|
)
|
|
|
|
// BlobConfig contains the parameters for blob-related formulas.
|
|
// These can be adjusted in a fork.
|
|
type BlobConfig struct {
|
|
Target int
|
|
Max int
|
|
UpdateFraction uint64
|
|
}
|
|
|
|
func (bc *BlobConfig) maxBlobGas() uint64 {
|
|
return uint64(bc.Max) * params.BlobTxBlobGasPerBlob
|
|
}
|
|
|
|
// blobBaseFee computes the blob fee.
|
|
func (bc *BlobConfig) blobBaseFee(excessBlobGas uint64) *big.Int {
|
|
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), new(big.Int).SetUint64(bc.UpdateFraction))
|
|
}
|
|
|
|
// blobPrice returns the price of one blob in Wei.
|
|
func (bc *BlobConfig) blobPrice(excessBlobGas uint64) *big.Int {
|
|
f := bc.blobBaseFee(excessBlobGas)
|
|
return new(big.Int).Mul(f, big.NewInt(params.BlobTxBlobGasPerBlob))
|
|
}
|
|
|
|
func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error) {
|
|
if cfg.BlobScheduleConfig == nil {
|
|
return BlobConfig{}, errors.New("no blob config")
|
|
}
|
|
var (
|
|
london = cfg.LondonBlock
|
|
s = cfg.BlobScheduleConfig
|
|
bc *params.BlobConfig
|
|
)
|
|
switch {
|
|
case cfg.IsBPO5(london, time) && s.BPO5 != nil:
|
|
bc = s.BPO5
|
|
case cfg.IsBPO4(london, time) && s.BPO4 != nil:
|
|
bc = s.BPO4
|
|
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
|
|
bc = s.BPO3
|
|
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
|
|
bc = s.BPO2
|
|
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
|
bc = s.BPO1
|
|
case cfg.IsOsaka(london, time) && s.Osaka != nil:
|
|
bc = s.Osaka
|
|
case cfg.IsPrague(london, time) && s.Prague != nil:
|
|
bc = s.Prague
|
|
case cfg.IsCancun(london, time) && s.Cancun != nil:
|
|
bc = s.Cancun
|
|
default:
|
|
return BlobConfig{}, errors.New("no blob config")
|
|
}
|
|
|
|
return BlobConfig{
|
|
Target: bc.Target,
|
|
Max: bc.Max,
|
|
UpdateFraction: bc.UpdateFraction,
|
|
}, nil
|
|
}
|
|
|
|
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
|
|
// if the current block contains no transactions, the excessBlobGas is updated
|
|
// accordingly.
|
|
func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Header) error {
|
|
if header.Number.Uint64() != parent.Number.Uint64()+1 {
|
|
panic("bad header pair")
|
|
}
|
|
|
|
bcfg, err := latestBlobConfig(config, header.Time)
|
|
if err != nil {
|
|
panic("called before EIP-4844 is active")
|
|
}
|
|
|
|
if header.ExcessBlobGas == nil {
|
|
return errors.New("header is missing excessBlobGas")
|
|
}
|
|
if header.BlobGasUsed == nil {
|
|
return errors.New("header is missing blobGasUsed")
|
|
}
|
|
|
|
// Verify that the blob gas used remains within reasonable limits.
|
|
if *header.BlobGasUsed > bcfg.maxBlobGas() {
|
|
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas())
|
|
}
|
|
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
|
|
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", *header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
|
|
}
|
|
|
|
// Verify the excessBlobGas is correct based on the parent header
|
|
expectedExcessBlobGas := CalcExcessBlobGas(config, parent, header.Time)
|
|
if *header.ExcessBlobGas != expectedExcessBlobGas {
|
|
return fmt.Errorf("invalid excessBlobGas: have %d, want %d", *header.ExcessBlobGas, expectedExcessBlobGas)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CalcExcessBlobGas calculates the excess blob gas after applying the set of
|
|
// blobs on top of the excess blob gas.
|
|
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
|
|
isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp)
|
|
bcfg, err := latestBlobConfig(config, headTimestamp)
|
|
if err != nil {
|
|
panic("calculating excess blob gas on nil blob config")
|
|
}
|
|
return calcExcessBlobGas(isOsaka, bcfg, parent)
|
|
}
|
|
|
|
func calcExcessBlobGas(isOsaka bool, bcfg BlobConfig, parent *types.Header) uint64 {
|
|
var parentExcessBlobGas, parentBlobGasUsed uint64
|
|
if parent.ExcessBlobGas != nil {
|
|
parentExcessBlobGas = *parent.ExcessBlobGas
|
|
parentBlobGasUsed = *parent.BlobGasUsed
|
|
}
|
|
|
|
var (
|
|
excessBlobGas = parentExcessBlobGas + parentBlobGasUsed
|
|
targetGas = uint64(bcfg.Target) * params.BlobTxBlobGasPerBlob
|
|
)
|
|
if excessBlobGas < targetGas {
|
|
return 0
|
|
}
|
|
|
|
// EIP-7918 (post-Osaka) introduces a different formula for computing excess,
|
|
// in cases where the price is lower than a 'reserve price'.
|
|
if isOsaka {
|
|
var (
|
|
baseCost = big.NewInt(params.BlobBaseCost)
|
|
reservePrice = baseCost.Mul(baseCost, parent.BaseFee)
|
|
blobPrice = bcfg.blobPrice(parentExcessBlobGas)
|
|
)
|
|
if reservePrice.Cmp(blobPrice) > 0 {
|
|
scaledExcess := parentBlobGasUsed * uint64(bcfg.Max-bcfg.Target) / uint64(bcfg.Max)
|
|
return parentExcessBlobGas + scaledExcess
|
|
}
|
|
}
|
|
|
|
// Original EIP-4844 formula.
|
|
return excessBlobGas - targetGas
|
|
}
|
|
|
|
// CalcBlobFee calculates the blobfee from the header's excess blob gas field.
|
|
func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
|
|
blobConfig, err := latestBlobConfig(config, header.Time)
|
|
if err != nil {
|
|
panic("calculating blob fee on unsupported fork")
|
|
}
|
|
return blobConfig.blobBaseFee(*header.ExcessBlobGas)
|
|
}
|
|
|
|
// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp.
|
|
func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
|
blobConfig, err := latestBlobConfig(cfg, time)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return blobConfig.Max
|
|
}
|
|
|
|
// MaxBlobGasPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp.
|
|
func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
|
|
return uint64(MaxBlobsPerBlock(cfg, time)) * params.BlobTxBlobGasPerBlob
|
|
}
|
|
|
|
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
|
|
// configuration, regardless of the currently active fork.
|
|
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
|
|
bcfg, err := latestBlobConfig(cfg, math.MaxUint64)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return bcfg.Max
|
|
}
|
|
|
|
// TargetBlobsPerBlock returns the target blobs per block for a block at the given timestamp.
|
|
func TargetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
|
blobConfig, err := latestBlobConfig(cfg, time)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return blobConfig.Target
|
|
}
|
|
|
|
// fakeExponential approximates factor * e ** (numerator / denominator) using
|
|
// Taylor expansion.
|
|
func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
|
|
var (
|
|
output = new(big.Int)
|
|
accum = new(big.Int).Mul(factor, denominator)
|
|
)
|
|
for i := 1; accum.Sign() > 0; i++ {
|
|
output.Add(output, accum)
|
|
|
|
accum.Mul(accum, numerator)
|
|
accum.Div(accum, denominator)
|
|
accum.Div(accum, big.NewInt(int64(i)))
|
|
}
|
|
return output.Div(output, denominator)
|
|
}
|