From e722f9cc89e4060ea82b3b71fe22cdafa3e1b630 Mon Sep 17 00:00:00 2001 From: inphi Date: Thu, 18 Dec 2025 11:12:08 -0500 Subject: [PATCH] op-e2e: Fix super proposals in super DG tests --- op-e2e/e2eutils/disputegame/helper.go | 89 +++++++++++++++++++-------- op-e2e/faultproofs/super_test.go | 36 ++++++----- 2 files changed, 84 insertions(+), 41 deletions(-) diff --git a/op-e2e/e2eutils/disputegame/helper.go b/op-e2e/e2eutils/disputegame/helper.go index e5fb8c6ca3c1a..bca17232095e0 100644 --- a/op-e2e/e2eutils/disputegame/helper.go +++ b/op-e2e/e2eutils/disputegame/helper.go @@ -45,16 +45,17 @@ var ( ) const ( - cannonGameType uint32 = 0 - permissionedGameType uint32 = 1 - superCannonGameType uint32 = 4 - superPermissionedGameType uint32 = 5 - alphabetGameType uint32 = 255 + cannonGameType uint32 = 0 + permissionedGameType uint32 = 1 + superCannonGameType uint32 = 4 + alphabetGameType uint32 = 255 ) type GameCfg struct { - allowFuture bool - allowUnsafe bool + allowFuture bool + allowUnsafe bool + superOutputRoots []eth.Bytes32 + super eth.Super } type GameOpt interface { Apply(cfg *GameCfg) @@ -77,6 +78,20 @@ func WithFutureProposal() GameOpt { }) } +// WithInvalidSuperRoot configures the game to use invalid super output roots. +func WithInvalidSuperRoot() GameOpt { + return gameOptFn(func(c *GameCfg) { + c.superOutputRoots = []eth.Bytes32{{0x01}, {0x02}} + }) +} + +// WithSuper allows specifying a custom super structure. +func WithSuper(super eth.Super) GameOpt { + return gameOptFn(func(c *GameCfg) { + c.super = super + }) +} + type DisputeSystem interface { L1BeaconEndpoint() endpoint.RestHTTP SupervisorClient() *sources.SupervisorClient @@ -225,37 +240,34 @@ func (h *FactoryHelper) StartSuperCannonGameWithCorrectRoot(ctx context.Context, require.NoError(h.T, err) l2Timestamp := b.Time() h.WaitForSuperTimestamp(l2Timestamp, cfg) - output, err := h.System.SupervisorClient().SuperRootAtTimestamp(ctx, hexutil.Uint64(l2Timestamp)) - h.Require.NoErrorf(err, "Failed to get output at timestamp %v", l2Timestamp) - return h.startSuperCannonGameOfType(ctx, l2Timestamp, common.Hash(output.SuperRoot), superCannonGameType, opts...) + return h.startSuperCannonGameOfType(ctx, l2Timestamp, superCannonGameType, opts...) } func (h *FactoryHelper) StartSuperCannonGameWithCorrectRootAtTimestamp(ctx context.Context, l2Timestamp uint64, opts ...GameOpt) *SuperCannonGameHelper { cfg := NewGameCfg(opts...) h.WaitForSuperTimestamp(l2Timestamp, cfg) - output, err := h.System.SupervisorClient().SuperRootAtTimestamp(ctx, hexutil.Uint64(l2Timestamp)) - h.Require.NoErrorf(err, "Failed to get output at timestamp %v", l2Timestamp) - return h.startSuperCannonGameOfType(ctx, l2Timestamp, common.Hash(output.SuperRoot), superCannonGameType, opts...) + return h.startSuperCannonGameOfType(ctx, l2Timestamp, superCannonGameType, opts...) } -func (h *FactoryHelper) StartSuperCannonGame(ctx context.Context, rootClaim common.Hash, opts ...GameOpt) *SuperCannonGameHelper { +func (h *FactoryHelper) StartSuperCannonGame(ctx context.Context, opts ...GameOpt) *SuperCannonGameHelper { // Can't create a game at L1 genesis! require.NoError(h.T, wait.ForBlock(ctx, h.Client, 1)) b, err := h.Client.BlockByNumber(ctx, nil) require.NoError(h.T, err) - return h.startSuperCannonGameOfType(ctx, b.Time(), rootClaim, superCannonGameType, opts...) + return h.startSuperCannonGameOfType(ctx, b.Time(), superCannonGameType, opts...) } -func (h *FactoryHelper) StartSuperCannonGameAtTimestamp(ctx context.Context, timestamp uint64, rootClaim common.Hash, opts ...GameOpt) *SuperCannonGameHelper { - return h.startSuperCannonGameOfType(ctx, timestamp, rootClaim, superCannonGameType, opts...) +func (h *FactoryHelper) StartSuperCannonGameAtTimestamp(ctx context.Context, timestamp uint64, opts ...GameOpt) *SuperCannonGameHelper { + return h.startSuperCannonGameOfType(ctx, timestamp, superCannonGameType, opts...) } -func (h *FactoryHelper) startSuperCannonGameOfType(ctx context.Context, timestamp uint64, rootClaim common.Hash, gameType uint32, opts ...GameOpt) *SuperCannonGameHelper { +func (h *FactoryHelper) startSuperCannonGameOfType(ctx context.Context, timestamp uint64, gameType uint32, opts ...GameOpt) *SuperCannonGameHelper { cfg := NewGameCfg(opts...) logger := testlog.Logger(h.T, log.LevelInfo).New("role", "CannonGameHelper") rootProvider := h.System.SupervisorClient() - extraData := h.CreateSuperGameExtraData(ctx, rootProvider, timestamp, cfg) + extraData := h.createSuperGameExtraData(ctx, rootProvider, timestamp, cfg) + rootClaim := crypto.Keccak256Hash(extraData) ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) defer cancel() @@ -348,7 +360,7 @@ func (h *FactoryHelper) CreateBisectionGameExtraData(l2Node string, l2BlockNumbe return extraData } -func (h *FactoryHelper) CreateSuperGameExtraData(ctx context.Context, supervisor *sources.SupervisorClient, timestamp uint64, cfg *GameCfg) []byte { +func (h *FactoryHelper) createSuperGameExtraData(ctx context.Context, supervisor *sources.SupervisorClient, timestamp uint64, cfg *GameCfg) []byte { if !cfg.allowFuture { timedCtx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() @@ -361,10 +373,26 @@ func (h *FactoryHelper) CreateSuperGameExtraData(ctx context.Context, supervisor }) require.NoError(h.T, err, "Safe head did not reach proposal timestamp") } - h.T.Logf("Creating game with l2 timestamp: %v", timestamp) - extraData := make([]byte, 32) - binary.BigEndian.PutUint64(extraData[24:], timestamp) - return extraData + + super := cfg.super + if super == nil { + h.T.Logf("Creating game with l2 timestamp: %v", timestamp) + superResponse, err := h.System.SupervisorClient().SuperRootAtTimestamp(ctx, hexutil.Uint64(timestamp)) + h.Require.NoErrorf(err, "Failed to get super root at timestamp %v", timestamp) + super, err = superResponse.ToSuper() + h.Require.NoErrorf(err, "Failed to parse super at timestamp %v", timestamp) + } + + superV1, ok := super.(*eth.SuperV1) + h.Require.Truef(ok, "Unsupported super type %T", super) + superV1.Timestamp = timestamp // override in case it's different from the game timestamp + if len(cfg.superOutputRoots) != 0 { + h.Require.Len(cfg.superOutputRoots, len(superV1.Chains), "Super output roots length mismatch") + for i := range superV1.Chains { + superV1.Chains[i].Output = cfg.superOutputRoots[i] + } + } + return superV1.Marshal() } func (h *FactoryHelper) WaitForBlock(l2Node string, l2BlockNumber uint64, cfg *GameCfg) { @@ -442,3 +470,16 @@ func (h *FactoryHelper) StartChallenger(ctx context.Context, name string, option }) return c } + +// CreateInvalidSuper creates a SuperV1 with invalid outputs +func CreateInvalidSuper(timestamp uint64) *eth.SuperV1 { + return ð.SuperV1{ + Timestamp: timestamp, + Chains: []eth.ChainIDAndOutput{ + { + ChainID: eth.ChainIDFromUInt64(1), + Output: eth.Bytes32{0x01}, + }, + }, + } +} diff --git a/op-e2e/faultproofs/super_test.go b/op-e2e/faultproofs/super_test.go index 0944f0d97dced..b739eb1a90712 100644 --- a/op-e2e/faultproofs/super_test.go +++ b/op-e2e/faultproofs/super_test.go @@ -31,7 +31,7 @@ func TestCreateSuperCannonGame(t *testing.T) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) sys.L2IDs() - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) game.LogGameData(ctx) }) } @@ -40,7 +40,7 @@ func TestSuperCannonGame(t *testing.T) { RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) testCannonGame(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) }) } @@ -49,7 +49,7 @@ func TestSuperCannonGame_WithBlobs(t *testing.T) { RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType), WithBlobBatches()) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) testCannonGame(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) }) } @@ -58,7 +58,7 @@ func TestSuperCannonGame_ChallengeAllZeroClaim(t *testing.T) { RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) testCannonChallengeAllZeroClaim(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) }, WithNextVMOnly[any]()) } @@ -88,7 +88,7 @@ func TestSuperCannonPublishCannonRootClaim(t *testing.T) { require.NoError(t, err) disputeL2SequenceNumber := b.Time() + test.disputeL2SequenceNumberOffset - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, disputeL2SequenceNumber, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, disputeL2SequenceNumber, disputegame.WithInvalidSuperRoot()) game.DisputeLastBlock(ctx) game.LogGameData(ctx) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) @@ -140,7 +140,7 @@ func TestSuperCannonDisputeGame(t *testing.T) { RunTestsAcrossVmTypes(t, tests, func(t *testing.T, allocType config.AllocType, test TestCase) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01, 0xaa}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) game.LogGameData(ctx) disputeClaim := game.DisputeLastBlock(ctx) @@ -180,7 +180,7 @@ func TestSuperCannonDefendStep(t *testing.T) { RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) testCannonDefendStep(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) }, WithNextVMOnly[any]()) } @@ -300,7 +300,7 @@ func TestSuperCannonPoisonedPostState(t *testing.T) { RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) testCannonPoisonedPostState(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) }, WithNextVMOnly[any]()) } @@ -318,7 +318,7 @@ func TestSuperCannonRootBeyondProposedBlock_InvalidRoot(t *testing.T) { RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) testDisputeRootBeyondProposedBlockInvalidOutputRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) }, WithNextVMOnly[any]()) } @@ -327,7 +327,7 @@ func TestSuperCannonRootChangeClaimedRoot(t *testing.T) { RunTestAcrossVmTypes(t, func(t *testing.T, allocType config.AllocType) { ctx := context.Background() sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) - game := disputeGameFactory.StartSuperCannonGame(ctx, common.Hash{0x01}) + game := disputeGameFactory.StartSuperCannonGame(ctx, disputegame.WithInvalidSuperRoot()) testDisputeRootChangeClaimedRoot(t, ctx, createSuperGameArena(t, sys, game), &game.SplitGameHelper) }, WithNextVMOnly[any]()) } @@ -387,8 +387,7 @@ func TestSuperInvalidateUnsafeProposal(t *testing.T) { // Root claim is _dishonest_ because the required data is not available on L1 unsafeSuper := createSuperRoot(t, ctx, sys, unsafeTimestamp) - unsafeRoot := eth.SuperRoot(unsafeSuper) - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, unsafeTimestamp, common.Hash(unsafeRoot), disputegame.WithFutureProposal()) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, unsafeTimestamp, disputegame.WithSuper(unsafeSuper), disputegame.WithFutureProposal()) correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) @@ -446,8 +445,7 @@ func TestSuperInalidateUnsafeProposal_SecondChainIsUnsafe(t *testing.T) { // Root claim is _dishonest_ because the required data to construct the chain B output root is not available on L1 unsafeSuper := createSuperRoot(t, ctx, sys, gameTimestamp) - unsafeRoot := eth.SuperRoot(unsafeSuper) - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, gameTimestamp, common.Hash(unsafeRoot), disputegame.WithFutureProposal()) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, gameTimestamp, disputegame.WithSuper(unsafeSuper), disputegame.WithFutureProposal()) prestateTimestamp, _, err := game.Game.GetGameRange(ctx) require.NoError(t, err, "Failed to get game range") @@ -501,7 +499,9 @@ func TestSuperInvalidateProposalForFutureBlock(t *testing.T) { sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(allocType)) // Root claim is _dishonest_ because the required data is not available on L1 farFutureTimestamp := time.Now().Add(time.Second * 10_000_000).Unix() - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, uint64(farFutureTimestamp), common.Hash{0x01}, disputegame.WithFutureProposal()) + invalidSuper := disputegame.CreateInvalidSuper(uint64(farFutureTimestamp)) + // manually create an invalid super since we can't rely on the super RPC to fetch one at the far timestamp + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, uint64(farFutureTimestamp), disputegame.WithSuper(invalidSuper), disputegame.WithFutureProposal()) correctTrace := game.CreateHonestActor(ctx, disputegame.WithPrivKey(malloryKey(t)), func(c *disputegame.HonestActorConfig) { c.ChallengerOpts = append(c.ChallengerOpts, challenger.WithDepset(t, sys.DependencySet())) }) @@ -567,6 +567,8 @@ func TestSuperInvalidateCorrectProposalFutureBlock(t *testing.T) { require.NoError(t, err, "Failed to get sync status") superRoot, err := client.SuperRootAtTimestamp(ctx, hexutil.Uint64(status.SafeTimestamp)) require.NoError(t, err, "Failed to get super root at safe timestamp") + super, err := superRoot.ToSuper() + require.NoError(t, err, "Failed to parse super root") // Stop the batcher so the safe head doesn't advance for _, id := range sys.L2IDs() { @@ -575,7 +577,7 @@ func TestSuperInvalidateCorrectProposalFutureBlock(t *testing.T) { // Create a dispute game with a proposal that is valid at `superRoot.Timestamp`, but that claims to correspond to timestamp // `superRoot.Timestamp + 100000`. This is dishonest, because the superchain hasn't reached this timestamp yet. - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, superRoot.Timestamp+100_000, common.Hash(superRoot.SuperRoot), disputegame.WithFutureProposal()) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, superRoot.Timestamp+100_000, disputegame.WithSuper(super), disputegame.WithFutureProposal()) game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(aliceKey(t)), challenger.WithDepset(t, sys.DependencySet())) @@ -673,7 +675,7 @@ func TestSuperCannonHonestSafeTraceExtensionInvalidRoot(t *testing.T) { disputeGameFactory.WaitForSuperTimestamp(safeTimestamp, new(disputegame.GameCfg)) - game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, safeTimestamp-1, common.Hash{0xCA, 0xFE}) + game := disputeGameFactory.StartSuperCannonGameAtTimestamp(ctx, safeTimestamp-1, disputegame.WithInvalidSuperRoot()) require.NotNil(t, game) // Create a correct trace actor with an honest trace extending to safeTimestamp