Skip to content

Commit 403f6d4

Browse files
authored
op-sync-tester: Verifier Engine APIs for Jovian (#18307)
* op-acceptance-tests: Include Jovian for Sync Tester HF tests * op-sync-tester: Support Jovian * Fix sync tester extra data validation * Check safe only advancement * fix comment * Disable discovery because we cut the CLP2P manually
1 parent 3ab2b6e commit 403f6d4

File tree

4 files changed

+81
-16
lines changed

4 files changed

+81
-16
lines changed

op-acceptance-tests/tests/sync_tester/sync_tester_hfs/init_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import (
1515
func TestMain(m *testing.M) {
1616
presets.DoMain(m, presets.WithSimpleWithSyncTester(),
1717
presets.WithCompatibleTypes(compat.SysGo),
18-
presets.WithHardforkSequentialActivation(forks.Bedrock, forks.Jovian, 15),
18+
presets.WithHardforkSequentialActivation(forks.Bedrock, forks.Jovian, 6),
19+
presets.WithNoDiscovery(),
1920
stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) {
2021
// For supporting pre-delta batches
2122
cfg.BatchType = derive.SingularBatchType

op-acceptance-tests/tests/sync_tester/sync_tester_hfs/sync_tester_hfs_test.go

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
77
"github.com/ethereum-optimism/optimism/op-devstack/dsl"
88
"github.com/ethereum-optimism/optimism/op-devstack/presets"
9+
"github.com/ethereum-optimism/optimism/op-service/eth"
910
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
1011
)
1112

@@ -14,21 +15,54 @@ func TestSyncTesterHardforks(gt *testing.T) {
1415

1516
sys := presets.NewSimpleWithSyncTester(t)
1617
require := t.Require()
18+
logger := t.Logger()
19+
ctx := t.Ctx()
1720

18-
// Hardforks will be activated from Bedrock to Isthmus, 9 hardforks with 15 second time delta between.
19-
// 15 * 9 = 135s, so we need at least 69 (135 / 2 + 1) L2 blocks with block time 2 to make the CL experience scheduled hardforks.
20-
targetNum := 70
21+
// Check the L2CL passed configured hardforks
22+
jovianTime := sys.L2Chain.Escape().ChainConfig().JovianTime
23+
require.NotNil(jovianTime, "jovian must be activated")
24+
25+
// Hardforks will be activated from Bedrock to Jovian, 10 hardforks with 6 second time delta between.
26+
// 6 * 10 = 60s, so we need at least 30 (60 / 2 + 1) L2 blocks with block time 2 to make the CL experience scheduled hardforks.
27+
targetNum := 32
28+
29+
// Unsafe advancement: NewPayload -> ForkchoiceUpdated(no attr)
2130
dsl.CheckAll(t,
22-
sys.L2CL.AdvancedFn(types.LocalUnsafe, uint64(targetNum), targetNum*2+10),
23-
sys.L2CL2.AdvancedFn(types.LocalUnsafe, uint64(targetNum), targetNum*2+10),
31+
sys.L2CL.AdvancedFn(types.LocalUnsafe, uint64(targetNum), targetNum+10),
32+
sys.L2CL2.AdvancedFn(types.LocalUnsafe, uint64(targetNum), targetNum+10),
2433
)
2534

2635
current := sys.L2CL2.HeadBlockRef(types.LocalUnsafe)
36+
require.Greater(current.Time, *jovianTime, "must pass jovian block")
37+
// Check block hash state from L2CL2 which was synced using the sync tester
38+
require.Equal(sys.L2EL.BlockRefByNumber(current.Number).Hash, current.Hash, "hash mismatch")
39+
logger.Info("Advancement using CLP2P done", "head", sys.L2EL.UnsafeHead())
2740

28-
// Check the L2CL passed configured hardforks
29-
isthmusTime := sys.L2Chain.Escape().ChainConfig().IsthmusTime
30-
require.NotNil(isthmusTime, "isthmus must be activated")
31-
require.Greater(current.Time, *isthmusTime, "must pass isthmus block")
41+
// Disconnect CLP2P to solely rely on derivation
42+
sys.L2CL2.DisconnectPeer(sys.L2CL)
43+
sys.L2CL.DisconnectPeer(sys.L2CL2)
44+
sys.L2CL2.Stop()
45+
sessionIDs := sys.SyncTester.ListSessions()
46+
require.GreaterOrEqual(len(sessionIDs), 1, "at least one session")
47+
sessionID := sessionIDs[0]
48+
logger.Info("SyncTester EL", "sessionID", sessionID)
49+
syncTesterClient := sys.SyncTester.Escape().APIWithSession(sessionID)
50+
// Resync starting from genesis
51+
require.NoError(syncTesterClient.ResetSession(ctx))
52+
sys.SyncTesterL2EL.UnsafeHead().NumEqualTo(0)
53+
54+
// Wait until safe head reached Jovian
55+
sys.L2CL.Reached(types.LocalSafe, current.Number, 20)
56+
57+
// Check safe head advancement can solely rely on derivation reaching Jovian
58+
// Safe advancement: ForkchoiceUpdated(with attr) -> GetPayload -> NewPayload -> ForkchoiceUpdated(no attr)
59+
sys.L2CL2.Start()
60+
sys.L2CL2.Reached(types.LocalSafe, current.Number, 20)
61+
sys.SyncTesterL2EL.Reached(eth.Safe, current.Number, 10)
62+
63+
current = sys.L2CL2.HeadBlockRef(types.LocalSafe)
64+
require.Greater(current.Time, *jovianTime, "must pass jovian block")
3265
// Check block hash state from L2CL2 which was synced using the sync tester
3366
require.Equal(sys.L2EL.BlockRefByNumber(current.Number).Hash, current.Hash, "hash mismatch")
67+
logger.Info("Advancement using derivation done", "head", sys.L2EL.UnsafeHead())
3468
}

op-devstack/sysgo/deployer.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ func WithInteropAtGenesis() DeployerOption {
332332

333333
// WithHardforkSequentialActivation configures a deployment such that L2 chains
334334
// activate hardforks sequentially, starting from startFork and continuing
335-
// until (but not including) endFork. Each successive fork is scheduled at
335+
// until (including) endFork. Each successive fork is scheduled at
336336
// an increasing offset.
337337
func WithHardforkSequentialActivation(startFork, endFork opforks.Name, delta *uint64) DeployerOption {
338338
return func(p devtest.P, keys devkeys.Keys, builder intentbuilder.Builder) {
@@ -341,7 +341,7 @@ func WithHardforkSequentialActivation(startFork, endFork opforks.Name, delta *ui
341341
activateWithOffset := false
342342
deactivate := false
343343
for idx, refFork := range opforks.All {
344-
if deactivate || refFork == endFork {
344+
if deactivate {
345345
l2Cfg.WithForkAtOffset(refFork, nil)
346346
deactivate = true
347347
continue
@@ -353,6 +353,9 @@ func WithHardforkSequentialActivation(startFork, endFork opforks.Name, delta *ui
353353
if startFork == refFork {
354354
activateWithOffset = true
355355
}
356+
if endFork == refFork {
357+
deactivate = true
358+
}
356359
}
357360
}
358361
}

op-sync-tester/synctester/backend/sync_tester.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package backend
33
import (
44
"bytes"
55
"context"
6+
"encoding/binary"
67
"encoding/hex"
78
"encoding/json"
89
"errors"
@@ -426,11 +427,17 @@ func (s *SyncTester) forkchoiceUpdated(ctx context.Context, session *eth.SyncTes
426427
// Consider as sync error if read only EL interaction fails because we cannot validate
427428
return &eth.ForkchoiceUpdatedResult{PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionSyncing}, PayloadID: nil}, nil
428429
}
430+
// https://github.com/ethereum-optimism/specs/blob/510377c586d0cbede2d40402d2371fcadd5656a0/specs/protocol/jovian/exec-engine.md#minimum-base-fee-in-block-header
431+
// Implicitly determine whether jovian is enabled by inspecting extraData from read only EL data
432+
isJovian := eip1559.ValidateMinBaseFeeExtraData(newBlock.Header().Extra) == nil
429433
// https://github.com/ethereum-optimism/specs/blob/972dec7c7c967800513c354b2f8e5b79340de1c3/specs/protocol/holocene/exec-engine.md#eip-1559-parameters-in-block-header
430434
// Implicitly determine whether holocene is enabled by inspecting extraData from read only EL data
431-
isHolocene := eip1559.ValidateHoloceneExtraData(newBlock.Header().Extra) == nil
435+
isHolocene := true // holocene is always activated when jovian is activated
436+
if !isJovian {
437+
isHolocene = eip1559.ValidateHoloceneExtraData(newBlock.Header().Extra) == nil
438+
}
432439
// Sanity check attr comparing with newBlock
433-
if err := s.validateAttributesForBlock(attr, newBlock, isHolocene); err != nil {
440+
if err := s.validateAttributesForBlock(attr, newBlock, isHolocene, isJovian); err != nil {
434441
// https://github.com/ethereum/execution-apis/blob/584905270d8ad665718058060267061ecfd79ca5/src/engine/paris.md#specification-1
435442
// Client software MUST respond to this method call in the following way: {error: {code: -38003, message: "Invalid payload attributes"}} if the payload is deemed VALID and forkchoiceState has been applied successfully, but no build process has been started due to invalid payloadAttributes.
436443
return &eth.ForkchoiceUpdatedResult{PayloadStatus: eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, PayloadID: nil}, engine.InvalidPayloadAttributes.With(err)
@@ -488,9 +495,12 @@ func (s *SyncTester) forkchoiceUpdated(ctx context.Context, session *eth.SyncTes
488495
// - Gas limit must match.
489496
// - If Holocene is active: Extra data must be exactly 9 bytes, the version byte must equal to 0,
490497
// the remaining 8 bytes must match the EIP-1559 parameters.
498+
// - If Jovian is active: Extra data must be exactly 17 bytes, the version byte must equal to 1,
499+
// the first 8 bytes must match the EIP-1559 parameters,
500+
// the remaining 8 bytes must match the MinBaseFee parameter.
491501
//
492502
// Returns an error if any mismatch or invalid condition is found, otherwise nil.
493-
func (s *SyncTester) validateAttributesForBlock(attr *eth.PayloadAttributes, block *types.Block, isHolocene bool) error {
503+
func (s *SyncTester) validateAttributesForBlock(attr *eth.PayloadAttributes, block *types.Block, isHolocene, isJovian bool) error {
494504
h := block.Header()
495505
if h.Time != uint64(attr.Timestamp) {
496506
return fmt.Errorf("timestamp mismatch: header=%d, attr=%d", h.Time, attr.Timestamp)
@@ -547,7 +557,7 @@ func (s *SyncTester) validateAttributesForBlock(attr *eth.PayloadAttributes, blo
547557
// Cannot validate since EL will fall back to prior eip1559 constants
548558
return nil
549559
}
550-
if !bytes.Equal(block.Extra()[1:], (*attr.EIP1559Params)[:]) {
560+
if !bytes.Equal(block.Extra()[1:1+8], (*attr.EIP1559Params)[:]) {
551561
return fmt.Errorf("eip1559Params mismatch: %s != 0x%s", *attr.EIP1559Params, hex.EncodeToString(block.Extra()[1:]))
552562
}
553563
} else {
@@ -557,6 +567,23 @@ func (s *SyncTester) validateAttributesForBlock(attr *eth.PayloadAttributes, blo
557567
return fmt.Errorf("holocene disabled but EIP1559Params not nil. eip1559Params: %s", attr.EIP1559Params)
558568
}
559569
}
570+
if isJovian {
571+
// https://github.com/ethereum-optimism/specs/blob/510377c586d0cbede2d40402d2371fcadd5656a0/specs/protocol/jovian/exec-engine.md#minimum-base-fee-in-payloadattributesv3
572+
// Spec: The Engine API PayloadAttributesV3 is extended with a new field minBaseFee
573+
if attr.MinBaseFee == nil {
574+
return errors.New("jovian enabled but MinBaseFee nil")
575+
}
576+
minBaseFee := binary.BigEndian.Uint64(block.Extra()[1+8 : 1+8+8])
577+
if minBaseFee != *attr.MinBaseFee {
578+
return fmt.Errorf("MinBaseFee mismatch: %d != %d", *attr.MinBaseFee, minBaseFee)
579+
}
580+
} else {
581+
// https://github.com/ethereum-optimism/specs/blob/510377c586d0cbede2d40402d2371fcadd5656a0/specs/protocol/jovian/exec-engine.md#minimum-base-fee-in-payloadattributesv3
582+
// Spec: The minBaseFee MUST be null prior to the Jovian fork, and MUST be non-null after the Jovian fork.
583+
if attr.MinBaseFee != nil {
584+
return fmt.Errorf("jovian disabled but MinBaseFee not nil. MinBaseFee: %d", attr.MinBaseFee)
585+
}
586+
}
560587
return nil
561588
}
562589

0 commit comments

Comments
 (0)