Skip to content

Commit 2e0d9ab

Browse files
geokneeDenseDenise
andauthored
op-e2e/actions: add L1 Osaka activation test (ethereum-optimism#17666)
* add Fusaka activation test * updated the test to add L1 blob base fee assertions * fixes * wire up Fusaka on L1 and improve test * give test teeth * improve prague fork test * activate prague at genesis and osaka after * updated op-geth to v1.101603.0-synctest.0.0.20250930110811-5eee1eab50e6 pairs with ethereum-optimism/op-geth#690 * extend test to cover BPO forks * typo fix * typo fix * dedupe tests * rename file * respond to review --------- Co-authored-by: Dense <[email protected]>
1 parent 77aef07 commit 2e0d9ab

File tree

6 files changed

+210
-23
lines changed

6 files changed

+210
-23
lines changed

op-chain-ops/genesis/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,18 @@ type UpgradeScheduleDeployConfig struct {
377377
L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"`
378378
// When Prague activates. Relative to L1 genesis.
379379
L1PragueTimeOffset *hexutil.Uint64 `json:"l1PragueTimeOffset,omitempty"`
380+
// When Osaka activates. Relative to L1 genesis.
381+
L1OsakaTimeOffset *hexutil.Uint64 `json:"l1OsakaTimeOffset,omitempty"`
382+
// When BPO1 activates. Relative to L1 genesis.
383+
L1BPO1TimeOffset *hexutil.Uint64 `json:"l1BPO1TimeOffset,omitempty"`
384+
// When BPO2 activates. Relative to L1 genesis.
385+
L1BPO2TimeOffset *hexutil.Uint64 `json:"l1BPO2TimeOffset,omitempty"`
386+
// When BPO3 activates. Relative to L1 genesis.
387+
L1BPO3TimeOffset *hexutil.Uint64 `json:"l1BPO3TimeOffset,omitempty"`
388+
// When BPO4 activates. Relative to L1 genesis.
389+
L1BPO4TimeOffset *hexutil.Uint64 `json:"l1BPO4TimeOffset,omitempty"`
390+
// Blob schedule config.
391+
L1BlobScheduleConfig *params.BlobScheduleConfig `json:"l1BlobScheduleConfig,omitempty"`
380392
}
381393

382394
var _ ConfigChecker = (*UpgradeScheduleDeployConfig)(nil)

op-chain-ops/genesis/genesis.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
139139
DevL1DeployConfig: config.DevL1DeployConfig,
140140
L1ChainID: eth.ChainIDFromUInt64(config.L1ChainID),
141141
L1PragueTimeOffset: (*uint64)(config.L1PragueTimeOffset),
142+
L1OsakaTimeOffset: (*uint64)(config.L1OsakaTimeOffset),
143+
L1BPO1TimeOffset: (*uint64)(config.L1BPO1TimeOffset),
144+
L1BPO2TimeOffset: (*uint64)(config.L1BPO2TimeOffset),
145+
L1BPO3TimeOffset: (*uint64)(config.L1BPO3TimeOffset),
146+
L1BPO4TimeOffset: (*uint64)(config.L1BPO4TimeOffset),
147+
BlobScheduleConfig: config.L1BlobScheduleConfig,
142148
})
143149
}
144150

@@ -148,6 +154,18 @@ type DevL1DeployConfigMinimal struct {
148154
L1ChainID eth.ChainID
149155
// When Prague activates. Relative to L1 genesis.
150156
L1PragueTimeOffset *uint64
157+
// When Osaka activates. Relative to L1 genesis.
158+
L1OsakaTimeOffset *uint64
159+
// When BPO1 activates. Relative to L1 genesis.
160+
L1BPO1TimeOffset *uint64
161+
// When BPO2 activates. Relative to L1 genesis.
162+
L1BPO2TimeOffset *uint64
163+
// When BPO3 activates. Relative to L1 genesis.
164+
L1BPO3TimeOffset *uint64
165+
// When BPO4 activates. Relative to L1 genesis.
166+
L1BPO4TimeOffset *uint64
167+
// Blob schedule config.
168+
BlobScheduleConfig *params.BlobScheduleConfig
151169
}
152170

153171
// NewL1GenesisMinimal creates a L1 dev genesis template.
@@ -203,6 +221,29 @@ func NewL1GenesisMinimal(config *DevL1DeployConfigMinimal) (*core.Genesis, error
203221
pragueTime := uint64(timestamp) + uint64(*config.L1PragueTimeOffset)
204222
chainConfig.PragueTime = &pragueTime
205223
}
224+
if config.L1OsakaTimeOffset != nil {
225+
osakaTime := uint64(timestamp) + uint64(*config.L1OsakaTimeOffset)
226+
chainConfig.OsakaTime = &osakaTime
227+
}
228+
if config.L1BPO1TimeOffset != nil {
229+
bpo1Time := uint64(timestamp) + uint64(*config.L1BPO1TimeOffset)
230+
chainConfig.BPO1Time = &bpo1Time
231+
}
232+
if config.L1BPO2TimeOffset != nil {
233+
bpo2Time := uint64(timestamp) + uint64(*config.L1BPO2TimeOffset)
234+
chainConfig.BPO2Time = &bpo2Time
235+
}
236+
if config.L1BPO3TimeOffset != nil {
237+
bpo3Time := uint64(timestamp) + uint64(*config.L1BPO3TimeOffset)
238+
chainConfig.BPO3Time = &bpo3Time
239+
}
240+
if config.L1BPO4TimeOffset != nil {
241+
bpo4Time := uint64(timestamp) + uint64(*config.L1BPO4TimeOffset)
242+
chainConfig.BPO4Time = &bpo4Time
243+
}
244+
if config.BlobScheduleConfig != nil {
245+
chainConfig.BlobScheduleConfig = config.BlobScheduleConfig
246+
}
206247
// Note: excess-blob-gas, blob-gas-used, withdrawals-hash, requests-hash are set to reasonable defaults for L1 by the ToBlock() function
207248
return &core.Genesis{
208249
Config: &chainConfig,

op-e2e/actions/helpers/l1_miner.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,17 @@ func (s *L1Miner) ActEmptyBlock(t Testing) *types.Block {
304304
return s.ActL1EndBlock(t)
305305
}
306306

307+
func (s *L1Miner) ActBuildToOsaka(t Testing) *types.Block {
308+
t.Helper()
309+
require.NotNil(t, s.l1Cfg.Config.OsakaTime, "cannot activate OsakaTime when it is not scheduled")
310+
h := s.L1Chain().CurrentHeader()
311+
for h.Time < *s.l1Cfg.Config.OsakaTime {
312+
h = s.ActEmptyBlock(t).Header()
313+
}
314+
require.True(t, s.l1Cfg.Config.IsOsaka(h.Number, h.Time), "Osaka not active at block", h.Number)
315+
return s.L1Chain().GetBlockByHash(h.Hash())
316+
}
317+
307318
func (s *L1Miner) Close() error {
308319
return s.L1Replica.Close()
309320
}

op-e2e/actions/helpers/l2_sequencer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ func (s *L2Sequencer) ActL2ForceAdvanceL1Origin(t Testing) {
162162
s.mockL1OriginSelector.originOverride = nextOrigin
163163
}
164164

165-
// ActBuildToL1Head builds empty blocks until (incl.) the L1 head becomes the L2 origin
165+
// ActBuildToL1Head builds empty blocks until (incl.) the L1 head becomes the L1 origin of the L2 head
166166
func (s *L2Sequencer) ActBuildToL1Head(t Testing) {
167-
for s.engine.UnsafeL2Head().L1Origin.Number < s.syncStatus.L1Head().Number {
167+
for s.L2Unsafe().L1Origin.Number < s.syncStatus.L1Head().Number {
168168
s.ActL2PipelineFull(t)
169169
s.ActL2EmptyBlock(t)
170170
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package proofs_test
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
7+
batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags"
8+
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
9+
actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
10+
"github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers"
11+
legacybindings "github.com/ethereum-optimism/optimism/op-e2e/bindings"
12+
"github.com/ethereum-optimism/optimism/op-service/eth"
13+
"github.com/ethereum-optimism/optimism/op-service/predeploys"
14+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
15+
"github.com/ethereum/go-ethereum/common"
16+
"github.com/ethereum/go-ethereum/common/hexutil"
17+
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
18+
"github.com/ethereum/go-ethereum/params"
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
// Test_ProgramAction_BlobParameterForks tests the blob base fee calculation for different forks.
23+
func Test_ProgramAction_BlobParameterForks(gt *testing.T) {
24+
runBlobParameterForksTest := func(gt *testing.T, testCfg *helpers.TestCfg[any]) {
25+
t := actionsHelpers.NewDefaultTesting(gt)
26+
27+
// Create test environment with Fusaka activation
28+
env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(),
29+
helpers.NewBatcherCfg(
30+
func(c *actionsHelpers.BatcherCfg) {
31+
c.DataAvailabilityType = batcherFlags.BlobsType
32+
},
33+
),
34+
func(dp *genesis.DeployConfig) {
35+
dp.L1CancunTimeOffset = ptr(hexutil.Uint64(0))
36+
dp.L1PragueTimeOffset = ptr(hexutil.Uint64(12))
37+
dp.L1OsakaTimeOffset = ptr(hexutil.Uint64(24))
38+
dp.L1BPO1TimeOffset = ptr(hexutil.Uint64(36))
39+
dp.L1BPO2TimeOffset = ptr(hexutil.Uint64(48))
40+
dp.L1BPO3TimeOffset = ptr(hexutil.Uint64(60))
41+
dp.L1BPO4TimeOffset = ptr(hexutil.Uint64(72))
42+
dp.L1BlobScheduleConfig = &params.BlobScheduleConfig{
43+
Cancun: params.DefaultCancunBlobConfig,
44+
Osaka: params.DefaultOsakaBlobConfig,
45+
Prague: params.DefaultPragueBlobConfig,
46+
BPO1: params.DefaultBPO1BlobConfig,
47+
BPO2: params.DefaultBPO2BlobConfig,
48+
BPO3: params.DefaultBPO3BlobConfig,
49+
BPO4: params.DefaultBPO4BlobConfig,
50+
}
51+
dp.L1GenesisBlockExcessBlobGas = ptr(hexutil.Uint64(1e8)) // Jack up the blob market so we can test the blob fee calculation
52+
},
53+
)
54+
55+
miner, sequencer := env.Miner, env.Sequencer
56+
57+
// Bind to L1Block contract on L2
58+
l1BlockContract, err := legacybindings.NewL1Block(predeploys.L1BlockAddr, env.Engine.EthClient())
59+
require.NoError(t, err)
60+
61+
atBlockWithHash := func(hash common.Hash) *bind.CallOpts {
62+
return &bind.CallOpts{
63+
BlockHash: hash,
64+
}
65+
}
66+
67+
// requireConsistentBlobBaseFeeForFork requires the blob base fee to be consistent between
68+
// the L1 Origin block (computed using the excess blob gas and l1 chain config)
69+
// and the L1 Block contract on L2 (accessed with a contract method call), for a given fork predicate.
70+
requireConsistentBlobBaseFeeForFork := func(t actionsHelpers.StatefulTesting, l2Block eth.L2BlockRef, expectActive bool, label string, isActive func(num *big.Int, time uint64) bool) {
71+
bbfL2, err := l1BlockContract.BlobBaseFee(atBlockWithHash(l2Block.Hash))
72+
require.NoError(t, err)
73+
74+
l1Origin := miner.L1Chain().GetHeaderByHash(l2Block.L1Origin.Hash)
75+
if expectActive {
76+
require.True(t, isActive(l1Origin.Number, l1Origin.Time), "%s not active at l1 origin %d, time %d", label, l1Origin.Number, l1Origin.Time)
77+
} else {
78+
require.False(t, isActive(l1Origin.Number, l1Origin.Time), "%s should not be active at l1 origin %d, time %d", label, l1Origin.Number, l1Origin.Time)
79+
}
80+
bbfL1 := eip4844.CalcBlobFee(env.Sd.L1Cfg.Config, l1Origin)
81+
82+
require.True(t, bbfL2.Cmp(bbfL1) == 0,
83+
"%s: blob base fee does not match, bbfL2=%d, bbfL1=%d, l1BlockNum=%d, l2BlockNum=%d", label, bbfL2, bbfL1, l1Origin.Number, l2Block.Number)
84+
85+
require.True(t, bbfL2.Cmp(big.NewInt(1)) > 0,
86+
"%s: blob base fee is unrealistically low and doesn't exercise the blob fee calculation", label)
87+
}
88+
89+
// buildL1ToTime advances L1 with empty blocks until the given fork time.
90+
buildL1ToTime := func(t actionsHelpers.StatefulTesting, forkTime *uint64) {
91+
require.NotNil(t, forkTime, "fork time must be configured")
92+
h := miner.L1Chain().CurrentHeader()
93+
for h.Time < *forkTime {
94+
h = miner.ActEmptyBlock(t).Header()
95+
}
96+
}
97+
98+
// Iterate through all forks and assert pre/post activation blob fees match expectations
99+
cfg := env.Sd.L1Cfg.Config
100+
forks := []struct {
101+
label string
102+
forkTime *uint64
103+
isActive func(num *big.Int, time uint64) bool
104+
}{
105+
{"Prague", cfg.PragueTime, func(num *big.Int, time uint64) bool { return cfg.IsPrague(num, time) }},
106+
{"Osaka", cfg.OsakaTime, func(num *big.Int, time uint64) bool { return cfg.IsOsaka(num, time) }},
107+
{"BPO1", cfg.BPO1Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO1(num, time) }},
108+
{"BPO2", cfg.BPO2Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO2(num, time) }},
109+
{"BPO3", cfg.BPO3Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO3(num, time) }},
110+
{"BPO4", cfg.BPO4Time, func(num *big.Int, time uint64) bool { return cfg.IsBPO4(num, time) }},
111+
}
112+
for _, f := range forks {
113+
// Advance L1 to fork activation
114+
buildL1ToTime(t, f.forkTime)
115+
116+
// Build an empty L2 block which still has a pre-fork L1 origin, and check blob fee
117+
sequencer.ActL2EmptyBlock(t)
118+
l2Block := sequencer.SyncStatus().UnsafeL2
119+
requireConsistentBlobBaseFeeForFork(t, l2Block, false, f.label, f.isActive)
120+
121+
// Advance L2 chain until L1 origin is at/after the fork activation
122+
sequencer.ActL1HeadSignal(t)
123+
sequencer.ActBuildToL1HeadUnsafe(t)
124+
125+
l2Block = sequencer.L2Unsafe()
126+
require.Greater(t, l2Block.Number, uint64(1))
127+
requireConsistentBlobBaseFeeForFork(t, l2Block, true, f.label, f.isActive)
128+
}
129+
130+
// Final sync
131+
env.BatchMineAndSync(t)
132+
133+
// Run fault proof program
134+
safeL2Head := sequencer.L2Safe()
135+
env.RunFaultProofProgramFromGenesis(t, safeL2Head.Number, testCfg.CheckResult, testCfg.InputParams...)
136+
}
137+
138+
matrix := helpers.NewMatrix[any]()
139+
matrix.AddDefaultTestCases(nil, helpers.NewForkMatrix(helpers.LatestFork), runBlobParameterForksTest)
140+
matrix.Run(gt)
141+
}

op-e2e/actions/proofs/l1_prague_fork_test.go

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ import (
77
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
88
actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
99
"github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers"
10-
legacybindings "github.com/ethereum-optimism/optimism/op-e2e/bindings"
1110
"github.com/ethereum-optimism/optimism/op-service/eth"
12-
"github.com/ethereum-optimism/optimism/op-service/predeploys"
13-
"github.com/ethereum/go-ethereum/accounts/abi/bind"
1411
"github.com/ethereum/go-ethereum/common/hexutil"
15-
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
1612
"github.com/ethereum/go-ethereum/core/types"
1713
"github.com/holiman/uint256"
1814
"github.com/stretchr/testify/require"
@@ -44,10 +40,7 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {
4440
},
4541
)
4642

47-
miner, batcher, verifier, sequencer, engine := env.Miner, env.Batcher, env.Sequencer, env.Sequencer, env.Engine
48-
49-
l1Block, err := legacybindings.NewL1Block(predeploys.L1BlockAddr, engine.EthClient())
50-
require.NoError(t, err)
43+
miner, batcher, verifier, sequencer, _ := env.Miner, env.Batcher, env.Sequencer, env.Sequencer, env.Engine
5144

5245
// utils
5346
checkVerifierDerivedToL1Head := func(t actionsHelpers.StatefulTesting) {
@@ -89,15 +82,6 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {
8982
checkVerifierDerivedToL1Head(t)
9083
}
9184

92-
checkL1BlockBlobBaseFee := func(t actionsHelpers.StatefulTesting, l2Block eth.L2BlockRef) {
93-
l1BlockID := l2Block.L1Origin
94-
l1BlockHeader := miner.L1Chain().GetHeaderByHash(l1BlockID.Hash)
95-
expectedBbf := eip4844.CalcBlobFee(env.Sd.L1Cfg.Config, l1BlockHeader)
96-
bbf, err := l1Block.BlobBaseFee(&bind.CallOpts{BlockHash: l2Block.Hash})
97-
require.NoError(t, err, "failed to get blob base fee")
98-
require.Equal(t, expectedBbf.Uint64(), bbf.Uint64(), "l1Block blob base fee does not match expectation, l1BlockNum %d, l2BlockNum %d", l1BlockID.Number, l2Block.Number)
99-
}
100-
10185
requireSafeHeadProgression := func(t actionsHelpers.StatefulTesting, safeL2Before, safeL2After eth.L2BlockRef, batchedWithSetCodeTx bool) {
10286
if batchedWithSetCodeTx {
10387
require.Equal(t, safeL2Before, safeL2After, "safe head should not have changed (SetCode / type 4 batcher tx ignored)")
@@ -142,11 +126,10 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {
142126
// Cache safe head before verifier sync
143127
safeL2Initial := verifier.SyncStatus().SafeL2
144128

145-
// Build an empty L2 block which has a pre-prague L1 origin, and check the blob fee is correct
129+
// Build an empty L2 block which has a pre-prague L1 origin
146130
sequencer.ActL2EmptyBlock(t)
147131
l1OriginHeader := miner.L1Chain().GetHeaderByHash(verifier.SyncStatus().UnsafeL2.L1Origin.Hash)
148132
requirePragueStatusOnL1(false, l1OriginHeader)
149-
checkL1BlockBlobBaseFee(t, verifier.SyncStatus().UnsafeL2)
150133

151134
// Build L2 unsafe chain and batch it to L1 using either DynamicFee or
152135
// EIP-7702 SetCode txs
@@ -163,10 +146,9 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) {
163146

164147
sequencer.ActBuildToL1Head(t) // Advance L2 chain until L1 origin has Prague active
165148

166-
// Check that the l1 origin is now a Prague block, and that the blob fee is correct
149+
// Check that the l1 origin is now a Prague block
167150
l1Origin := miner.L1Chain().GetHeaderByNumber(verifier.SyncStatus().UnsafeL2.L1Origin.Number)
168151
requirePragueStatusOnL1(true, l1Origin)
169-
checkL1BlockBlobBaseFee(t, verifier.SyncStatus().UnsafeL2)
170152

171153
// Batch and sync again
172154
buildUnsafeL2AndSubmit(testCfg.Custom.useSetCodeTx)

0 commit comments

Comments
 (0)