Skip to content

Commit 1d29e3e

Browse files
consensus/misc/eip4844: use blob parameters of current header (ethereum#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 <[email protected]>
1 parent 88922d2 commit 1d29e3e

File tree

2 files changed

+161
-94
lines changed

2 files changed

+161
-94
lines changed

consensus/misc/eip4844/eip4844.go

Lines changed: 98 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package eip4844
1919
import (
2020
"errors"
2121
"fmt"
22+
"math"
2223
"math/big"
2324

2425
"github.com/ethereum/go-ethereum/core/types"
@@ -29,28 +30,94 @@ var (
2930
minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
3031
)
3132

33+
// BlobConfig contains the parameters for blob-related formulas.
34+
// These can be adjusted in a fork.
35+
type BlobConfig struct {
36+
Target int
37+
Max int
38+
UpdateFraction uint64
39+
}
40+
41+
func (bc *BlobConfig) maxBlobGas() uint64 {
42+
return uint64(bc.Max) * params.BlobTxBlobGasPerBlob
43+
}
44+
45+
// blobBaseFee computes the blob fee.
46+
func (bc *BlobConfig) blobBaseFee(excessBlobGas uint64) *big.Int {
47+
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), new(big.Int).SetUint64(bc.UpdateFraction))
48+
}
49+
50+
// blobPrice returns the price of one blob in Wei.
51+
func (bc *BlobConfig) blobPrice(excessBlobGas uint64) *big.Int {
52+
f := bc.blobBaseFee(excessBlobGas)
53+
return new(big.Int).Mul(f, big.NewInt(params.BlobTxBlobGasPerBlob))
54+
}
55+
56+
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *BlobConfig {
57+
if cfg.BlobScheduleConfig == nil {
58+
return nil
59+
}
60+
var (
61+
london = cfg.LondonBlock
62+
s = cfg.BlobScheduleConfig
63+
bc *params.BlobConfig
64+
)
65+
switch {
66+
case cfg.IsBPO5(london, time) && s.BPO5 != nil:
67+
bc = s.BPO5
68+
case cfg.IsBPO4(london, time) && s.BPO4 != nil:
69+
bc = s.BPO4
70+
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
71+
bc = s.BPO3
72+
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
73+
bc = s.BPO2
74+
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
75+
bc = s.BPO1
76+
case cfg.IsOsaka(london, time) && s.Osaka != nil:
77+
bc = s.Osaka
78+
case cfg.IsPrague(london, time) && s.Prague != nil:
79+
bc = s.Prague
80+
case cfg.IsCancun(london, time) && s.Cancun != nil:
81+
bc = s.Cancun
82+
default:
83+
return nil
84+
}
85+
86+
return &BlobConfig{
87+
Target: bc.Target,
88+
Max: bc.Max,
89+
UpdateFraction: bc.UpdateFraction,
90+
}
91+
}
92+
3293
// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that
3394
// if the current block contains no transactions, the excessBlobGas is updated
3495
// accordingly.
3596
func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Header) error {
3697
if header.Number.Uint64() != parent.Number.Uint64()+1 {
3798
panic("bad header pair")
3899
}
39-
// Verify the header is not malformed
100+
101+
bcfg := latestBlobConfig(config, header.Time)
102+
if bcfg == nil {
103+
panic("called before EIP-4844 is active")
104+
}
105+
40106
if header.ExcessBlobGas == nil {
41107
return errors.New("header is missing excessBlobGas")
42108
}
43109
if header.BlobGasUsed == nil {
44110
return errors.New("header is missing blobGasUsed")
45111
}
112+
46113
// Verify that the blob gas used remains within reasonable limits.
47-
maxBlobGas := MaxBlobGasPerBlock(config, header.Time)
48-
if *header.BlobGasUsed > maxBlobGas {
49-
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, maxBlobGas)
114+
if *header.BlobGasUsed > bcfg.maxBlobGas() {
115+
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas())
50116
}
51117
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
52118
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
53119
}
120+
54121
// Verify the excessBlobGas is correct based on the parent header
55122
expectedExcessBlobGas := CalcExcessBlobGas(config, parent, header.Time)
56123
if *header.ExcessBlobGas != expectedExcessBlobGas {
@@ -62,38 +129,41 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
62129
// CalcExcessBlobGas calculates the excess blob gas after applying the set of
63130
// blobs on top of the excess blob gas.
64131
func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTimestamp uint64) uint64 {
65-
var (
66-
parentExcessBlobGas uint64
67-
parentBlobGasUsed uint64
68-
)
132+
isOsaka := config.IsOsaka(config.LondonBlock, headTimestamp)
133+
bcfg := latestBlobConfig(config, headTimestamp)
134+
return calcExcessBlobGas(isOsaka, bcfg, parent)
135+
}
136+
137+
func calcExcessBlobGas(isOsaka bool, bcfg *BlobConfig, parent *types.Header) uint64 {
138+
var parentExcessBlobGas, parentBlobGasUsed uint64
69139
if parent.ExcessBlobGas != nil {
70140
parentExcessBlobGas = *parent.ExcessBlobGas
71141
parentBlobGasUsed = *parent.BlobGasUsed
72142
}
143+
73144
var (
74145
excessBlobGas = parentExcessBlobGas + parentBlobGasUsed
75-
target = targetBlobsPerBlock(config, headTimestamp)
76-
targetGas = uint64(target) * params.BlobTxBlobGasPerBlob
146+
targetGas = uint64(bcfg.Target) * params.BlobTxBlobGasPerBlob
77147
)
78148
if excessBlobGas < targetGas {
79149
return 0
80150
}
81-
if !config.IsOsaka(config.LondonBlock, headTimestamp) {
82-
// Pre-Osaka, we use the formula defined by EIP-4844.
83-
return excessBlobGas - targetGas
84-
}
85151

86-
// EIP-7918 (post-Osaka) introduces a different formula for computing excess.
87-
var (
88-
baseCost = big.NewInt(params.BlobBaseCost)
89-
reservePrice = baseCost.Mul(baseCost, parent.BaseFee)
90-
blobPrice = calcBlobPrice(config, parent)
91-
)
92-
if reservePrice.Cmp(blobPrice) > 0 {
93-
max := MaxBlobsPerBlock(config, headTimestamp)
94-
scaledExcess := parentBlobGasUsed * uint64(max-target) / uint64(max)
95-
return parentExcessBlobGas + scaledExcess
152+
// EIP-7918 (post-Osaka) introduces a different formula for computing excess,
153+
// in cases where the price is lower than a 'reserve price'.
154+
if isOsaka {
155+
var (
156+
baseCost = big.NewInt(params.BlobBaseCost)
157+
reservePrice = baseCost.Mul(baseCost, parent.BaseFee)
158+
blobPrice = bcfg.blobPrice(parentExcessBlobGas)
159+
)
160+
if reservePrice.Cmp(blobPrice) > 0 {
161+
scaledExcess := parentBlobGasUsed * uint64(bcfg.Max-bcfg.Target) / uint64(bcfg.Max)
162+
return parentExcessBlobGas + scaledExcess
163+
}
96164
}
165+
166+
// Original EIP-4844 formula.
97167
return excessBlobGas - targetGas
98168
}
99169

@@ -103,7 +173,7 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
103173
if blobConfig == nil {
104174
panic("calculating blob fee on unsupported fork")
105175
}
106-
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(blobConfig.UpdateFraction))
176+
return blobConfig.blobBaseFee(*header.ExcessBlobGas)
107177
}
108178

109179
// 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 {
115185
return blobConfig.Max
116186
}
117187

118-
func latestBlobConfig(cfg *params.ChainConfig, time uint64) *params.BlobConfig {
119-
if cfg.BlobScheduleConfig == nil {
120-
return nil
121-
}
122-
var (
123-
london = cfg.LondonBlock
124-
s = cfg.BlobScheduleConfig
125-
)
126-
switch {
127-
case cfg.IsBPO5(london, time) && s.BPO5 != nil:
128-
return s.BPO5
129-
case cfg.IsBPO4(london, time) && s.BPO4 != nil:
130-
return s.BPO4
131-
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
132-
return s.BPO3
133-
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
134-
return s.BPO2
135-
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
136-
return s.BPO1
137-
case cfg.IsOsaka(london, time) && s.Osaka != nil:
138-
return s.Osaka
139-
case cfg.IsPrague(london, time) && s.Prague != nil:
140-
return s.Prague
141-
case cfg.IsCancun(london, time) && s.Cancun != nil:
142-
return s.Cancun
143-
default:
144-
return nil
145-
}
146-
}
147-
148188
// MaxBlobGasPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp.
149189
func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
150190
return uint64(MaxBlobsPerBlock(cfg, time)) * params.BlobTxBlobGasPerBlob
@@ -153,39 +193,11 @@ func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 {
153193
// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the
154194
// configuration, regardless of the currently active fork.
155195
func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
156-
s := cfg.BlobScheduleConfig
157-
if s == nil {
158-
return 0
159-
}
160-
switch {
161-
case s.BPO5 != nil:
162-
return s.BPO5.Max
163-
case s.BPO4 != nil:
164-
return s.BPO4.Max
165-
case s.BPO3 != nil:
166-
return s.BPO3.Max
167-
case s.BPO2 != nil:
168-
return s.BPO2.Max
169-
case s.BPO1 != nil:
170-
return s.BPO1.Max
171-
case s.Osaka != nil:
172-
return s.Osaka.Max
173-
case s.Prague != nil:
174-
return s.Prague.Max
175-
case s.Cancun != nil:
176-
return s.Cancun.Max
177-
default:
196+
bcfg := latestBlobConfig(cfg, math.MaxUint64)
197+
if bcfg == nil {
178198
return 0
179199
}
180-
}
181-
182-
// targetBlobsPerBlock returns the target number of blobs in a block at the given timestamp.
183-
func targetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int {
184-
blobConfig := latestBlobConfig(cfg, time)
185-
if blobConfig == nil {
186-
return 0
187-
}
188-
return blobConfig.Target
200+
return bcfg.Max
189201
}
190202

191203
// fakeExponential approximates factor * e ** (numerator / denominator) using
@@ -204,9 +216,3 @@ func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
204216
}
205217
return output.Div(output, denominator)
206218
}
207-
208-
// calcBlobPrice calculates the blob price for a block.
209-
func calcBlobPrice(config *params.ChainConfig, header *types.Header) *big.Int {
210-
blobBaseFee := CalcBlobFee(config, header)
211-
return new(big.Int).Mul(blobBaseFee, big.NewInt(params.BlobTxBlobGasPerBlob))
212-
}

consensus/misc/eip4844/eip4844_test.go

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ import (
2828
func TestCalcExcessBlobGas(t *testing.T) {
2929
var (
3030
config = params.MainnetChainConfig
31-
targetBlobs = targetBlobsPerBlock(config, *config.CancunTime)
31+
targetBlobs = config.BlobScheduleConfig.Cancun.Target
3232
targetBlobGas = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
3333
)
34+
3435
var tests = []struct {
3536
excess uint64
3637
blobs int
@@ -90,6 +91,65 @@ func TestCalcBlobFee(t *testing.T) {
9091
}
9192
}
9293

94+
func TestCalcBlobFeePostOsaka(t *testing.T) {
95+
zero := uint64(0)
96+
bpo1 := uint64(1754836608)
97+
bpo2 := uint64(1754934912)
98+
bpo3 := uint64(1755033216)
99+
100+
tests := []struct {
101+
excessBlobGas uint64
102+
blobGasUsed uint64
103+
blobfee uint64
104+
basefee uint64
105+
parenttime uint64
106+
headertime uint64
107+
}{
108+
{5149252, 1310720, 5617366, 30, 1754904516, 1754904528},
109+
{19251039, 2490368, 20107103, 50, 1755033204, 1755033216},
110+
}
111+
for i, tt := range tests {
112+
config := &params.ChainConfig{
113+
LondonBlock: big.NewInt(0),
114+
CancunTime: &zero,
115+
PragueTime: &zero,
116+
OsakaTime: &zero,
117+
BPO1Time: &bpo1,
118+
BPO2Time: &bpo2,
119+
BPO3Time: &bpo3,
120+
BlobScheduleConfig: &params.BlobScheduleConfig{
121+
Cancun: params.DefaultCancunBlobConfig,
122+
Prague: params.DefaultPragueBlobConfig,
123+
Osaka: params.DefaultOsakaBlobConfig,
124+
BPO1: &params.BlobConfig{
125+
Target: 9,
126+
Max: 14,
127+
UpdateFraction: 8832827,
128+
},
129+
BPO2: &params.BlobConfig{
130+
Target: 14,
131+
Max: 21,
132+
UpdateFraction: 13739630,
133+
},
134+
BPO3: &params.BlobConfig{
135+
Target: 21,
136+
Max: 32,
137+
UpdateFraction: 20609697,
138+
},
139+
}}
140+
parent := &types.Header{
141+
ExcessBlobGas: &tt.excessBlobGas,
142+
BlobGasUsed: &tt.blobGasUsed,
143+
BaseFee: big.NewInt(int64(tt.basefee)),
144+
Time: tt.parenttime,
145+
}
146+
have := CalcExcessBlobGas(config, parent, tt.headertime)
147+
if have != tt.blobfee {
148+
t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee)
149+
}
150+
}
151+
}
152+
93153
func TestFakeExponential(t *testing.T) {
94154
tests := []struct {
95155
factor int64
@@ -131,9 +191,10 @@ func TestFakeExponential(t *testing.T) {
131191
func TestCalcExcessBlobGasEIP7918(t *testing.T) {
132192
var (
133193
cfg = params.MergedTestChainConfig
134-
targetBlobs = targetBlobsPerBlock(cfg, *cfg.CancunTime)
194+
targetBlobs = cfg.BlobScheduleConfig.Osaka.Target
135195
blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
136196
)
197+
137198
makeHeader := func(parentExcess, parentBaseFee uint64, blobsUsed int) *types.Header {
138199
blobGasUsed := uint64(blobsUsed) * params.BlobTxBlobGasPerBlob
139200
return &types.Header{

0 commit comments

Comments
 (0)