mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
consensus/misc/eip4844: use blob parameters of current header (#32424)
This changes the implementation to resolve the blob parameters according to the current header timestamp. This matters for EIP-7918, where we would previously resolve the UpdateFraction according to the parent header fork, leading to a confusing situation at the fork transition block. --------- Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
This commit is contained in:
parent
88922d2bf5
commit
1d29e3ec0e
2 changed files with 161 additions and 94 deletions
|
|
@ -19,6 +19,7 @@ package eip4844
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -29,6 +30,66 @@ 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 {
|
||||
if cfg.BlobScheduleConfig == nil {
|
||||
return nil
|
||||
}
|
||||
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 nil
|
||||
}
|
||||
|
||||
return &BlobConfig{
|
||||
Target: bc.Target,
|
||||
Max: bc.Max,
|
||||
UpdateFraction: bc.UpdateFraction,
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
|
||||
// if the current block contains no transactions, the excessBlobGas is updated
|
||||
// accordingly.
|
||||
|
|
@ -36,21 +97,27 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
|
|||
if header.Number.Uint64() != parent.Number.Uint64()+1 {
|
||||
panic("bad header pair")
|
||||
}
|
||||
// Verify the header is not malformed
|
||||
|
||||
bcfg := latestBlobConfig(config, header.Time)
|
||||
if bcfg == 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.
|
||||
maxBlobGas := MaxBlobGasPerBlock(config, header.Time)
|
||||
if *header.BlobGasUsed > maxBlobGas {
|
||||
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, maxBlobGas)
|
||||
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 {
|
||||
|
|
@ -62,38 +129,41 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
|
|||
// 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 {
|
||||
var (
|
||||
parentExcessBlobGas uint64
|
||||
parentBlobGasUsed uint64
|
||||
)
|
||||
isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp)
|
||||
bcfg := latestBlobConfig(config, headTimestamp)
|
||||
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
|
||||
target = targetBlobsPerBlock(config, headTimestamp)
|
||||
targetGas = uint64(target) * params.BlobTxBlobGasPerBlob
|
||||
targetGas = uint64(bcfg.Target) * params.BlobTxBlobGasPerBlob
|
||||
)
|
||||
if excessBlobGas < targetGas {
|
||||
return 0
|
||||
}
|
||||
if !config.IsOsaka(config.LondonBlock, headTimestamp) {
|
||||
// Pre-Osaka, we use the formula defined by EIP-4844.
|
||||
return excessBlobGas - targetGas
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// EIP-7918 (post-Osaka) introduces a different formula for computing excess.
|
||||
var (
|
||||
baseCost = big.NewInt(params.BlobBaseCost)
|
||||
reservePrice = baseCost.Mul(baseCost, parent.BaseFee)
|
||||
blobPrice = calcBlobPrice(config, parent)
|
||||
)
|
||||
if reservePrice.Cmp(blobPrice) > 0 {
|
||||
max := MaxBlobsPerBlock(config, headTimestamp)
|
||||
scaledExcess := parentBlobGasUsed * uint64(max-target) / uint64(max)
|
||||
return parentExcessBlobGas + scaledExcess
|
||||
}
|
||||
// Original EIP-4844 formula.
|
||||
return excessBlobGas - targetGas
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +173,7 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
|
|||
if blobConfig == nil {
|
||||
panic("calculating blob fee on unsupported fork")
|
||||
}
|
||||
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(blobConfig.UpdateFraction))
|
||||
return blobConfig.blobBaseFee(*header.ExcessBlobGas)
|
||||
}
|
||||
|
||||
// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp.
|
||||
|
|
@ -115,36 +185,6 @@ func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
|||
return blobConfig.Max
|
||||
}
|
||||
|
||||
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *params.BlobConfig {
|
||||
if cfg.BlobScheduleConfig == nil {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
london = cfg.LondonBlock
|
||||
s = cfg.BlobScheduleConfig
|
||||
)
|
||||
switch {
|
||||
case cfg.IsBPO5(london, time) && s.BPO5 != nil:
|
||||
return s.BPO5
|
||||
case cfg.IsBPO4(london, time) && s.BPO4 != nil:
|
||||
return s.BPO4
|
||||
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
|
||||
return s.BPO3
|
||||
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
|
||||
return s.BPO2
|
||||
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
||||
return s.BPO1
|
||||
case cfg.IsOsaka(london, time) && s.Osaka != nil:
|
||||
return s.Osaka
|
||||
case cfg.IsPrague(london, time) && s.Prague != nil:
|
||||
return s.Prague
|
||||
case cfg.IsCancun(london, time) && s.Cancun != nil:
|
||||
return s.Cancun
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -153,39 +193,11 @@ func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
|
|||
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
|
||||
// configuration, regardless of the currently active fork.
|
||||
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
|
||||
s := cfg.BlobScheduleConfig
|
||||
if s == nil {
|
||||
bcfg := latestBlobConfig(cfg, math.MaxUint64)
|
||||
if bcfg == nil {
|
||||
return 0
|
||||
}
|
||||
switch {
|
||||
case s.BPO5 != nil:
|
||||
return s.BPO5.Max
|
||||
case s.BPO4 != nil:
|
||||
return s.BPO4.Max
|
||||
case s.BPO3 != nil:
|
||||
return s.BPO3.Max
|
||||
case s.BPO2 != nil:
|
||||
return s.BPO2.Max
|
||||
case s.BPO1 != nil:
|
||||
return s.BPO1.Max
|
||||
case s.Osaka != nil:
|
||||
return s.Osaka.Max
|
||||
case s.Prague != nil:
|
||||
return s.Prague.Max
|
||||
case s.Cancun != nil:
|
||||
return s.Cancun.Max
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// targetBlobsPerBlock returns the target number of blobs in a block at the given timestamp.
|
||||
func targetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
|
||||
blobConfig := latestBlobConfig(cfg, time)
|
||||
if blobConfig == nil {
|
||||
return 0
|
||||
}
|
||||
return blobConfig.Target
|
||||
return bcfg.Max
|
||||
}
|
||||
|
||||
// fakeExponential approximates factor * e ** (numerator / denominator) using
|
||||
|
|
@ -204,9 +216,3 @@ func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
|
|||
}
|
||||
return output.Div(output, denominator)
|
||||
}
|
||||
|
||||
// calcBlobPrice calculates the blob price for a block.
|
||||
func calcBlobPrice(config *params.ChainConfig, header *types.Header) *big.Int {
|
||||
blobBaseFee := CalcBlobFee(config, header)
|
||||
return new(big.Int).Mul(blobBaseFee, big.NewInt(params.BlobTxBlobGasPerBlob))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,9 +28,10 @@ import (
|
|||
func TestCalcExcessBlobGas(t *testing.T) {
|
||||
var (
|
||||
config = params.MainnetChainConfig
|
||||
targetBlobs = targetBlobsPerBlock(config, *config.CancunTime)
|
||||
targetBlobs = config.BlobScheduleConfig.Cancun.Target
|
||||
targetBlobGas = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
excess uint64
|
||||
blobs int
|
||||
|
|
@ -90,6 +91,65 @@ func TestCalcBlobFee(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCalcBlobFeePostOsaka(t *testing.T) {
|
||||
zero := uint64(0)
|
||||
bpo1 := uint64(1754836608)
|
||||
bpo2 := uint64(1754934912)
|
||||
bpo3 := uint64(1755033216)
|
||||
|
||||
tests := []struct {
|
||||
excessBlobGas uint64
|
||||
blobGasUsed uint64
|
||||
blobfee uint64
|
||||
basefee uint64
|
||||
parenttime uint64
|
||||
headertime uint64
|
||||
}{
|
||||
{5149252, 1310720, 5617366, 30, 1754904516, 1754904528},
|
||||
{19251039, 2490368, 20107103, 50, 1755033204, 1755033216},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
config := ¶ms.ChainConfig{
|
||||
LondonBlock: big.NewInt(0),
|
||||
CancunTime: &zero,
|
||||
PragueTime: &zero,
|
||||
OsakaTime: &zero,
|
||||
BPO1Time: &bpo1,
|
||||
BPO2Time: &bpo2,
|
||||
BPO3Time: &bpo3,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
Osaka: params.DefaultOsakaBlobConfig,
|
||||
BPO1: ¶ms.BlobConfig{
|
||||
Target: 9,
|
||||
Max: 14,
|
||||
UpdateFraction: 8832827,
|
||||
},
|
||||
BPO2: ¶ms.BlobConfig{
|
||||
Target: 14,
|
||||
Max: 21,
|
||||
UpdateFraction: 13739630,
|
||||
},
|
||||
BPO3: ¶ms.BlobConfig{
|
||||
Target: 21,
|
||||
Max: 32,
|
||||
UpdateFraction: 20609697,
|
||||
},
|
||||
}}
|
||||
parent := &types.Header{
|
||||
ExcessBlobGas: &tt.excessBlobGas,
|
||||
BlobGasUsed: &tt.blobGasUsed,
|
||||
BaseFee: big.NewInt(int64(tt.basefee)),
|
||||
Time: tt.parenttime,
|
||||
}
|
||||
have := CalcExcessBlobGas(config, parent, tt.headertime)
|
||||
if have != tt.blobfee {
|
||||
t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeExponential(t *testing.T) {
|
||||
tests := []struct {
|
||||
factor int64
|
||||
|
|
@ -131,9 +191,10 @@ func TestFakeExponential(t *testing.T) {
|
|||
func TestCalcExcessBlobGasEIP7918(t *testing.T) {
|
||||
var (
|
||||
cfg = params.MergedTestChainConfig
|
||||
targetBlobs = targetBlobsPerBlock(cfg, *cfg.CancunTime)
|
||||
targetBlobs = cfg.BlobScheduleConfig.Osaka.Target
|
||||
blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
|
||||
)
|
||||
|
||||
makeHeader := func(parentExcess, parentBaseFee uint64, blobsUsed int) *types.Header {
|
||||
blobGasUsed := uint64(blobsUsed) * params.BlobTxBlobGasPerBlob
|
||||
return &types.Header{
|
||||
|
|
|
|||
Loading…
Reference in a new issue