@@ -19,6 +19,7 @@ import (
19
19
"github.com/ethereum-optimism/optimism/op-service/eth"
20
20
"github.com/ethereum-optimism/optimism/op-service/testlog"
21
21
"github.com/ethereum-optimism/optimism/op-service/testutils"
22
+ "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/cross"
22
23
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
23
24
supervisortypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
24
25
"github.com/ethereum/go-ethereum/common"
@@ -33,7 +34,22 @@ import (
33
34
"github.com/stretchr/testify/require"
34
35
)
35
36
36
- func setupTwoChains () (* staticConfigSource , * eth.SuperV1 , * stubTasks ) {
37
+ type chainSetupOpts struct {
38
+ expiryWindow uint64
39
+ }
40
+
41
+ func WithExpiryWindow (window uint64 ) func (* chainSetupOpts ) {
42
+ return func (opts * chainSetupOpts ) {
43
+ opts .expiryWindow = window
44
+ }
45
+ }
46
+
47
+ func setupTwoChains (opts ... func (* chainSetupOpts )) (* staticConfigSource , * eth.SuperV1 , * stubTasks ) {
48
+ chainSetupOpts := & chainSetupOpts {}
49
+ for _ , opt := range opts {
50
+ opt (chainSetupOpts )
51
+ }
52
+
37
53
rollupCfg1 := chaincfg .OPSepolia ()
38
54
chainCfg1 := chainconfig .OPSepoliaChainConfig ()
39
55
@@ -49,14 +65,23 @@ func setupTwoChains() (*staticConfigSource, *eth.SuperV1, *stubTasks) {
49
65
{ChainID : eth .ChainIDFromBig (rollupCfg2 .L2ChainID ), Output : eth .OutputRoot (& eth.OutputV0 {BlockHash : common.Hash {0x22 }})},
50
66
},
51
67
}
52
- depset , _ := depset .NewStaticConfigDependencySet (map [eth.ChainID ]* depset.StaticConfigDependency {
53
- eth .ChainIDFromBig (rollupCfg1 .L2ChainID ): {ChainIndex : chainA , ActivationTime : 0 , HistoryMinTime : 0 },
54
- eth .ChainIDFromBig (rollupCfg2 .L2ChainID ): {ChainIndex : chainB , ActivationTime : 0 , HistoryMinTime : 0 },
55
- })
68
+
69
+ var ds * depset.StaticConfigDependencySet
70
+ if chainSetupOpts .expiryWindow > 0 {
71
+ ds , _ = depset .NewStaticConfigDependencySetWithMessageExpiryOverride (map [eth.ChainID ]* depset.StaticConfigDependency {
72
+ eth .ChainIDFromBig (rollupCfg1 .L2ChainID ): {ChainIndex : chainA , ActivationTime : 0 , HistoryMinTime : 0 },
73
+ eth .ChainIDFromBig (rollupCfg2 .L2ChainID ): {ChainIndex : chainB , ActivationTime : 0 , HistoryMinTime : 0 },
74
+ }, chainSetupOpts .expiryWindow )
75
+ } else {
76
+ ds , _ = depset .NewStaticConfigDependencySet (map [eth.ChainID ]* depset.StaticConfigDependency {
77
+ eth .ChainIDFromBig (rollupCfg1 .L2ChainID ): {ChainIndex : chainA , ActivationTime : 0 , HistoryMinTime : 0 },
78
+ eth .ChainIDFromBig (rollupCfg2 .L2ChainID ): {ChainIndex : chainB , ActivationTime : 0 , HistoryMinTime : 0 },
79
+ })
80
+ }
56
81
configSource := & staticConfigSource {
57
82
rollupCfgs : []* rollup.Config {rollupCfg1 , & rollupCfg2 },
58
83
chainConfigs : []* params.ChainConfig {chainCfg1 , & chainCfg2 },
59
- depset : depset ,
84
+ depset : ds ,
60
85
}
61
86
tasksStub := & stubTasks {
62
87
l2SafeHead : eth.L2BlockRef {Number : 918429823450218 }, // Past the claimed block
@@ -577,6 +602,94 @@ func TestPanicIfAgreedPrestateIsAfterGameTimestamp(t *testing.T) {
577
602
})
578
603
}
579
604
605
+ func TestHazardSet_ExpiredMessageShortCircuitsInclusionCheck (t * testing.T ) {
606
+ // This test is also covered by safe_update_test.go in op-supervisor.
607
+ // However, since this short-circuit behavior is critical for fault proofs, we doubly assert the desired behavior here to prevent a regression.
608
+
609
+ runTest := func (t * testing.T , expiryWindow uint64 , expectInclusionCheck bool ) {
610
+ logger := testlog .Logger (t , log .LevelError )
611
+ configSource , agreedSuperRoot , tasksStub := setupTwoChains (WithExpiryWindow (expiryWindow ))
612
+ defer tasksStub .AssertExpectations (t )
613
+ rng := rand .New (rand .NewSource (123 ))
614
+
615
+ configA := configSource .rollupCfgs [0 ]
616
+ configB := configSource .rollupCfgs [1 ]
617
+
618
+ initLog := & gethTypes.Log {Address : initiatingMessageOrigin , Topics : []common.Hash {initiatingMessageTopic }}
619
+ block1A , _ := createBlock (rng , configA , 1 , gethTypes.Receipts {{Logs : []* gethTypes.Log {initLog }}})
620
+
621
+ exec := interoptypes.Message {
622
+ Identifier : interoptypes.Identifier {
623
+ Origin : initiatingMessageOrigin ,
624
+ BlockNumber : 1 ,
625
+ Timestamp : block1A .Time (),
626
+ ChainID : uint256 .Int (eth .ChainIDFromBig (configB .L2ChainID )),
627
+ },
628
+ PayloadHash : initPayloadHash ,
629
+ }
630
+ logA := convertExecutingMessageToLog (t , exec )
631
+ block2A , block2AReceipts := createBlock (rng , configA , 2 , gethTypes.Receipts {{Logs : []* gethTypes.Log {logA }}})
632
+ block2B , block2BReceipts := createBlock (rng , configB , 2 , nil )
633
+
634
+ pendingOutputs := [2 ]* eth.OutputV0 {0 : createOutput (block2A .Hash ()), 1 : createOutput (block2B .Hash ())}
635
+ transitionState := & types.TransitionState {
636
+ SuperRoot : agreedSuperRoot .Marshal (),
637
+ PendingProgress : []types.OptimisticBlock {
638
+ {BlockHash : block2A .Hash (), OutputRoot : eth .OutputRoot (pendingOutputs [0 ])},
639
+ {BlockHash : block2B .Hash (), OutputRoot : eth .OutputRoot (pendingOutputs [1 ])},
640
+ },
641
+ Step : ConsolidateStep ,
642
+ }
643
+ l2PreimageOracle , _ := test .NewStubOracle (t )
644
+ l2PreimageOracle .Blocks [block2A .Hash ()] = block2A
645
+ l2PreimageOracle .Blocks [block2B .Hash ()] = block2B
646
+ l2PreimageOracle .Receipts [block2A .Hash ()] = block2AReceipts
647
+ l2PreimageOracle .Receipts [block2B .Hash ()] = block2BReceipts
648
+
649
+ consolidateState := newConsolidateState (transitionState )
650
+ consolidateDeps , err := newConsolidateCheckDeps (configSource .depset , configSource , transitionState , agreedSuperRoot .Chains , l2PreimageOracle , consolidateState )
651
+ require .NoError (t , err )
652
+
653
+ mockConsolidateDeps := & mockConsolidateDeps {consolidateCheckDeps : consolidateDeps }
654
+ mockConsolidateDeps .
655
+ On ("Contains" , mock .Anything , mock .Anything ).Return (supervisortypes.BlockSeal {}, supervisortypes .ErrConflict ).
656
+ Maybe ()
657
+
658
+ deps := & cross.UnsafeHazardDeps {UnsafeStartDeps : mockConsolidateDeps }
659
+ candidate := supervisortypes.BlockSeal {
660
+ Hash : block2A .Hash (),
661
+ Number : block2A .NumberU64 (),
662
+ Timestamp : block2A .Time (),
663
+ }
664
+ _ , err = cross .NewHazardSet (deps , logger , eth .ChainIDFromBig (configA .L2ChainID ), candidate )
665
+ require .ErrorIs (t , err , supervisortypes .ErrConflict )
666
+
667
+ if expectInclusionCheck {
668
+ mockConsolidateDeps .AssertCalled (t , "Contains" , mock .Anything , mock .Anything )
669
+ } else {
670
+ mockConsolidateDeps .AssertNotCalled (t , "Contains" , mock .Anything , mock .Anything )
671
+ }
672
+ mockConsolidateDeps .AssertExpectations (t )
673
+ }
674
+
675
+ t .Run ("expired message short-circuits inclusion check" , func (t * testing.T ) {
676
+ runTest (t , 1 , false )
677
+ })
678
+ t .Run ("message not expired does not short-circuit inclusion check" , func (t * testing.T ) {
679
+ runTest (t , 2 , true )
680
+ })
681
+ }
682
+
683
+ type mockConsolidateDeps struct {
684
+ mock.Mock
685
+ * consolidateCheckDeps
686
+ }
687
+
688
+ func (m * mockConsolidateDeps ) Contains (chainID eth.ChainID , query supervisortypes.ContainsQuery ) (supervisortypes.BlockSeal , error ) {
689
+ out := m .Mock .Called (chainID , query )
690
+ return out .Get (0 ).(supervisortypes.BlockSeal ), out .Error (1 )
691
+ }
692
+
580
693
func verifyResult (t * testing.T , logger log.Logger , tasks * stubTasks , configSource * staticConfigSource , l2PreimageOracle * test.StubBlockOracle , agreedPrestate common.Hash , gameTimestamp uint64 , expectedClaim common.Hash ) {
581
694
bootInfo := & boot.BootInfoInterop {
582
695
AgreedPrestate : agreedPrestate ,
0 commit comments