Skip to content

Commit 6658a36

Browse files
authored
all: Store DA footprint in blob gas used header field (#694)
1 parent e061c29 commit 6658a36

File tree

11 files changed

+158
-93
lines changed

11 files changed

+158
-93
lines changed

consensus/beacon/consensus.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,16 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
410410
}
411411
}
412412

413+
// Store DA footprint in BlobGasUsed header field if it hasn't already been set yet.
414+
// Builder code may already calculate it during block building to avoid recalculating it here.
415+
if chain.Config().IsDAFootprintBlockLimit(header.Time) && (header.BlobGasUsed == nil || *header.BlobGasUsed == 0) {
416+
daFootprint, err := types.CalcDAFootprint(body.Transactions)
417+
if err != nil {
418+
return nil, fmt.Errorf("error calculating DA footprint: %w", err)
419+
}
420+
header.BlobGasUsed = &daFootprint
421+
}
422+
413423
// Assemble the final block.
414424
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil), chain.Config())
415425

consensus/misc/eip1559/eip1559.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
3636
if !config.IsLondon(parent.Number) {
3737
parentGasLimit = parent.GasLimit * config.ElasticityMultiplier()
3838
}
39-
if config.Optimism == nil { // gasLimit can adjust instantly in optimism
39+
if !config.IsOptimism() { // OP Stack gasLimit can adjust instantly
4040
if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil {
4141
return err
4242
}
@@ -75,7 +75,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64)
7575
}
7676

7777
// OPStack addition: calculate the base fee using the upstream code.
78-
baseFee := calcBaseFeeInner(parent, elasticity, denominator)
78+
baseFee := calcBaseFeeInner(config, parent, elasticity, denominator)
7979

8080
// OPStack addition: enforce minimum base fee.
8181
// If the minimum base fee is 0, this has no effect.
@@ -89,10 +89,20 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64)
8989
return baseFee
9090
}
9191

92-
func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint64) *big.Int {
92+
func calcBaseFeeInner(config *params.ChainConfig, parent *types.Header, elasticity uint64, denominator uint64) *big.Int {
9393
parentGasTarget := parent.GasLimit / elasticity
94-
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
95-
if parent.GasUsed == parentGasTarget {
94+
parentGasMetered := parent.GasUsed
95+
if config.IsDAFootprintBlockLimit(parent.Time) {
96+
if parent.BlobGasUsed == nil {
97+
panic("Jovian parent block has nil BlobGasUsed")
98+
} else if *parent.BlobGasUsed > parent.GasUsed {
99+
// Jovian updates the base fee based on the maximum of total transactions gas used and total DA footprint (which is
100+
// stored in the BlobGasUsed field of the header).
101+
parentGasMetered = *parent.BlobGasUsed
102+
}
103+
}
104+
// If the parent gasMetered is the same as the target, the baseFee remains unchanged.
105+
if parentGasMetered == parentGasTarget {
96106
return new(big.Int).Set(parent.BaseFee)
97107
}
98108

@@ -101,10 +111,10 @@ func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint6
101111
denom = new(big.Int)
102112
)
103113

104-
if parent.GasUsed > parentGasTarget {
114+
if parentGasMetered > parentGasTarget {
105115
// If the parent block used more gas than its target, the baseFee should increase.
106116
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
107-
num.SetUint64(parent.GasUsed - parentGasTarget)
117+
num.SetUint64(parentGasMetered - parentGasTarget)
108118
num.Mul(num, parent.BaseFee)
109119
num.Div(num, denom.SetUint64(parentGasTarget))
110120
num.Div(num, denom.SetUint64(denominator))
@@ -115,7 +125,7 @@ func calcBaseFeeInner(parent *types.Header, elasticity uint64, denominator uint6
115125
} else {
116126
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
117127
// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
118-
num.SetUint64(parentGasTarget - parent.GasUsed)
128+
num.SetUint64(parentGasTarget - parentGasMetered)
119129
num.Mul(num, parent.BaseFee)
120130
num.Div(num, denom.SetUint64(parentGasTarget))
121131
num.Div(num, denom.SetUint64(denominator))

consensus/misc/eip1559/eip1559_test.go

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,19 @@ func config() *params.ChainConfig {
5757
return config
5858
}
5959

60-
var TestCanyonTime = uint64(10)
61-
var TestHoloceneTime = uint64(12)
62-
var TestJovianTime = uint64(14)
60+
var (
61+
testCanyonTime = uint64(10)
62+
testHoloceneTime = uint64(12)
63+
testJovianTime = uint64(14)
64+
)
6365

6466
func opConfig() *params.ChainConfig {
6567
config := copyConfig(params.TestChainConfig)
6668
config.LondonBlock = big.NewInt(5)
6769
eip1559DenominatorCanyon := uint64(250)
68-
config.CanyonTime = &TestCanyonTime
69-
config.HoloceneTime = &TestHoloceneTime
70-
config.JovianTime = &TestJovianTime
70+
config.CanyonTime = &testCanyonTime
71+
config.HoloceneTime = &testHoloceneTime
72+
config.JovianTime = &testJovianTime
7173
config.Optimism = &params.OptimismConfig{
7274
EIP1559Elasticity: 6,
7375
EIP1559Denominator: 50,
@@ -227,59 +229,74 @@ func TestCalcBaseFeeOptimismHolocene(t *testing.T) {
227229
// TestCalcBaseFeeJovian tests that the minimum base fee is enforced
228230
// when the computed base fee is less than the minimum base fee,
229231
// if the feature is active and not enforced otherwise.
232+
// It also tests that the base fee udpate will take the DA footprint as stored
233+
// in the blob gas used field into account if it is larger than the gas used
234+
// field.
230235
func TestCalcBaseFeeJovian(t *testing.T) {
231236
parentGasLimit := uint64(30_000_000)
232237
denom := uint64(50)
233238
elasticity := uint64(3)
239+
parentGasTarget := parentGasLimit / elasticity
240+
const zeroParentBlobGasUsed = 0
234241

235-
preJovian := TestJovianTime - 1
236-
postJovian := TestJovianTime
242+
preJovian := testJovianTime - 1
243+
postJovian := testJovianTime
237244

238245
tests := []struct {
239-
parentBaseFee int64
240-
parentGasUsed uint64
241-
parentTime uint64
242-
minBaseFee uint64
243-
expectedBaseFee uint64
246+
parentBaseFee int64
247+
parentGasUsed uint64
248+
parentBlobGasUsed uint64
249+
parentTime uint64
250+
minBaseFee uint64
251+
expectedBaseFee uint64
244252
}{
245253
// Test 0: gas used is below target, and the new calculated base fee is very low.
246254
// But since we are pre Jovian, we don't enforce the minBaseFee.
247-
{1, parentGasLimit/elasticity - 1_000_000, preJovian, 1e9, 1},
255+
{1, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, preJovian, 1e9, 1},
248256
// Test 1: gas used is exactly the target gas, but the base fee is set too low so
249257
// the base fee is expected to be the minBaseFee
250-
{1, parentGasLimit / elasticity, postJovian, 1e9, 1e9},
258+
{1, parentGasTarget, zeroParentBlobGasUsed, postJovian, 1e9, 1e9},
251259
// Test 2: gas used exceeds gas target, but the new calculated base fee is still
252260
// too low so the base fee is expected to be the minBaseFee
253-
{1, parentGasLimit/elasticity + 1_000_000, postJovian, 1e9, 1e9},
261+
{1, parentGasTarget + 1_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 1e9},
254262
// Test 3: gas used exceeds gas target, but the new calculated base fee is higher
255263
// than the minBaseFee, so don't enforce minBaseFee. See the calculation below:
256264
// gasUsedDelta = gasUsed - parentGasTarget = 20_000_000 - 30_000_000 / 3 = 10_000_000
257265
// 2e9 * 10_000_000 / 10_000_000 / 50 = 40_000_000
258266
// 2e9 + 40_000_000 = 2_040_000_000, which is greater than minBaseFee
259-
{2e9, parentGasLimit/elasticity + 10_000_000, postJovian, 1e9, 2_040_000_000},
267+
{2e9, parentGasTarget + 10_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 2_040_000_000},
260268
// Test 4: gas used is below target, but the new calculated base fee is still
261269
// too low so the base fee is expected to be the minBaseFee
262-
{1, parentGasLimit/elasticity - 1_000_000, postJovian, 1e9, 1e9},
270+
{1, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, postJovian, 1e9, 1e9},
263271
// Test 5: gas used is below target, and the new calculated base fee is higher
264272
// than the minBaseFee, so don't enforce minBaseFee. See the calculation below:
265273
// gasUsedDelta = gasUsed - parentGasTarget = 9_000_000 - 30_000_000 / 3 = -1_000_000
266274
// 2_097_152 * -1_000_000 / 10_000_000 / 50 = -4194.304
267275
// 2_097_152 - 4194.304 = 2_092_957.696, which is greater than minBaseFee
268-
{2_097_152, parentGasLimit/elasticity - 1_000_000, postJovian, 2e6, 2_092_958},
276+
{2_097_152, parentGasTarget - 1_000_000, zeroParentBlobGasUsed, postJovian, 2e6, 2_092_958},
269277
// Test 6: parent base fee already at minimum, below target => no change
270-
{1e4, parentGasLimit/elasticity - 1, postJovian, 1e4, 1e4},
278+
{1e4, parentGasTarget - 1, zeroParentBlobGasUsed, postJovian, 1e4, 1e4},
271279
// Test 7: parent base fee already at minimum, above target => small increase as usual
272-
{1e4, parentGasLimit/elasticity + 1, postJovian, 1e4, 1e4 + 1},
280+
{1e4, parentGasTarget + 1, zeroParentBlobGasUsed, postJovian, 1e4, 1e4 + 1},
281+
282+
// Test 8: Pre-Jovian: parent base fee already at minimum, gas used at target, blob gas used at limit
283+
// => no increase, minBaseFee ignored, high blob gas used ignored
284+
{1e4, parentGasTarget, parentGasLimit, preJovian, 1e6, 1e4},
285+
// Test 9: parent base fee already at minimum, gas used at target, da footprint above target => small increase
286+
{1e4, parentGasTarget, parentGasTarget + 1, postJovian, 1e4, 1e4 + 1},
287+
// Test 10: Test 3, but with high blob gas used instead of gas used
288+
{2e9, parentGasTarget, parentGasTarget + 10_000_000, postJovian, 1e9, 2_040_000_000},
273289
}
274290
for i, test := range tests {
275291
testName := fmt.Sprintf("test %d", i)
276292
t.Run(testName, func(t *testing.T) {
277293
parent := &types.Header{
278-
Number: common.Big32,
279-
GasLimit: parentGasLimit,
280-
GasUsed: test.parentGasUsed,
281-
BaseFee: big.NewInt(test.parentBaseFee),
282-
Time: test.parentTime,
294+
Number: common.Big32,
295+
GasLimit: parentGasLimit,
296+
GasUsed: test.parentGasUsed,
297+
BlobGasUsed: &test.parentBlobGasUsed,
298+
BaseFee: big.NewInt(test.parentBaseFee),
299+
Time: test.parentTime,
283300
}
284301
parent.Extra = EncodeOptimismExtraData(opConfig(), test.parentTime, denom, elasticity, &test.minBaseFee)
285302
have, want := CalcBaseFee(opConfig(), parent, parent.Time+2), big.NewInt(int64(test.expectedBaseFee))

consensus/misc/eip4844/eip4844.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,16 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade
110110
return errors.New("header is missing blobGasUsed")
111111
}
112112

113-
// Verify that the blob gas used remains within reasonable limits.
114-
if !config.IsOptimism() && *header.BlobGasUsed > bcfg.maxBlobGas() {
115-
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas())
116-
}
117-
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
118-
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
113+
// OP Stack sets a zero blobGasUsed pre-Jovian. Post-Jovian, it stores the DA footprint, which is
114+
// probably not a multiple of [params.BlobTxBlobGasPerBlob].
115+
if !config.IsOptimism() {
116+
// Verify that the blob gas used remains within reasonable limits.
117+
if *header.BlobGasUsed > bcfg.maxBlobGas() {
118+
return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas())
119+
}
120+
if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 {
121+
return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob)
122+
}
119123
}
120124

121125
// Verify the excessBlobGas is correct based on the parent header

core/block_validator.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
106106
}
107107

108108
// Check blob gas usage.
109-
if header.BlobGasUsed != nil {
109+
if !v.config.IsOptimism() && header.BlobGasUsed != nil {
110110
if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated
111111
return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob)
112112
}
@@ -116,6 +116,23 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
116116
}
117117
}
118118

119+
// OP Stack Jovian DA footprint block limit.
120+
if v.config.IsDAFootprintBlockLimit(header.Time) {
121+
if header.BlobGasUsed == nil {
122+
return errors.New("nil blob gas used in post-Jovian block header, should store DA footprint")
123+
}
124+
blobGasUsed := *header.BlobGasUsed
125+
daFootprint, err := types.CalcDAFootprint(block.Transactions())
126+
if err != nil {
127+
return fmt.Errorf("failed to calculate DA footprint: %w", err)
128+
} else if blobGasUsed != daFootprint {
129+
return fmt.Errorf("invalid DA footprint in blobGasUsed field (remote: %d local: %d)", blobGasUsed, daFootprint)
130+
}
131+
if daFootprint > block.GasLimit() {
132+
return fmt.Errorf("DA footprint %d exceeds block gas limit %d", daFootprint, block.GasLimit())
133+
}
134+
}
135+
119136
// Ancestor block must be known.
120137
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
121138
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {

core/chain_makers.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -421,14 +421,6 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
421421
b.header.RequestsHash = &reqHash
422422
}
423423

424-
if config.IsDAFootprintBlockLimit(b.header.Time) {
425-
gasUsed, err := types.CalcGasUsedJovian(b.txs, b.header.GasUsed)
426-
if err != nil {
427-
panic(err)
428-
}
429-
b.header.GasUsed = gasUsed
430-
}
431-
432424
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
433425
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
434426
if err != nil {

core/state_processor.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
129129
requests = [][]byte{}
130130
}
131131

132-
if p.config.IsDAFootprintBlockLimit(block.Time()) {
133-
gasUsed, err := types.CalcGasUsedJovian(block.Transactions(), *usedGas)
134-
if err != nil {
135-
return nil, fmt.Errorf("failed to calculate Jovian gas used: %w", err)
136-
}
137-
*usedGas = gasUsed
138-
}
139-
140132
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
141133
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())
142134

core/types/block.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ type Header struct {
9696
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
9797

9898
// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
99+
// OP Stack stores the DA footprint in this field starting with the Jovian fork.
99100
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
100101

101102
// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.

core/types/rollup_cost.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -556,10 +556,11 @@ func ExtractDAFootprintGasScalar(data []byte) (uint16, error) {
556556
return daFootprintGasScalar, nil
557557
}
558558

559-
// CalcGasUsedJovian calculates the gas used for an OP Stack chain.
560-
// Jovian introduces a DA footprint block limit, which potentially increases the gasUsed.
561-
// CalcGasUsedJovian must not be called for pre-Jovian blocks.
562-
func CalcGasUsedJovian(txs []*Transaction, evmGasUsed uint64) (uint64, error) {
559+
// CalcDAFootprint calculates the total DA footprint of a block for an OP Stack chain.
560+
// Jovian introduces a DA footprint block limit which is stored in the BlobGasUsed header field and that is taken
561+
// into account during base fee updates.
562+
// CalcDAFootprint must not be called for pre-Jovian blocks.
563+
func CalcDAFootprint(txs []*Transaction) (uint64, error) {
563564
if len(txs) == 0 || !txs[0].IsDepositTx() {
564565
return 0, errors.New("missing deposit transaction")
565566
}
@@ -572,25 +573,21 @@ func CalcGasUsedJovian(txs []*Transaction, evmGasUsed uint64) (uint64, error) {
572573
// sufficient to check last transaction because deposits precede non-deposit txs
573574
return 0, errors.New("unexpected non-deposit transactions in Jovian activation block")
574575
}
575-
return evmGasUsed, nil
576+
return 0, nil
576577
} // ExtractDAFootprintGasScalar catches all invalid lengths
577578

578579
daFootprintGasScalar, err := ExtractDAFootprintGasScalar(data)
579580
if err != nil {
580581
return 0, err
581582
}
582-
var cumulativeDAFootprint uint64
583+
var daFootprint uint64
583584
for _, tx := range txs {
584585
if tx.IsDepositTx() {
585586
continue
586587
}
587-
cumulativeDAFootprint += tx.RollupCostData().EstimatedDASize().Uint64()
588+
daFootprint += tx.RollupCostData().EstimatedDASize().Uint64() * uint64(daFootprintGasScalar)
588589
}
589-
daFootprint := uint64(daFootprintGasScalar) * cumulativeDAFootprint
590-
if evmGasUsed < daFootprint {
591-
return daFootprint, nil
592-
}
593-
return evmGasUsed, nil
590+
return daFootprint, nil
594591
}
595592

596593
// L1Cost computes the the data availability fee for transactions in blocks prior to the Ecotone

0 commit comments

Comments
 (0)