From 12a38d0d1745f0986d59d0dbbd438e1a4f36a271 Mon Sep 17 00:00:00 2001
From: Adrian Sutton <adrian@oplabs.co>
Date: Thu, 26 Sep 2024 11:00:48 +1000
Subject: [PATCH] op-challenger: Skip prestate verifications for the
 permissioned game. (#12140)

Simplifies deployment with minimal risk given that only permissioned actors are involved in the game and typically the challenger is only resolving games.
---
 op-challenger/game/fault/register_task.go | 16 ++++++---
 op-e2e/e2eutils/challenger/helper.go      | 21 +++++++++++-
 op-e2e/e2eutils/disputegame/helper.go     | 40 ++++++++++++++++++-----
 op-e2e/faultproofs/permissioned_test.go   | 35 ++++++++++++++++++++
 4 files changed, 99 insertions(+), 13 deletions(-)
 create mode 100644 op-e2e/faultproofs/permissioned_test.go

diff --git a/op-challenger/game/fault/register_task.go b/op-challenger/game/fault/register_task.go
index 3b438ad8e..dd346f420 100644
--- a/op-challenger/game/fault/register_task.go
+++ b/op-challenger/game/fault/register_task.go
@@ -30,7 +30,8 @@ import (
 )
 
 type RegisterTask struct {
-	gameType faultTypes.GameType
+	gameType               faultTypes.GameType
+	skipPrestateValidation bool
 
 	getPrestateProvider func(prestateHash common.Hash) (faultTypes.PrestateProvider, error)
 	newTraceAccessor    func(
@@ -51,6 +52,10 @@ func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m c
 	stateConverter := cannon.NewStateConverter()
 	return &RegisterTask{
 		gameType: gameType,
+		// Don't validate the absolute prestate or genesis output root for permissioned games
+		// Only trusted actors participate in these games so they aren't expected to reach the step() call and
+		// are often configured without valid prestates but the challenger should still resolve the games.
+		skipPrestateValidation: gameType == faultTypes.PermissionedGameType,
 		getPrestateProvider: cachePrestates(
 			gameType,
 			stateConverter,
@@ -244,9 +249,12 @@ func (e *RegisterTask) Register(
 			}
 			return accessor, nil
 		}
-		prestateValidator := NewPrestateValidator(e.gameType.String(), contract.GetAbsolutePrestateHash, vmPrestateProvider)
-		startingValidator := NewPrestateValidator("output root", contract.GetStartingRootHash, prestateProvider)
-		return NewGamePlayer(ctx, systemClock, l1Clock, logger, m, dir, game.Proxy, txSender, contract, syncValidator, []Validator{prestateValidator, startingValidator}, creator, l1HeaderSource, selective, claimants)
+		var validators []Validator
+		if !e.skipPrestateValidation {
+			validators = append(validators, NewPrestateValidator(e.gameType.String(), contract.GetAbsolutePrestateHash, vmPrestateProvider))
+			validators = append(validators, NewPrestateValidator("output root", contract.GetStartingRootHash, prestateProvider))
+		}
+		return NewGamePlayer(ctx, systemClock, l1Clock, logger, m, dir, game.Proxy, txSender, contract, syncValidator, validators, creator, l1HeaderSource, selective, claimants)
 	}
 	err := registerOracle(ctx, m, oracles, gameFactory, caller, e.gameType)
 	if err != nil {
diff --git a/op-e2e/e2eutils/challenger/helper.go b/op-e2e/e2eutils/challenger/helper.go
index 87a51d96a..177fd90a9 100644
--- a/op-e2e/e2eutils/challenger/helper.go
+++ b/op-e2e/e2eutils/challenger/helper.go
@@ -58,7 +58,7 @@ func NewHelper(log log.Logger, t *testing.T, require *require.Assertions, dir st
 	}
 }
 
-type Option func(config2 *config.Config)
+type Option func(c *config.Config)
 
 func WithFactoryAddress(addr common.Address) Option {
 	return func(c *config.Config) {
@@ -84,6 +84,18 @@ func WithPollInterval(pollInterval time.Duration) Option {
 	}
 }
 
+func WithValidPrestateRequired() Option {
+	return func(c *config.Config) {
+		c.AllowInvalidPrestate = false
+	}
+}
+
+func WithInvalidCannonPrestate() Option {
+	return func(c *config.Config) {
+		c.CannonAbsolutePreState = "/tmp/not-a-real-prestate.foo"
+	}
+}
+
 // FindMonorepoRoot finds the relative path to the monorepo root
 // Different tests might be nested in subdirectories of the op-e2e dir.
 func FindMonorepoRoot(t *testing.T) string {
@@ -136,6 +148,13 @@ func WithCannon(t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis)
 	}
 }
 
+func WithPermissioned(t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis) Option {
+	return func(c *config.Config) {
+		c.TraceTypes = append(c.TraceTypes, types.TraceTypePermissioned)
+		applyCannonConfig(c, t, rollupCfg, l2Genesis)
+	}
+}
+
 func WithAlphabet() Option {
 	return func(c *config.Config) {
 		c.TraceTypes = append(c.TraceTypes, types.TraceTypeAlphabet)
diff --git a/op-e2e/e2eutils/disputegame/helper.go b/op-e2e/e2eutils/disputegame/helper.go
index 972314c4c..7651d0941 100644
--- a/op-e2e/e2eutils/disputegame/helper.go
+++ b/op-e2e/e2eutils/disputegame/helper.go
@@ -41,8 +41,9 @@ var (
 )
 
 const (
-	cannonGameType   uint32 = 0
-	alphabetGameType uint32 = 255
+	cannonGameType       uint32 = 0
+	permissionedGameType uint32 = 1
+	alphabetGameType     uint32 = 255
 )
 
 type GameCfg struct {
@@ -95,13 +96,28 @@ type FactoryHelper struct {
 	Factory     *bindings.DisputeGameFactory
 }
 
-func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem) *FactoryHelper {
+type FactoryCfg struct {
+	PrivKey *ecdsa.PrivateKey
+}
+
+type FactoryOption func(c *FactoryCfg)
+
+func WithFactoryPrivKey(privKey *ecdsa.PrivateKey) FactoryOption {
+	return func(c *FactoryCfg) {
+		c.PrivKey = privKey
+	}
+}
+
+func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem, opts ...FactoryOption) *FactoryHelper {
 	require := require.New(t)
 	client := system.NodeClient("l1")
 	chainID, err := client.ChainID(ctx)
 	require.NoError(err)
-	privKey := TestKey
-	opts, err := bind.NewKeyedTransactorWithChainID(privKey, chainID)
+	factoryCfg := &FactoryCfg{PrivKey: TestKey}
+	for _, opt := range opts {
+		opt(factoryCfg)
+	}
+	txOpts, err := bind.NewKeyedTransactorWithChainID(factoryCfg.PrivKey, chainID)
 	require.NoError(err)
 
 	l1Deployments := system.L1Deployments()
@@ -114,8 +130,8 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem) *
 		Require:     require,
 		System:      system,
 		Client:      client,
-		Opts:        opts,
-		PrivKey:     privKey,
+		Opts:        txOpts,
+		PrivKey:     factoryCfg.PrivKey,
 		Factory:     factory,
 		FactoryAddr: factoryAddr,
 	}
@@ -152,6 +168,14 @@ func (h *FactoryHelper) StartOutputCannonGameWithCorrectRoot(ctx context.Context
 }
 
 func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash, opts ...GameOpt) *OutputCannonGameHelper {
+	return h.startOutputCannonGameOfType(ctx, l2Node, l2BlockNumber, rootClaim, cannonGameType, opts...)
+}
+
+func (h *FactoryHelper) StartPermissionedGame(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash, opts ...GameOpt) *OutputCannonGameHelper {
+	return h.startOutputCannonGameOfType(ctx, l2Node, l2BlockNumber, rootClaim, permissionedGameType, opts...)
+}
+
+func (h *FactoryHelper) startOutputCannonGameOfType(ctx context.Context, l2Node string, l2BlockNumber uint64, rootClaim common.Hash, gameType uint32, opts ...GameOpt) *OutputCannonGameHelper {
 	cfg := NewGameCfg(opts...)
 	logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputCannonGameHelper")
 	rollupClient := h.System.RollupClient(l2Node)
@@ -163,7 +187,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string
 	defer cancel()
 
 	tx, err := transactions.PadGasEstimate(h.Opts, 2, func(opts *bind.TransactOpts) (*types.Transaction, error) {
-		return h.Factory.Create(opts, cannonGameType, rootClaim, extraData)
+		return h.Factory.Create(opts, gameType, rootClaim, extraData)
 	})
 	h.Require.NoError(err, "create fault dispute game")
 	rcpt, err := wait.ForReceiptOK(ctx, h.Client, tx.Hash())
diff --git a/op-e2e/faultproofs/permissioned_test.go b/op-e2e/faultproofs/permissioned_test.go
new file mode 100644
index 000000000..09c4646fe
--- /dev/null
+++ b/op-e2e/faultproofs/permissioned_test.go
@@ -0,0 +1,35 @@
+package faultproofs
+
+import (
+	"context"
+	"testing"
+
+	op_e2e "github.com/ethereum-optimism/optimism/op-e2e"
+
+	"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
+	"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame"
+	"github.com/ethereum/go-ethereum/common"
+)
+
+func TestPermissionedGameType(t *testing.T) {
+	op_e2e.InitParallel(t, op_e2e.UsesCannon)
+
+	ctx := context.Background()
+	sys, _ := StartFaultDisputeSystem(t)
+	t.Cleanup(sys.Close)
+
+	gameFactory := disputegame.NewFactoryHelper(t, ctx, sys, disputegame.WithFactoryPrivKey(sys.Cfg.Secrets.Proposer))
+
+	game := gameFactory.StartPermissionedGame(ctx, "sequencer", 1, common.Hash{0x01, 0xaa})
+
+	// Start a challenger with both cannon and alphabet support
+	gameFactory.StartChallenger(ctx, "TowerDefense",
+		challenger.WithValidPrestateRequired(),
+		challenger.WithInvalidCannonPrestate(),
+		challenger.WithPermissioned(t, sys.RollupConfig, sys.L2GenesisCfg),
+		challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
+	)
+
+	// Wait for the challenger to respond
+	game.RootClaim(ctx).WaitForCounterClaim(ctx)
+}
-- 
2.23.0