diff --git a/op-challenger/game/fault/register_task.go b/op-challenger/game/fault/register_task.go index b1921d5a46be4..3290453fe076f 100644 --- a/op-challenger/game/fault/register_task.go +++ b/op-challenger/game/fault/register_task.go @@ -78,6 +78,9 @@ func newSuperCannonVMRegisterTaskWithConfig( syncValidator: syncValidator, skipPrestateValidation: gameType == gameTypes.SuperPermissionedGameType, getTopPrestateProvider: func(ctx context.Context, prestateTimestamp uint64) (faultTypes.PrestateProvider, error) { + if superNodeProvider != nil { + return super.NewSuperNodePrestateProvider(superNodeProvider, prestateTimestamp), nil + } return super.NewSuperRootPrestateProvider(rootProvider, prestateTimestamp), nil }, getBottomPrestateProvider: cachePrestates( @@ -102,7 +105,7 @@ func newSuperCannonVMRegisterTaskWithConfig( poststateBlock uint64) (*trace.Accessor, error) { provider := vmPrestateProvider.(*vm.PrestateProvider) preimagePrestateProvider := prestateProvider.(super.PreimagePrestateProvider) - return super.NewSuperCannonTraceAccessor(logger, m, vmCfg, serverExecutor, preimagePrestateProvider, rootProvider, superNodeProvider, provider.PrestatePath(), dir, l1Head, splitDepth, prestateBlock, poststateBlock) + return super.NewSuperCannonTraceAccessor(logger, m, vmCfg, serverExecutor, preimagePrestateProvider, rootProvider, superNodeProvider, superNodeProvider != nil, provider.PrestatePath(), dir, l1Head, splitDepth, prestateBlock, poststateBlock) }, } } diff --git a/op-challenger/game/fault/trace/super/prestate_supernode.go b/op-challenger/game/fault/trace/super/prestate_supernode.go new file mode 100644 index 0000000000000..d5cd5f552435f --- /dev/null +++ b/op-challenger/game/fault/trace/super/prestate_supernode.go @@ -0,0 +1,42 @@ +package super + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" +) + +type SuperNodePrestateProvider struct { + provider SuperNodeRootProvider + timestamp uint64 +} + +var _ PreimagePrestateProvider = (*SuperNodePrestateProvider)(nil) + +func NewSuperNodePrestateProvider(provider SuperNodeRootProvider, prestateTimestamp uint64) *SuperNodePrestateProvider { + return &SuperNodePrestateProvider{ + provider: provider, + timestamp: prestateTimestamp, + } +} + +func (s *SuperNodePrestateProvider) AbsolutePreStateCommitment(ctx context.Context) (common.Hash, error) { + prestate, err := s.AbsolutePreState(ctx) + if err != nil { + return common.Hash{}, err + } + return common.Hash(eth.SuperRoot(prestate)), nil +} + +func (s *SuperNodePrestateProvider) AbsolutePreState(ctx context.Context) (eth.Super, error) { + response, err := s.provider.SuperRootAtTimestamp(ctx, s.timestamp) + if err != nil { + return nil, err + } + if response.Data == nil { + return nil, ethereum.NotFound + } + return response.Data.Super, nil +} diff --git a/op-challenger/game/fault/trace/super/prestate_supernode_test.go b/op-challenger/game/fault/trace/super/prestate_supernode_test.go new file mode 100644 index 0000000000000..df98def0b54e4 --- /dev/null +++ b/op-challenger/game/fault/trace/super/prestate_supernode_test.go @@ -0,0 +1,67 @@ +package super + +import ( + "context" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestAbsolutePreState_SuperNode(t *testing.T) { + t.Run("FailedToFetchOutput", func(t *testing.T) { + rootProvider := &stubSuperNodeRootProvider{} + provider := NewSuperNodePrestateProvider(rootProvider, 100) + + _, err := provider.AbsolutePreState(context.Background()) + require.Error(t, err) + require.NotErrorIs(t, err, ethereum.NotFound, "The API shouldn't return not found, it returns a response with no data instead") + + _, err = provider.AbsolutePreStateCommitment(context.Background()) + require.Error(t, err) + require.NotErrorIs(t, err, ethereum.NotFound, "The API shouldn't return not found, it returns a response with no data instead") + }) + + t.Run("NoDataResponse", func(t *testing.T) { + rootProvider := &stubSuperNodeRootProvider{} + rootProvider.AddAtTimestamp(100, eth.SuperRootAtTimestampResponse{ + Data: nil, + }) + provider := NewSuperNodePrestateProvider(rootProvider, 100) + + _, err := provider.AbsolutePreState(context.Background()) + require.ErrorIs(t, err, ethereum.NotFound) + + _, err = provider.AbsolutePreStateCommitment(context.Background()) + require.ErrorIs(t, err, ethereum.NotFound) + }) + + t.Run("ReturnsSuperRootForTimestamp", func(t *testing.T) { + expectedPreimage := eth.NewSuperV1(100, + eth.ChainIDAndOutput{ + ChainID: eth.ChainID{2987}, + Output: eth.Bytes32{0x88}, + }, eth.ChainIDAndOutput{ + ChainID: eth.ChainID{100}, + Output: eth.Bytes32{0x10}, + }) + rootProvider := &stubSuperNodeRootProvider{} + rootProvider.AddAtTimestamp(100, eth.SuperRootAtTimestampResponse{ + Data: ð.SuperRootResponseData{ + Super: expectedPreimage, + SuperRoot: eth.SuperRoot(expectedPreimage), + }, + }) + provider := NewSuperNodePrestateProvider(rootProvider, 100) + + preimage, err := provider.AbsolutePreState(context.Background()) + require.NoError(t, err) + require.Equal(t, expectedPreimage, preimage) + + commitment, err := provider.AbsolutePreStateCommitment(context.Background()) + require.NoError(t, err) + require.Equal(t, common.Hash(eth.SuperRoot(expectedPreimage)), commitment) + }) +} diff --git a/op-challenger/game/fault/trace/super/prestate.go b/op-challenger/game/fault/trace/super/prestate_supervisor.go similarity index 100% rename from op-challenger/game/fault/trace/super/prestate.go rename to op-challenger/game/fault/trace/super/prestate_supervisor.go diff --git a/op-challenger/game/fault/trace/super/prestate_test.go b/op-challenger/game/fault/trace/super/prestate_supervisor_test.go similarity index 100% rename from op-challenger/game/fault/trace/super/prestate_test.go rename to op-challenger/game/fault/trace/super/prestate_supervisor_test.go diff --git a/op-challenger/game/fault/trace/super/provider_supervisor.go b/op-challenger/game/fault/trace/super/provider_supervisor.go index 4769c662e6bc3..89f67ad31c843 100644 --- a/op-challenger/game/fault/trace/super/provider_supervisor.go +++ b/op-challenger/game/fault/trace/super/provider_supervisor.go @@ -9,7 +9,6 @@ import ( interopTypes "github.com/ethereum-optimism/optimism/op-program/client/interop/types" "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -54,16 +53,6 @@ type SupervisorSuperTraceProvider struct { gameDepth types.Depth } -func NewSuperTraceProvider(logger log.Logger, rollupCfgs *RollupConfigs, prestateProvider PreimagePrestateProvider, rootProvider *sources.SupervisorClient, superNodeProvider *sources.SuperNodeClient, l1Head eth.BlockID, gameDepth types.Depth, prestateTimestamp, poststateTimestamp uint64) SuperTraceProvider { - if rootProvider == nil && superNodeProvider != nil { - return NewSuperNodeTraceProvider(logger, prestateProvider, superNodeProvider, l1Head, gameDepth, prestateTimestamp, poststateTimestamp) - } else if rootProvider != nil && superNodeProvider == nil { - return NewSupervisorSuperTraceProvider(logger, rollupCfgs, prestateProvider, rootProvider, l1Head, gameDepth, prestateTimestamp, poststateTimestamp) - } else { - panic(fmt.Sprintf("Invalid configuration: must provide either a super node provider or a root provider, but not both. Root provider: %v, SuperNodeProvider: %v", rootProvider, superNodeProvider)) - } -} - func NewSupervisorSuperTraceProvider(logger log.Logger, rollupCfgs *RollupConfigs, prestateProvider PreimagePrestateProvider, rootProvider RootProvider, l1Head eth.BlockID, gameDepth types.Depth, prestateTimestamp, poststateTimestamp uint64) *SupervisorSuperTraceProvider { return &SupervisorSuperTraceProvider{ logger: logger, diff --git a/op-challenger/game/fault/trace/super/super_cannon.go b/op-challenger/game/fault/trace/super/super_cannon.go index 9fcc9d634cc90..cc7c0d84dcbf6 100644 --- a/op-challenger/game/fault/trace/super/super_cannon.go +++ b/op-challenger/game/fault/trace/super/super_cannon.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" @@ -26,8 +25,10 @@ func NewSuperCannonTraceAccessor( cfg vm.Config, serverExecutor vm.OracleServerExecutor, prestateProvider PreimagePrestateProvider, - rootProvider *sources.SupervisorClient, - superNodeProvider *sources.SuperNodeClient, + rootProvider RootProvider, + superNodeProvider SuperNodeRootProvider, + // Because go interfaces are a pointer and a type, they are not nil if the type is not nil. So we can't just check if the superNodeProvider != nil because it will have a type and be non-nil even though the pointer is nil. + useSuperNode bool, cannonPrestate string, dir string, l1Head eth.BlockID, @@ -39,7 +40,12 @@ func NewSuperCannonTraceAccessor( if err != nil { return nil, fmt.Errorf("failed to load rollup configs: %w", err) } - outputProvider := NewSuperTraceProvider(logger, rollupCfgs, prestateProvider, rootProvider, superNodeProvider, l1Head, splitDepth, prestateTimestamp, poststateTimestamp) + var outputProvider SuperTraceProvider + if useSuperNode { + outputProvider = NewSuperNodeTraceProvider(logger, prestateProvider, superNodeProvider, l1Head, splitDepth, prestateTimestamp, poststateTimestamp) + } else { + outputProvider = NewSupervisorSuperTraceProvider(logger, rollupCfgs, prestateProvider, rootProvider, l1Head, splitDepth, prestateTimestamp, poststateTimestamp) + } cannonCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, claimInfo ClaimInfo) (types.TraceProvider, error) { logger := logger.New("agreedPrestate", hexutil.Bytes(claimInfo.AgreedPrestate), "claim", claimInfo.Claim, "localContext", localContext) subdir := filepath.Join(dir, localContext.Hex()) diff --git a/op-devstack/dsl/proofs/dispute_game_factory.go b/op-devstack/dsl/proofs/dispute_game_factory.go index 253f7aa7a141b..cadc519bf589b 100644 --- a/op-devstack/dsl/proofs/dispute_game_factory.go +++ b/op-devstack/dsl/proofs/dispute_game_factory.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/prestates" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" @@ -209,7 +210,7 @@ func (f *DisputeGameFactory) startSuperCannonGameOfType(eoa *dsl.EOA, gameType g } game, addr := f.createNewGame(eoa, gameType, rootClaim, extraData) - return NewSuperFaultDisputeGame(f.t, f.require, addr, f.getGameHelper, game) + return NewSuperFaultDisputeGame(f.t, f.require, addr, f.getGameHelper, f.honestTraceForGame, game) } func (f *DisputeGameFactory) createSuperGameExtraData(timestamp uint64, cfg *GameCfg) []byte { @@ -270,6 +271,14 @@ func (f *DisputeGameFactory) honestTraceForGame(game *FaultDisputeGame) challeng f.challengerCfg.CannonKona, vm.NewKonaExecutor(), ) + case gameTypes.SuperCannonGameType: + return f.honestSuperCannonTrace( + game, + f.challengerCfg.CannonAbsolutePreStateBaseURL, + f.challengerCfg.CannonAbsolutePreState, + f.challengerCfg.Cannon, + vm.NewOpProgramServerExecutor(f.log), + ) default: f.require.Truef(false, "Honest trace not supported for game type %v", game.GameType()) return nil @@ -320,6 +329,56 @@ func (f *DisputeGameFactory) honestOutputCannonTrace( return accessor } +func (f *DisputeGameFactory) honestSuperCannonTrace( + game *FaultDisputeGame, + prestateBaseUrl *url.URL, + prestateFile string, + vmConfig vm.Config, + serverExecutor vm.OracleServerExecutor, +) challengerTypes.TraceAccessor { + logger := f.t.Logger().New("role", "honestSuperTrace") + f.require.NotNil(f.superNode, "SuperNode is required to create honest super trace") + + prestateTimestamp := game.StartingL2SequenceNumber() + poststateTimestamp := game.L2SequenceNumber() + + l1HeadHash := game.L1Head() + l1Head, err := f.ethClient.BlockRefByHash(f.t.Ctx(), l1HeadHash) + f.require.NoError(err, "Failed to fetch L1 Head") + + prestateProvider := super.NewSuperNodePrestateProvider(f.superNode.QueryAPI(), prestateTimestamp) + + vmPrestateSource := prestates.NewPrestateSource( + prestateBaseUrl, + prestateFile, + path.Join(f.challengerCfg.Datadir, "test-prestates"), + cannon.NewStateConverter(vmConfig), + ) + vmPrestatePath, err := vmPrestateSource.PrestatePath(f.t.Ctx(), game.absolutePrestate()) + f.require.NoError(err, "Failed to get prestate path") + + accessor, err := super.NewSuperCannonTraceAccessor( + logger, + metrics.NoopMetrics, + vmConfig, + serverExecutor, + prestateProvider, + nil, // supervisor client + f.superNode.QueryAPI(), + true, + vmPrestatePath, + path.Join(f.challengerCfg.Datadir, "test-prestates"), + l1Head.ID(), + game.SplitDepth(), + prestateTimestamp, + poststateTimestamp, + ) + f.require.NoError(err, "Failed to create super cannon trace accessor") + + f.honestTraces[game.Address] = accessor + return accessor +} + func (f *DisputeGameFactory) startOutputRootGameOfType( eoa *dsl.EOA, gameType gameTypes.GameType, diff --git a/op-devstack/dsl/proofs/super_fault_dispute_game.go b/op-devstack/dsl/proofs/super_fault_dispute_game.go index a680aabcfa524..2d741a3731ccf 100644 --- a/op-devstack/dsl/proofs/super_fault_dispute_game.go +++ b/op-devstack/dsl/proofs/super_fault_dispute_game.go @@ -14,12 +14,15 @@ type SuperFaultDisputeGame struct { *FaultDisputeGame } -func NewSuperFaultDisputeGame(t devtest.T, require *require.Assertions, addr common.Address, helperProvider gameHelperProvider, game *bindings.FaultDisputeGame) *SuperFaultDisputeGame { - honestTraceProvider := func(_ *FaultDisputeGame) types.TraceAccessor { - require.Fail("Honest trace not supported for super games") - return nil - } - fdg := NewFaultDisputeGame(t, require, addr, helperProvider, honestTraceProvider, game) +func NewSuperFaultDisputeGame( + t devtest.T, + require *require.Assertions, + addr common.Address, + helperProvider gameHelperProvider, + honestTrace func(game *FaultDisputeGame) types.TraceAccessor, + game *bindings.FaultDisputeGame, +) *SuperFaultDisputeGame { + fdg := NewFaultDisputeGame(t, require, addr, helperProvider, honestTrace, game) return &SuperFaultDisputeGame{ FaultDisputeGame: fdg, } diff --git a/op-e2e/e2eutils/disputegame/super_cannon_helper.go b/op-e2e/e2eutils/disputegame/super_cannon_helper.go index 55347f65b4827..5826efacc7836 100644 --- a/op-e2e/e2eutils/disputegame/super_cannon_helper.go +++ b/op-e2e/e2eutils/disputegame/super_cannon_helper.go @@ -77,6 +77,7 @@ func (g *SuperCannonGameHelper) CreateHonestActor(ctx context.Context, options . prestateProvider, supervisorClient, nil, + false, cfg.CannonAbsolutePreState, dir, l1Head,