diff --git a/.circleci/config.yml b/.circleci/config.yml index 12fd931fcd8..5250db39416 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1257,6 +1257,21 @@ jobs: when: on_fail - notify-failures-on-develop + contracts-bedrock-upload: + machine: true + resource_class: ethereum-optimism/latitude-1 + steps: + - utils/checkout-with-mise: + checkout-method: blobless + - install-contracts-dependencies + - check-changed: + patterns: contracts-bedrock + - get-target-branch + - run: + name: upload selectors + command: just update-selectors + working_directory: packages/contracts-bedrock + contracts-bedrock-checks: docker: - image: <> @@ -1296,6 +1311,8 @@ jobs: command: size-check - run-contracts-check: command: unused-imports-check-no-build + - run-contracts-check: + command: strict-pragma-check-no-build - run-contracts-check: command: validate-spacers-no-build - run-contracts-check: @@ -2597,6 +2614,11 @@ workflows: - contracts-bedrock-build context: - circleci-repo-readonly-authenticated-github-token + - contracts-bedrock-upload: + requires: + - contracts-bedrock-build + context: + - circleci-repo-readonly-authenticated-github-token - diff-fetcher-forge-artifacts: context: - circleci-repo-readonly-authenticated-github-token @@ -3243,7 +3265,7 @@ workflows: - op-acceptance-tests: name: memory-all gate: "" # Empty gate = gateless mode - no_output_timeout: 80m # Keep this less than 90m to avoid CircleCI timeout + no_output_timeout: 120m # Allow longer runs for memory-all gate context: - circleci-repo-readonly-authenticated-github-token - slack diff --git a/.github/workflows/protected.yaml b/.github/workflows/protected.yaml index 4dba40e36b6..020c1fc99cc 100644 --- a/.github/workflows/protected.yaml +++ b/.github/workflows/protected.yaml @@ -61,7 +61,7 @@ jobs: - cannon - op-dripper - op-interop-mon - uses: ethereum-optimism/factory/.github/workflows/docker-bake.yaml@2ff3f9bba03d59a6ad10fdf660ed253f53956188 + uses: ethereum-optimism/factory/.github/workflows/docker-bake.yaml@753bcd4284a6d36eac6e31df2492015ab8650331 with: image_name: ${{ matrix.image_name }} bake_file: docker-bake.hcl @@ -69,6 +69,7 @@ jobs: tag: ${{ needs.prep.outputs.sanitised_ref_name }} gcp_project_id: ${{ vars.GCP_PROJECT_ID_OPLABS_TOOLS_ARTIFACTS }} registry: us-docker.pkg.dev/oplabs-tools-artifacts/oss + attest: true env: | GIT_VERSION=${{ fromJson(needs.prep.outputs.versions)[matrix.image_name] }} KONA_VERSION=${{ needs.prep.outputs.kona_version }} diff --git a/.github/workflows/unprotected.yaml b/.github/workflows/unprotected.yaml index 6fc5e44296c..e8ee041f0aa 100644 --- a/.github/workflows/unprotected.yaml +++ b/.github/workflows/unprotected.yaml @@ -67,14 +67,14 @@ jobs: - cannon - op-dripper - op-interop-mon - uses: ethereum-optimism/factory/.github/workflows/docker-bake.yaml@2ff3f9bba03d59a6ad10fdf660ed253f53956188 + uses: ethereum-optimism/factory/.github/workflows/docker-bake.yaml@753bcd4284a6d36eac6e31df2492015ab8650331 with: image_name: ${{ matrix.image_name }} bake_file: docker-bake.hcl target: ${{ matrix.image_name }} tag: 24h registry: ttl.sh/${{ github.sha }} - push_provenance: false + attest: false env: | GIT_VERSION=${{ fromJson(needs.prep.outputs.versions)[matrix.image_name] }} KONA_VERSION=${{ needs.prep.outputs.kona_version }} diff --git a/op-acceptance-tests/justfile b/op-acceptance-tests/justfile index 266bd7bac28..633894f24d8 100644 --- a/op-acceptance-tests/justfile +++ b/op-acceptance-tests/justfile @@ -101,7 +101,7 @@ acceptance-test devnet="" gate="base": "--validators" "./acceptance-tests.yaml" "--exclude-gates" "flake-shake" "--allow-skips" - "--timeout" "60m" + "--timeout" "120m" "--orchestrator" "$ORCHESTRATOR" "--show-progress" ) diff --git a/op-acceptance-tests/scripts/ci_flake_shake_prepare_slack.sh b/op-acceptance-tests/scripts/ci_flake_shake_prepare_slack.sh index 86007884e6d..a3d28e66ed6 100644 --- a/op-acceptance-tests/scripts/ci_flake_shake_prepare_slack.sh +++ b/op-acceptance-tests/scripts/ci_flake_shake_prepare_slack.sh @@ -81,11 +81,8 @@ if [ -f "$PROMO_JSON" ]; then ($meta.date // "") as $date | ($meta.gate // "flake-shake") as $gate | ($meta.pr_url // "") as $pr_url | - ( if (($meta.flake_gate_tests // 0) == 0) then - [ - {"type":"header","text":{"type":"plain_text","text":":partywizard: Acceptance Tests: Flake-Shake — Gate Empty"}}, - {"type":"section","text":{"type":"mrkdwn","text":"No tests in flake-shake gate; nothing to promote. Artifacts: <\($job)|CircleCI Job>"}} - ] + ( if (($meta|length) > 0 and ($meta.flake_gate_tests // 0) == 0) then + [] elif ($root.candidates|length) == 0 then [ {"type":"header","text":{"type":"plain_text","text":":partywizard: Acceptance Tests: No Flake-Shake Promotion Candidates — \(if $date != "" then $date else (now|strftime("%Y-%m-%d")) end)"}}, diff --git a/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go b/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go index fd770988cf4..7e61b8931e7 100644 --- a/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go +++ b/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go @@ -1,6 +1,3 @@ -//go:build !ci - -// use a tag prefixed with "!". Such tag ensures that the default behaviour of this test would be to be built/run even when the go toolchain (go test) doesn't specify any tag filter. package conductor import ( diff --git a/op-acceptance-tests/tests/base/disputegame_v2/smoke_test.go b/op-acceptance-tests/tests/base/disputegame_v2/smoke_test.go index 8509a22a85f..b93ad395624 100644 --- a/op-acceptance-tests/tests/base/disputegame_v2/smoke_test.go +++ b/op-acceptance-tests/tests/base/disputegame_v2/smoke_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/gameargs" - challengerTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" ) @@ -15,18 +15,18 @@ func TestSmoke(gt *testing.T) { require := t.Require() dgf := sys.DisputeGameFactory() - gameArgs := dgf.GameArgs(challengerTypes.PermissionedGameType) + gameArgs := dgf.GameArgs(gameTypes.PermissionedGameType) require.NotEmpty(gameArgs, "game args is must be set for permissioned v2 dispute games") _, err := gameargs.Parse(gameArgs) require.NoError(err, "Permissioned game args invalid") - gameArgs = dgf.GameArgs(challengerTypes.CannonGameType) + gameArgs = dgf.GameArgs(gameTypes.CannonGameType) require.NotEmpty(gameArgs, "game args is must be set for cannon v2 dispute games") _, err = gameargs.Parse(gameArgs) require.NoError(err, "Permissionless game args invalid") - permissionedGame := dgf.GameImpl(challengerTypes.PermissionedGameType) + permissionedGame := dgf.GameImpl(gameTypes.PermissionedGameType) require.NotEmpty(permissionedGame.Address, "permissioned game impl must be set") - cannonGame := dgf.GameImpl(challengerTypes.CannonGameType) + cannonGame := dgf.GameImpl(gameTypes.CannonGameType) require.NotEmpty(cannonGame.Address, "cannon game impl must be set") } diff --git a/op-acceptance-tests/tests/base/withdrawal/cannon/init_test.go b/op-acceptance-tests/tests/base/withdrawal/cannon/init_test.go index 8ab26ed8957..693c20e9253 100644 --- a/op-acceptance-tests/tests/base/withdrawal/cannon/init_test.go +++ b/op-acceptance-tests/tests/base/withdrawal/cannon/init_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/base/withdrawal" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" ) func TestMain(m *testing.M) { - withdrawal.InitWithGameType(m, types.CannonGameType) + withdrawal.InitWithGameType(m, gameTypes.CannonGameType) } diff --git a/op-acceptance-tests/tests/base/withdrawal/cannon/withdrawal_test.go b/op-acceptance-tests/tests/base/withdrawal/cannon/withdrawal_test.go index 091d33f234e..324221eebf4 100644 --- a/op-acceptance-tests/tests/base/withdrawal/cannon/withdrawal_test.go +++ b/op-acceptance-tests/tests/base/withdrawal/cannon/withdrawal_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/base/withdrawal" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" ) func TestWithdrawal_Cannon(gt *testing.T) { - withdrawal.TestWithdrawal(gt, faultTypes.CannonGameType) + withdrawal.TestWithdrawal(gt, gameTypes.CannonGameType) } diff --git a/op-acceptance-tests/tests/base/withdrawal/cannon_kona/init_test.go b/op-acceptance-tests/tests/base/withdrawal/cannon_kona/init_test.go index 663551f0a6f..80e12341b19 100644 --- a/op-acceptance-tests/tests/base/withdrawal/cannon_kona/init_test.go +++ b/op-acceptance-tests/tests/base/withdrawal/cannon_kona/init_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/base/withdrawal" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" ) func TestMain(m *testing.M) { - withdrawal.InitWithGameType(m, types.CannonKonaGameType) + withdrawal.InitWithGameType(m, gameTypes.CannonKonaGameType) } diff --git a/op-acceptance-tests/tests/base/withdrawal/cannon_kona/withdrawal_test.go b/op-acceptance-tests/tests/base/withdrawal/cannon_kona/withdrawal_test.go index 13f470958d1..72f18178f12 100644 --- a/op-acceptance-tests/tests/base/withdrawal/cannon_kona/withdrawal_test.go +++ b/op-acceptance-tests/tests/base/withdrawal/cannon_kona/withdrawal_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/base/withdrawal" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" ) func TestWithdrawal_CannonKona(gt *testing.T) { - withdrawal.TestWithdrawal(gt, faultTypes.CannonKonaGameType) + withdrawal.TestWithdrawal(gt, gameTypes.CannonKonaGameType) } diff --git a/op-acceptance-tests/tests/base/withdrawal/permissioned/init_test.go b/op-acceptance-tests/tests/base/withdrawal/permissioned/init_test.go index f25f2451817..0017d530c31 100644 --- a/op-acceptance-tests/tests/base/withdrawal/permissioned/init_test.go +++ b/op-acceptance-tests/tests/base/withdrawal/permissioned/init_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/base/withdrawal" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" ) func TestMain(m *testing.M) { - withdrawal.InitWithGameType(m, types.PermissionedGameType) + withdrawal.InitWithGameType(m, gameTypes.PermissionedGameType) } diff --git a/op-acceptance-tests/tests/base/withdrawal/permissioned/withdrawal_test.go b/op-acceptance-tests/tests/base/withdrawal/permissioned/withdrawal_test.go index 777c2df3b84..e0715788c3c 100644 --- a/op-acceptance-tests/tests/base/withdrawal/permissioned/withdrawal_test.go +++ b/op-acceptance-tests/tests/base/withdrawal/permissioned/withdrawal_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/base/withdrawal" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" ) func TestWithdrawal_Permissioned(gt *testing.T) { - withdrawal.TestWithdrawal(gt, faultTypes.PermissionedGameType) + withdrawal.TestWithdrawal(gt, gameTypes.PermissionedGameType) } diff --git a/op-acceptance-tests/tests/base/withdrawal/withdrawal_test_helper.go b/op-acceptance-tests/tests/base/withdrawal/withdrawal_test_helper.go index abe47939bad..c7d087104d4 100644 --- a/op-acceptance-tests/tests/base/withdrawal/withdrawal_test_helper.go +++ b/op-acceptance-tests/tests/base/withdrawal/withdrawal_test_helper.go @@ -3,14 +3,14 @@ package withdrawal import ( "testing" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-devstack/compat" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" ) -func InitWithGameType(m *testing.M, gameType faultTypes.GameType) { +func InitWithGameType(m *testing.M, gameType gameTypes.GameType) { presets.DoMain(m, presets.WithCompatibleTypes(compat.SysGo), presets.WithMinimal(), @@ -25,7 +25,7 @@ func InitWithGameType(m *testing.M, gameType faultTypes.GameType) { ) } -func TestWithdrawal(gt *testing.T, gameType faultTypes.GameType) { +func TestWithdrawal(gt *testing.T, gameType gameTypes.GameType) { t := devtest.SerialT(gt) sys := presets.NewMinimal(t) require := sys.T.Require() diff --git a/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go b/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go index c534aae772f..cd08cb0e20c 100644 --- a/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go +++ b/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go @@ -6,16 +6,18 @@ package flashblocks import ( "context" "encoding/json" - "fmt" + "log/slog" "os" "strconv" "testing" "time" "github.com/ethereum-optimism/optimism/op-devstack/devtest" - "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/log/logfilter" + "github.com/ethereum-optimism/optimism/op-service/logmods" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -25,14 +27,20 @@ var ( maxExpectedFlashblocks = 20 ) -// TestFlashblocksStream checks we can connect to the flashblocks stream +// TestFlashblocksStream checks we can connect to the flashblocks stream across multiple CL backends. func TestFlashblocksStream(gt *testing.T) { t := devtest.SerialT(gt) - sys := presets.NewSimpleFlashblocks(t) - logger := testlog.Logger(t, log.LevelInfo).With("Test", "TestFlashblocksStream") + logger := t.Logger() + sys := presets.NewSingleChainWithFlashblocks(t) + filterHandler, ok := logmods.FindHandler[logfilter.FilterHandler](logger.Handler()) + if ok { + filterHandler.Set(logfilter.DefaultMute( + logfilter.Level(slog.LevelError).Show(), + logfilter.Select("kind", "L2CLNode").Show(), + )) + } tracer := t.Tracer() ctx := t.Ctx() - logger.Info("Started Flashblocks Stream test") ctx, span := tracer.Start(ctx, "test chains") defer span.End() @@ -51,33 +59,91 @@ func TestFlashblocksStream(gt *testing.T) { logger.Info("Flashblocks stream rate", "rate", flashblocksStreamRateMs) // Test all L2 chains in the system - for l2Chain, flashblocksBuilderSet := range sys.FlashblocksBuilderSets { - _, span = tracer.Start(ctx, "test chain") - defer span.End() - - networkName := l2Chain.String() - t.Run(fmt.Sprintf("L2_Chain_%s", networkName), func(tt devtest.T) { - if len(flashblocksBuilderSet) == 0 { - tt.Skip("no flashblocks builders for chain", l2Chain.String()) - } + oprbuilderNode := sys.L2OPRBuilder + rollupBoostNode := sys.L2RollupBoost + _, span = tracer.Start(ctx, "test chain") + defer span.End() + + expectedChainID := sys.L2Chain.ChainID().ToBig() + require.Equal(t, oprbuilderNode.Escape().ChainID().ToBig(), expectedChainID, "flashblocks builder node chain id should match expected chain id") + + driveViaTestSequencer(t, sys, 3) + + // Test the presence / absence of a flashblocks stream operating at a 250ms rate from a flashblocks-websocket-proxy node. + // Allow a generous window for first flashblocks to appear. + testDuration := time.Duration(int64(flashblocksStreamRateMs*maxExpectedFlashblocks*2)) * time.Millisecond + // Allow up to 15% of expected flashblocks to be missing due to timing variations + failureTolerance := int(0.15 * float64(maxExpectedFlashblocks)) + + logger.Debug("Test duration", "duration", testDuration, "failure tolerance (of flashblocks)", failureTolerance) + + // Instrument builder stream separately to confirm flashblocks emission upstream. + builderOutput := make(chan []byte, maxExpectedFlashblocks) + defer close(builderOutput) + builderDone := make(chan struct{}) + go func() { + err := oprbuilderNode.FlashblocksClient().ListenFor(ctx, logger.With("stream_source", "op-rbuilder"), testDuration, builderOutput, builderDone) + require.NoError(t, err) + }() + builderMessages := make([]string, 0) + + output := make(chan []byte, maxExpectedFlashblocks) + defer close(output) + doneListening := make(chan struct{}) + streamedMessages := make([]string, 0) + go func() { + err := rollupBoostNode.FlashblocksClient().ListenFor(ctx, logger.With("stream_source", "rollup-boost"), testDuration, output, doneListening) + require.NoError(t, err) + }() + + listening := true + for listening { + select { + case <-doneListening: + doneListening = nil + case <-builderDone: + builderDone = nil + case msg := <-output: + streamedMessages = append(streamedMessages, string(msg)) + case msg := <-builderOutput: + builderMessages = append(builderMessages, string(msg)) + } - expectedChainID := l2Chain.ChainID().ToBig() - for _, flashblocksBuilderNode := range flashblocksBuilderSet { - require.Equal(t, flashblocksBuilderNode.Escape().ChainID().ToBig(), expectedChainID, "flashblocks builder node chain id should match expected chain id") + if doneListening == nil && builderDone == nil { + listening = false + } + } - mode := FlashblocksStreamMode_Follower - if dsl.NewConductor(flashblocksBuilderNode.Escape().Conductor()).IsLeader() { - mode = FlashblocksStreamMode_Leader - } + logger.Info("Completed WebSocket stream reading", "msg_count", len(streamedMessages), "builder_msg_count", len(builderMessages)) - testFlashblocksStreamRbuilder(tt, logger, flashblocksBuilderNode, mode, flashblocksStreamRateMs) - } + if len(builderMessages) > 0 { + logger.Info("Sample builder message", "payload", builderMessages[0]) + } - for _, flashblocksWebsocketProxy := range sys.FlashblocksWebsocketProxies[l2Chain] { - testFlashblocksStreamFbWsProxy(tt, logger, flashblocksWebsocketProxy, flashblocksStreamRateMs) - } - }) + totalFlashblocksProduced := evaluateFlashblocksStream(t, logger, streamedMessages, failureTolerance) + require.Greater(t, totalFlashblocksProduced, 0, "expected to receive flashblocks from rollup-boost stream") + logger.Info("Flashblocks stream validation completed", "total_flashblocks_produced", totalFlashblocksProduced) +} + +// driveViaTestSequencer explicitly builds a few blocks to ensure the builder/rollup-boost +// have payloads to serve before we start listening for flashblocks. +func driveViaTestSequencer(t devtest.T, sys *presets.SingleChainWithFlashblocks, count int) { + t.Helper() + ts := sys.TestSequencer.Escape().ControlAPI(sys.L2Chain.ChainID()) + ctx := t.Ctx() + + head := sys.L2EL.BlockRefByLabel(eth.Unsafe) + for i := 0; i < count; i++ { + require.NoError(t, ts.New(ctx, seqtypes.BuildOpts{Parent: head.Hash})) + require.NoError(t, ts.Next(ctx)) + head = sys.L2EL.BlockRefByLabel(eth.Unsafe) } + // Ensure the sequencer EL has produced at least one unsafe block before subscribing. + sys.L2EL.WaitForBlockNumber(1) + + // Log the latest unsafe head and L1 origin to confirm block production before listening. + head = sys.L2EL.BlockRefByLabel(eth.Unsafe) + sys.Log.Info("Pre-listen unsafe head", "unsafe", head) } func evaluateFlashblocksStream(t devtest.T, logger log.Logger, streamedMessages []string, failureTolerance int) int { @@ -133,102 +199,3 @@ func evaluateFlashblocksStream(t devtest.T, logger log.Logger, streamedMessages return totalFlashblocksProduced } - -// testFlashblocksStreamRbuilder tests the presence / absence of a flashblocks stream operating at a 250ms (configurable via env var FLASHBLOCKS_STREAM_RATE) rate from an rbuilder node -func testFlashblocksStreamRbuilder(t devtest.T, logger log.Logger, flashblocksBuilderNode *dsl.FlashblocksBuilderNode, mode FlashblocksStreamMode, expectedFlashblocksStreamRateMs int) { - t.Run(fmt.Sprintf("Flashblocks_Stream_Rbuilder_%s_%s", flashblocksBuilderNode.Escape().ID(), mode), func(t devtest.T) { - testDuration := time.Duration(int64(expectedFlashblocksStreamRateMs*maxExpectedFlashblocks)) * time.Millisecond - failureTolerance := int(0.15 * float64(maxExpectedFlashblocks)) - - logger.Debug("Test duration", "duration", testDuration, "failure tolerance (of flashblocks)", failureTolerance) - - require.Contains(t, []FlashblocksStreamMode{FlashblocksStreamMode_Leader, FlashblocksStreamMode_Follower}, mode, "mode should be either leader or follower") - require.NotNil(t, flashblocksBuilderNode, "flashblocksBuilderNode should not be nil") - - output := make(chan []byte, maxExpectedFlashblocks) - doneListening := make(chan struct{}) - streamedMessages := make([]string, 0) - go flashblocksBuilderNode.ListenFor(logger, testDuration, output, doneListening) //nolint:errcheck - - for { - select { - case <-doneListening: - goto done - case msg := <-output: - streamedMessages = append(streamedMessages, string(msg)) - } - } - done: - - defer close(output) - - logger.Info("Completed WebSocket stream reading", "message_count", len(streamedMessages)) - if mode == FlashblocksStreamMode_Follower { - require.Equal(t, len(streamedMessages), 0, "follower should not receive any messages") - return - } - - totalFlashblocksProduced := evaluateFlashblocksStream(t, logger, streamedMessages, failureTolerance) - - minExpectedFlashblocks := maxExpectedFlashblocks - failureTolerance - require.Greater(t, - totalFlashblocksProduced, minExpectedFlashblocks, - fmt.Sprintf("total flashblocks produced should be greater than %d (%d over %s with a %dms rate with a failure tolerance of %d flashblocks)", - minExpectedFlashblocks, - maxExpectedFlashblocks, - testDuration, - expectedFlashblocksStreamRateMs, - failureTolerance, - ), - ) - - logger.Info("Flashblocks stream validation completed", "total_flashblocks_produced", totalFlashblocksProduced) - }) -} - -// testFlashblocksStreamFbWsProxy tests the presence / absence of a flashblocks stream operating at a 250ms (configurable via env var FLASHBLOCKS_STREAM_RATE) rate from a flashblocks-websocket-proxy node -func testFlashblocksStreamFbWsProxy(t devtest.T, logger log.Logger, flashblocksWebsocketProxy *dsl.FlashblocksWebsocketProxy, expectedFlashblocksStreamRateMs int) { - t.Run(fmt.Sprintf("Flashblocks_Stream_FbWsProxy_%s", flashblocksWebsocketProxy.Escape().ID()), func(t devtest.T) { - testDuration := time.Duration(int64(expectedFlashblocksStreamRateMs*maxExpectedFlashblocks)) * time.Millisecond - failureTolerance := int(0.15 * float64(maxExpectedFlashblocks)) - - logger.Debug("Test duration", "duration", testDuration, "failure tolerance (of flashblocks)", failureTolerance) - - require.NotNil(t, flashblocksWebsocketProxy, "flashblocksWebsocketProxy should not be nil") - - output := make(chan []byte, maxExpectedFlashblocks) - doneListening := make(chan struct{}) - streamedMessages := make([]string, 0) - go flashblocksWebsocketProxy.ListenFor(logger, testDuration, output, doneListening) //nolint:errcheck - - for { - select { - case <-doneListening: - goto done - case msg := <-output: - streamedMessages = append(streamedMessages, string(msg)) - } - } - done: - - defer close(output) - - logger.Info("Completed WebSocket stream reading", "message_count", len(streamedMessages)) - - totalFlashblocksProduced := evaluateFlashblocksStream(t, logger, streamedMessages, failureTolerance) - - minExpectedFlashblocks := maxExpectedFlashblocks - failureTolerance - require.Greater(t, - totalFlashblocksProduced, minExpectedFlashblocks, - fmt.Sprintf("total flashblocks produced should be greater than %d (%d over %s with a %dms rate with a failure tolerance of %d flashblocks)", - minExpectedFlashblocks, - maxExpectedFlashblocks, - testDuration, - expectedFlashblocksStreamRateMs, - failureTolerance, - ), - ) - - logger.Info("Flashblocks stream validation completed", "total_flashblocks_produced", totalFlashblocksProduced) - }) -} diff --git a/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go b/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go index 0761c870ba9..77c925955c4 100644 --- a/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go +++ b/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go @@ -1,6 +1,3 @@ -//go:build !ci - -// use a tag prefixed with "!". Such tag ensures that the default behaviour of this test would be to be built/run even when the go toolchain (go test) doesn't specify any tag filter. package flashblocks import ( @@ -14,9 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/txplan" - "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -37,117 +32,101 @@ type timedMessage struct { // - That Flashblock's time in nanoseconds must be before the approximated transaction confirmation time recorded previously. func TestFlashblocksTransfer(gt *testing.T) { t := devtest.SerialT(gt) - sys := presets.NewSimpleFlashblocks(t) - logger := testlog.Logger(t, log.LevelInfo).With("Test", "TestFlashblocksTransfer") + logger := t.Logger() tracer := t.Tracer() ctx := t.Ctx() - logger.Info("Started Flashblocks Transfer test") + sys := presets.NewSingleChainWithFlashblocks(t) topLevelCtx, span := tracer.Start(ctx, "test chains") defer span.End() - // Test all L2 chains in the system - for l2Chain, funder := range sys.Funders { - ctx, cancel := context.WithTimeout(topLevelCtx, 45*time.Second) - defer cancel() - _, span = tracer.Start(ctx, fmt.Sprintf("test chain %s", l2Chain.String())) - defer span.End() - - t.Run(fmt.Sprintf("L2_Chain_%s", l2Chain.String()), func(tt devtest.T) { - if len(sys.FlashblocksBuilderSets[l2Chain]) == 0 && len(sys.FlashblocksWebsocketProxies[l2Chain]) == 0 { - tt.Skip("no flashblocks builders or websocket proxies found for chain", l2Chain.String()) - } - - doneListening := make(chan struct{}) - output := make(chan []byte, 100) - - alice := funder.NewFundedEOA(eth.ThreeHundredthsEther) - bob := sys.Wallet.NewEOA(sys.L2ELNodes[l2Chain]) - bobAddress := bob.Address().Hex() - - // flashblocks listener - fbWsProxies := sys.FlashblocksWebsocketProxies[l2Chain] - if len(fbWsProxies) > 0 { - fbWsProxy := fbWsProxies[0] - logger.Info("Listening for flashblocks via websocket proxy", "proxy", fbWsProxy.String()) - go fbWsProxy.ListenFor(logger, 20*time.Second, output, doneListening) //nolint:errcheck - } else { - leaderFbBuilder := sys.FlashblocksBuilderSets[l2Chain].Leader() - require.NotNil(tt, leaderFbBuilder, "should have a leader", "chain", l2Chain.String()) - - logger.Info("Listening for flashblocks via flashblocks builder", "builder", leaderFbBuilder.String()) - - go leaderFbBuilder.ListenFor(logger, 20*time.Second, output, doneListening) //nolint:errcheck - } - - var executedTransaction *txplan.PlannedTx - var transactionApproxConfirmationTime time.Time - var expectedBobBalance string - - // transactor - go func() { - time.Sleep(6 * time.Second) // warm up for the websocket handshake to be established - bobBalance := bob.GetBalance() - - depositAmount := eth.OneHundredthEther - bobAddr := bob.Address() - executedTransaction = alice.Transact( - alice.Plan(), - txplan.WithTo(&bobAddr), - txplan.WithValue(depositAmount), - ) - transactionApproxConfirmationTime = time.Now() - newBobBalance := bobBalance.Add(depositAmount) - expectedBobBalance = newBobBalance.Hex() - bob.VerifyBalanceExact(newBobBalance) - }() - - streamedMessages := make([]timedMessage, 0) - for { - select { - case <-doneListening: - goto done - case msg := <-output: - streamedMessages = append(streamedMessages, timedMessage{message: msg, timestamp: time.Now()}) - } - } - done: - require.Greater(tt, len(streamedMessages), 0, "should have received at least one message from the flashblocks stream") - require.NotNil(tt, executedTransaction, "should have executed a transaction") - - var bobFlashblockTime time.Time - var bobFlashblock *Flashblock - var observedBobBalance string - - for _, msg := range streamedMessages { - flashblock := &Flashblock{} - err := json.Unmarshal(msg.message, flashblock) - require.NoError(tt, err, "should be able to unmarshal the message") - - bobBalance := flashblock.Metadata.NewAccountBalances[strings.ToLower(bobAddress)] - if bobBalance != "" && bobBalance != "0x0" { - bobFlashblockTime = msg.timestamp - bobFlashblock = flashblock - observedBobBalance = bobBalance - break - } - } - - require.NotNil(tt, bobFlashblock, "should have received a flashblock corresponding to Bob's receival of the funds") - - txBlock, err := executedTransaction.IncludedBlock.Eval(ctx) - require.NoError(tt, err, "should be able to evaluate the block in which the transaction was included") - - txBlockNum := int(txBlock.Number) // block number of the block in which the transaction was included / confirmed - flashblockParentBlockNum := int(bobFlashblock.Metadata.BlockNumber) // block number of the parent block of the flashblock which first recorded the update in Bob's account balance (representative of the flashblock which included this transaction) - - txBlockTimeSeconds := int64(txBlock.Time) // timestamp of the block in which the transaction was included / confirmed - txFlashblockTimeInSeconds := bobFlashblockTime.Unix() // timestamp of the flashblock which supposedly included Bob's transaction - - require.Equal(tt, observedBobBalance, expectedBobBalance, "Bob's balance must be correct as per exactly what Alice transferred to them") - require.Equal(tt, txBlockNum, flashblockParentBlockNum, "the transaction's block number should be the same as the flashblock's parent block number") - require.LessOrEqual(tt, txFlashblockTimeInSeconds, txBlockTimeSeconds, "the transaction's block time (in seconds) should be less than or equal to the flashblock's time (in seconds)") - require.Less(tt, bobFlashblockTime.UnixNano(), transactionApproxConfirmationTime.UnixNano(), "flashblock time should be before the transaction's (approximated) confirmation time") - }) + ctx, cancel := context.WithTimeout(topLevelCtx, 45*time.Second) + defer cancel() + _, span = tracer.Start(ctx, fmt.Sprintf("test chain %s", sys.L2Chain.String())) + defer span.End() + + doneListening := make(chan struct{}) + output := make(chan []byte, 100) + + // Drive a couple blocks on the test sequencer so the faucet L2 funding tx has a chance to land before we rely on it. + driveViaTestSequencer(t, sys, 2) + + alice := sys.FunderL2.NewFundedEOA(eth.ThreeHundredthsEther) + bob := sys.Wallet.NewEOA(sys.L2EL) + bobAddress := bob.Address().Hex() + + // flashblocks listener + flashblocksClient := sys.L2RollupBoost.FlashblocksClient() + go func() { + err := flashblocksClient.ListenFor(ctx, logger, 20*time.Second, output, doneListening) + t.Require().NoError(err, "failed to listen for flashblocks") + }() + + var executedTransaction *txplan.PlannedTx + var transactionApproxConfirmationTime time.Time + var expectedBobBalance string + + // transactor + go func() { + bobBalance := bob.GetBalance() + + depositAmount := eth.OneHundredthEther + bobAddr := bob.Address() + executedTransaction = alice.Transact( + alice.Plan(), + txplan.WithTo(&bobAddr), + txplan.WithValue(depositAmount), + ) + transactionApproxConfirmationTime = time.Now() + newBobBalance := bobBalance.Add(depositAmount) + expectedBobBalance = newBobBalance.Hex() + bob.VerifyBalanceExact(newBobBalance) + }() + + streamedMessages := make([]timedMessage, 0) + listening := true + for listening { + select { + case <-doneListening: + listening = false + case msg := <-output: + streamedMessages = append(streamedMessages, timedMessage{message: msg, timestamp: time.Now()}) + } } + require.Greater(t, len(streamedMessages), 0, "should have received at least one message from the flashblocks stream") + require.NotNil(t, executedTransaction, "should have executed a transaction") + + var bobFlashblockTime time.Time + var bobFlashblock *Flashblock + var observedBobBalance string + + for _, msg := range streamedMessages { + flashblock := &Flashblock{} + err := json.Unmarshal(msg.message, flashblock) + require.NoError(t, err, "should be able to unmarshal the message") + + bobBalance := flashblock.Metadata.NewAccountBalances[strings.ToLower(bobAddress)] + if bobBalance != "" && bobBalance != "0x0" { + bobFlashblockTime = msg.timestamp + bobFlashblock = flashblock + observedBobBalance = bobBalance + break + } + } + + require.NotNil(t, bobFlashblock, "should have received a flashblock corresponding to Bob's receival of the funds") + + txBlock, err := executedTransaction.IncludedBlock.Eval(ctx) + require.NoError(t, err, "should be able to evaluate the block in which the transaction was included") + + txBlockNum := int(txBlock.Number) // block number of the block in which the transaction was included / confirmed + flashblockParentBlockNum := int(bobFlashblock.Metadata.BlockNumber) // block number of the parent block of the flashblock which first recorded the update in Bob's account balance (representative of the flashblock which included this transaction) + + txBlockTimeSeconds := int64(txBlock.Time) // timestamp of the block in which the transaction was included / confirmed + txFlashblockTimeInSeconds := bobFlashblockTime.Unix() // timestamp of the flashblock which supposedly included Bob's transaction + + require.Equal(t, observedBobBalance, expectedBobBalance, "Bob's balance must be correct as per exactly what Alice transferred to them") + require.Equal(t, txBlockNum, flashblockParentBlockNum, "the transaction's block number should be the same as the flashblock's parent block number") + require.LessOrEqual(t, txFlashblockTimeInSeconds, txBlockTimeSeconds, "the transaction's block time (in seconds) should be less than or equal to the flashblock's time (in seconds)") + require.Less(t, bobFlashblockTime.UnixNano(), transactionApproxConfirmationTime.UnixNano(), "flashblock time should be before the transaction's (approximated) confirmation time") } diff --git a/op-acceptance-tests/tests/flashblocks/init_test.go b/op-acceptance-tests/tests/flashblocks/init_test.go index 3c4d36f9a39..6d2759e47bd 100644 --- a/op-acceptance-tests/tests/flashblocks/init_test.go +++ b/op-acceptance-tests/tests/flashblocks/init_test.go @@ -8,5 +8,5 @@ import ( // TestMain creates the test-setups against the shared backend func TestMain(m *testing.M) { - presets.DoMain(m, presets.WithSimpleFlashblocks()) + presets.DoMain(m, presets.WithSingleChainSystemWithFlashblocks()) } diff --git a/op-acceptance-tests/tests/sync/unsafe_only/init_test.go b/op-acceptance-tests/tests/sync/unsafe_only/init_test.go new file mode 100644 index 00000000000..f2e144bf063 --- /dev/null +++ b/op-acceptance-tests/tests/sync/unsafe_only/init_test.go @@ -0,0 +1,18 @@ +package unsafe_only + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" +) + +func TestMain(m *testing.M) { + presets.DoMain(m, presets.WithSingleChainTwoVerifiers(), + presets.WithExecutionLayerSyncOnVerifiers(), + presets.WithReqRespSyncDisabled(), + presets.WithNoDiscovery(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithUnsafeOnly(), + ) +} diff --git a/op-acceptance-tests/tests/sync/unsafe_only/sync_test.go b/op-acceptance-tests/tests/sync/unsafe_only/sync_test.go new file mode 100644 index 00000000000..aec7fc54e13 --- /dev/null +++ b/op-acceptance-tests/tests/sync/unsafe_only/sync_test.go @@ -0,0 +1,103 @@ +package unsafe_only + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func TestUnsafeOnly_VerifierUnsafeGapClosed(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainTwoVerifiersWithoutCheck(t) + require := t.Require() + attempts := 10 + + sys.L2CL.AdvancedUnsafe(3, attempts) + sys.L2EL.MatchedUnsafe(sys.L2ELB, attempts) + sys.L2CL.MatchedUnsafe(sys.L2CLB, attempts) + + // Case 1: Closing the gap starting from genesis + sys.L2CLB.Stop() + sys.L2ELB.DisconnectPeerWith(sys.L2EL) + // Wipe EL to genesis + sys.L2ELB.Stop() + sys.L2ELB.Start() + // Check EL rewinded to genesis. Unsafe gap introduced + sys.L2ELB.UnsafeHead().IsGenesis() + // Verifier CL triggers EL Sync to close the gap including genesis + sys.L2CLB.Start() + sys.L2CLB.ConnectPeer(sys.L2CL) + sys.L2ELB.PeerWith(sys.L2EL) + // Gap is closed + sys.L2CLB.MatchedUnsafe(sys.L2CL, attempts) + sys.L2ELB.MatchedUnsafe(sys.L2EL, attempts) + + // Case 2: Closing the gap not starting from genesis + sys.L2CLB.DisconnectPeer(sys.L2CL) + sys.L2CL.AdvancedUnsafe(3, attempts) + sys.L2CLB.NotAdvanced(types.LocalUnsafe, 3) + // Turn back the CLP2P + sys.L2CLB.ConnectPeer(sys.L2CL) + // gap is closed again + sys.L2CLB.MatchedUnsafe(sys.L2CL, attempts) + sys.L2ELB.MatchedUnsafe(sys.L2EL, attempts) + + // Derivation did not happen + sys.L2CL.SafeHead().IsGenesis() + + // Derivation happened at the second verifier + require.Greater(sys.L2CLC.SafeHead().BlockRef.Number, uint64(0)) + + t.Cleanup(func() { + sys.L2ELB.Start() + sys.L2ELB.PeerWith(sys.L2EL) + sys.L2CLB.Start() + sys.L2CLB.ConnectPeer(sys.L2CL) + }) +} + +func TestUnsafeOnly_SequencerRestart(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainTwoVerifiersWithoutCheck(t) + require := t.Require() + + attempts := 10 + + sys.L2CL.AdvancedUnsafe(3, attempts) + sys.L2EL.MatchedUnsafe(sys.L2ELB, attempts) + sys.L2CL.MatchedUnsafe(sys.L2CLB, attempts) + + // Stop the sequencer + sys.L2CL.Stop() + sys.L2ELB.NotAdvancedUnsafe(3) + + // Restart the sequencer + sys.L2CL.Start() + // Sequencer produces blocks again + sys.L2CL.AdvancedUnsafe(3, attempts) + + // Derivation did not happen at sequencer + sys.L2CL.SafeHead().IsGenesis() + + // Stop the sequencer with API + sys.L2CL.StopSequencer() + sys.L2ELB.NotAdvancedUnsafe(3) + + // Restart the sequencer with API + sys.L2CL.StartSequencer() + // Sequencer produces blocks again + sys.L2CL.AdvancedUnsafe(3, attempts) + + // Derivation did not happen at sequencer + sys.L2CL.SafeHead().IsGenesis() + + // Derivation happened at the second verifier + safeHeadNum := sys.L2CLC.SafeHead().BlockRef.Number + require.Greater(safeHeadNum, uint64(0)) + + t.Cleanup(func() { + sys.L2CL.Start() + }) +} diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el/ext_config.go b/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el/ext_config.go new file mode 100644 index 00000000000..f41811d7815 --- /dev/null +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el/ext_config.go @@ -0,0 +1,88 @@ +package sync_tester_ext_el + +import ( + "fmt" + "os" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// Configuration defaults for op-sepolia +const ( + DefaultNetworkPreset = "op-sepolia" + + // Tailscale networking endpoints + DefaultL2ELEndpointTailscale = "https://proxyd-l2-sepolia.primary.client.dev.oplabs.cloud" + DefaultL1CLBeaconEndpointTailscale = "https://beacon-api-proxy-sepolia.primary.client.dev.oplabs.cloud" + DefaultL1ELEndpointTailscale = "https://proxyd-l1-sepolia.primary.client.dev.oplabs.cloud" +) + +var ( + // Network presets for different networks against which we test op-node syncing + networkPresets = map[string]stack.ExtNetworkConfig{ + "op-sepolia": { + L2NetworkName: "op-sepolia", + L1ChainID: eth.ChainIDFromUInt64(11155111), + L2ELEndpoint: "https://ci-sepolia-l2.optimism.io", + L1CLBeaconEndpoint: "https://ci-sepolia-beacon.optimism.io", + L1ELEndpoint: "https://ci-sepolia-l1.optimism.io", + }, + "base-sepolia": { + L2NetworkName: "base-sepolia", + L1ChainID: eth.ChainIDFromUInt64(11155111), + L2ELEndpoint: "https://base-sepolia-rpc.optimism.io", + L1CLBeaconEndpoint: "https://ci-sepolia-beacon.optimism.io", + L1ELEndpoint: "https://ci-sepolia-l1.optimism.io", + }, + "unichain-sepolia": { + L2NetworkName: "unichain-sepolia", + L1ChainID: eth.ChainIDFromUInt64(11155111), + L2ELEndpoint: "https://unichain-sepolia-rpc.optimism.io", + L1CLBeaconEndpoint: "https://ci-sepolia-beacon.optimism.io", + L1ELEndpoint: "https://ci-sepolia-l1.optimism.io", + }, + "op-mainnet": { + L2NetworkName: "op-mainnet", + L1ChainID: eth.ChainIDFromUInt64(1), + L2ELEndpoint: "https://op-mainnet-rpc.optimism.io", + L1CLBeaconEndpoint: "https://ci-mainnet-beacon.optimism.io", + L1ELEndpoint: "https://ci-mainnet-l1.optimism.io", + }, + "base-mainnet": { + L2NetworkName: "base-mainnet", + L1ChainID: eth.ChainIDFromUInt64(1), + L2ELEndpoint: "https://base-mainnet-rpc.optimism.io", + L1CLBeaconEndpoint: "https://ci-mainnet-beacon.optimism.io", + L1ELEndpoint: "https://ci-mainnet-l1.optimism.io", + }, + } +) + +func GetNetworkPreset(name string) (stack.ExtNetworkConfig, error) { + var config stack.ExtNetworkConfig + if name == "" { + config = networkPresets[DefaultNetworkPreset] + } else { + var ok bool + config, ok = networkPresets[name] + if !ok { + return stack.ExtNetworkConfig{}, fmt.Errorf("NETWORK_PRESET %s not found", name) + } + } + // Override configuration with Tailscale endpoints if Tailscale networking is enabled + if os.Getenv("TAILSCALE_NETWORKING") == "true" { + config.L2ELEndpoint = getEnvOrDefault("L2_EL_ENDPOINT_TAILSCALE", DefaultL2ELEndpointTailscale) + config.L1CLBeaconEndpoint = getEnvOrDefault("L1_CL_BEACON_ENDPOINT_TAILSCALE", DefaultL1CLBeaconEndpointTailscale) + config.L1ELEndpoint = getEnvOrDefault("L1_EL_ENDPOINT_TAILSCALE", DefaultL1ELEndpointTailscale) + } + return config, nil +} + +// getEnvOrDefault returns the environment variable value or the default if not set +func getEnvOrDefault(envVar, defaultValue string) string { + if value := os.Getenv(envVar); value != "" { + return value + } + return defaultValue +} diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el/sync_tester_ext_el_test.go b/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el/sync_tester_ext_el_test.go index adb8366db00..33253bba88f 100644 --- a/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el/sync_tester_ext_el_test.go +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el/sync_tester_ext_el_test.go @@ -22,64 +22,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// Configuration defaults for op-sepolia -const ( - DefaultNetworkPreset = "op-sepolia" - - // Tailscale networking endpoints - DefaultL2ELEndpointTailscale = "https://proxyd-l2-sepolia.primary.client.dev.oplabs.cloud" - DefaultL1CLBeaconEndpointTailscale = "https://beacon-api-proxy-sepolia.primary.client.dev.oplabs.cloud" - DefaultL1ELEndpointTailscale = "https://proxyd-l1-sepolia.primary.client.dev.oplabs.cloud" -) - -var ( - // Network presets for different networks against which we test op-node syncing - networkPresets = map[string]stack.ExtNetworkConfig{ - "op-sepolia": { - L2NetworkName: "op-sepolia", - L1ChainID: eth.ChainIDFromUInt64(11155111), - L2ELEndpoint: "https://ci-sepolia-l2.optimism.io", - L1CLBeaconEndpoint: "https://ci-sepolia-beacon.optimism.io", - L1ELEndpoint: "https://ci-sepolia-l1.optimism.io", - }, - "base-sepolia": { - L2NetworkName: "base-sepolia", - L1ChainID: eth.ChainIDFromUInt64(11155111), - L2ELEndpoint: "https://base-sepolia-rpc.optimism.io", - L1CLBeaconEndpoint: "https://ci-sepolia-beacon.optimism.io", - L1ELEndpoint: "https://ci-sepolia-l1.optimism.io", - }, - "unichain-sepolia": { - L2NetworkName: "unichain-sepolia", - L1ChainID: eth.ChainIDFromUInt64(11155111), - L2ELEndpoint: "https://unichain-sepolia-rpc.optimism.io", - L1CLBeaconEndpoint: "https://ci-sepolia-beacon.optimism.io", - L1ELEndpoint: "https://ci-sepolia-l1.optimism.io", - }, - "op-mainnet": { - L2NetworkName: "op-mainnet", - L1ChainID: eth.ChainIDFromUInt64(1), - L2ELEndpoint: "https://op-mainnet-rpc.optimism.io", - L1CLBeaconEndpoint: "https://ci-mainnet-beacon.optimism.io", - L1ELEndpoint: "https://ci-mainnet-l1.optimism.io", - }, - "base-mainnet": { - L2NetworkName: "base-mainnet", - L1ChainID: eth.ChainIDFromUInt64(1), - L2ELEndpoint: "https://base-mainnet-rpc.optimism.io", - L1CLBeaconEndpoint: "https://ci-mainnet-beacon.optimism.io", - L1ELEndpoint: "https://ci-mainnet-l1.optimism.io", - }, - } - L2CLSyncMode = getSyncMode("L2_CL_SYNCMODE") -) - -func getSyncMode(envVar string) sync.Mode { - if value := os.Getenv(envVar); value == sync.ELSyncString { - return sync.ELSync - } - return sync.CLSync -} +var L2CLSyncMode = getSyncMode("L2_CL_SYNCMODE") func TestSyncTesterExtEL(gt *testing.T) { t := devtest.SerialT(gt) @@ -161,22 +104,8 @@ func setupOrchestrator(gt *testing.T, t devtest.T, blocksToSync uint64) (*sysgo. ctx := t.Ctx() require := t.Require() - config := networkPresets[DefaultNetworkPreset] - - // Override configuration with Tailscale endpoints if Tailscale networking is enabled - if os.Getenv("TAILSCALE_NETWORKING") == "true" { - config.L2ELEndpoint = getEnvOrDefault("L2_EL_ENDPOINT_TAILSCALE", DefaultL2ELEndpointTailscale) - config.L1CLBeaconEndpoint = getEnvOrDefault("L1_CL_BEACON_ENDPOINT_TAILSCALE", DefaultL1CLBeaconEndpointTailscale) - config.L1ELEndpoint = getEnvOrDefault("L1_EL_ENDPOINT_TAILSCALE", DefaultL1ELEndpointTailscale) - } - - if os.Getenv("NETWORK_PRESET") != "" { - var ok bool - config, ok = networkPresets[os.Getenv("NETWORK_PRESET")] - if !ok { - gt.Errorf("NETWORK_PRESET %s not found", os.Getenv("NETWORK_PRESET")) - } - } + config, err := GetNetworkPreset(os.Getenv("NETWORK_PRESET")) + require.NoError(err, "failed to initialize network preset") // Runtime configuration values l.Info("Runtime configuration values for TestSyncTesterExtEL") @@ -246,10 +175,9 @@ func setupOrchestrator(gt *testing.T, t devtest.T, blocksToSync uint64) (*sysgo. return orch.(*sysgo.Orchestrator), target } -// getEnvOrDefault returns the environment variable value or the default if not set -func getEnvOrDefault(envVar, defaultValue string) string { - if value := os.Getenv(envVar); value != "" { - return value +func getSyncMode(envVar string) sync.Mode { + if value := os.Getenv(envVar); value == sync.ELSyncString { + return sync.ELSync } - return defaultValue + return sync.CLSync } diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_unsafe_only_ext/init_test.go b/op-acceptance-tests/tests/sync_tester/sync_tester_unsafe_only_ext/init_test.go new file mode 100644 index 00000000000..d566ebef267 --- /dev/null +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_unsafe_only_ext/init_test.go @@ -0,0 +1,43 @@ +package sync_tester_unsafe_only_ext + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/sync_tester/sync_tester_ext_el" + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-devstack/compat" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-node/chaincfg" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +func TestMain(m *testing.M) { + // Target op-sepolia + networkName := "op-sepolia" + config, _ := sync_tester_ext_el.GetNetworkPreset(networkName) + chainCfg := chaincfg.ChainByName(networkName) + presets.DoMain(m, + presets.WithExternalELWithSuperchainRegistry(config), + // CL connected to sync tester EL is verifier + presets.WithExecutionLayerSyncOnVerifiers(), + // Make sync tester EL mock EL Sync + presets.WithELSyncActive(), + // Only rely on EL sync for unsafe gap filling + presets.WithReqRespSyncDisabled(), + presets.WithNoDiscovery(), + presets.WithCompatibleTypes(compat.SysGo), + presets.WithUnsafeOnly(), + stack.MakeCommon(sysgo.WithBatcherOption(func(id stack.L2BatcherID, cfg *bss.CLIConfig) { + // For stopping derivation, not to advance safe heads + cfg.Stopped = true + })), + // Sync tester EL at genesis + presets.WithSyncTesterELInitialState(eth.FCUState{ + Latest: chainCfg.Genesis.L2.Number, + Safe: chainCfg.Genesis.L2.Number, + Finalized: chainCfg.Genesis.L2.Number, + }), + ) +} diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_unsafe_only_ext/sync_test.go b/op-acceptance-tests/tests/sync_tester/sync_tester_unsafe_only_ext/sync_test.go new file mode 100644 index 00000000000..74bee285dfb --- /dev/null +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_unsafe_only_ext/sync_test.go @@ -0,0 +1,50 @@ +package sync_tester_unsafe_only_ext + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func TestSyncTesterUnsafeOnlyReachUnsafeTip(gt *testing.T) { + t := devtest.SerialT(gt) + require := t.Require() + + sys := presets.NewMinimalExternalEL(t) + sys.L2EL.UnsafeHead().IsGenesis() + + // Check external read only EL is advancing + sys.L2ELReadOnly.Advanced(eth.Unsafe, 3) + + unsafeTip := sys.L2ELReadOnly.UnsafeHead() + unsafeTipNum := unsafeTip.BlockRef.Number + startNum := unsafeTipNum - 3 + // Trigger and finish EL Sync + for i := startNum; i <= unsafeTipNum; i++ { + sys.L2CL.SignalTarget(sys.L2ELReadOnly, i) + } + + sys.L2EL.Reached(eth.Unsafe, unsafeTipNum, 5) + require.Equal(unsafeTip.BlockRef, sys.L2EL.UnsafeHead().BlockRef) + + // Make sure the unsafe only CL can still advance unsafe + target := unsafeTipNum + 3 + sys.L2ELReadOnly.Reached(eth.Unsafe, target, 3) + for i := unsafeTipNum + 1; i <= target; i++ { + sys.L2CL.SignalTarget(sys.L2ELReadOnly, i) + } + sys.L2EL.Reached(eth.Unsafe, target, 5) + sys.L2CL.Reached(types.LocalUnsafe, target, 5) + + // Check unsafe gap is closed + target = unsafeTipNum + 9 + sys.L2ELReadOnly.Reached(eth.Unsafe, target, 6) + for i := unsafeTipNum + 6; i <= target; i++ { + sys.L2CL.SignalTarget(sys.L2ELReadOnly, i) + } + sys.L2EL.Reached(eth.Unsafe, target, 5) + sys.L2CL.Reached(types.LocalUnsafe, target, 5) +} diff --git a/op-challenger/README.md b/op-challenger/README.md index 84d7b53ccda..d217402479b 100644 --- a/op-challenger/README.md +++ b/op-challenger/README.md @@ -35,7 +35,7 @@ Run the `op-challenger` with: ```shell DISPUTE_GAME_FACTORY=$(jq -r .DisputeGameFactoryProxy .devnet/addresses.json) ./op-challenger/bin/op-challenger \ - --trace-type cannon \ + --game-types cannon \ --l1-eth-rpc http://localhost:8545 \ --rollup-rpc http://localhost:9546 \ --game-factory-address $DISPUTE_GAME_FACTORY \ @@ -99,8 +99,7 @@ in the L2 output oracle. * `L2_BLOCK_NUM` the L2 block number the proposed output root is from. * `SIGNER_ARGS` arguments to specify the key to sign transactions with (e.g `--private-key`) -Optionally, you may specify the game type (aka "trace type") using the `--trace-type` -flag, which is set to the cannon trace type by default. +Optionally, you may override the game types to support using the `--game-types` flag. For known networks, the `--game-factory-address` option can be replaced by `--network`. See the `--help` output for a list of predefined networks. @@ -226,10 +225,10 @@ configured with multiple different prestates. This allows testing both the curre the fault proofs virtual machine used by the trace provider. The same CLI options as `op-challenger` itself are supported to configure the trace providers. The additional `--run` -option allows specifying which prestates to use. The format is `traceType/name/prestateHash` where traceType is the -trace type to use with the prestate (e.g cannon or asterisc-kona), name is an arbitrary name for the prestate to use +option allows specifying which prestates to use. The format is `gameType/name/prestateHash` where gameType is the +game type to use with the prestate (e.g cannon or asterisc-kona), name is an arbitrary name for the prestate to use when reporting metrics and prestateHash is the hex encoded absolute prestate commitment to use. If name is omitted the -trace type name is used. If the prestateHash is omitted, the absolute prestate hash used for new games on-chain is used. +game type name is used. If the prestateHash is omitted, the absolute prestate hash used for new games on-chain is used. For example to run both the production cannon prestate and a custom prestate, use `--run cannon,cannon/next-prestate/0x03c1f0d45248190f80430a4c31e24f8108f05f80ff8b16ecb82d20df6b1b43f3`. diff --git a/op-challenger/cmd/create_game.go b/op-challenger/cmd/create_game.go index 073ecff1959..71159e34582 100644 --- a/op-challenger/cmd/create_game.go +++ b/op-challenger/cmd/create_game.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/flags" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/tools" opservice "github.com/ethereum-optimism/optimism/op-service" oplog "github.com/ethereum-optimism/optimism/op-service/log" @@ -22,7 +22,7 @@ var ( Name: "game-type", Usage: "Game type to create (numeric values).", EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "TRACE_TYPE"), - Value: types.CannonGameType.String(), + Value: gameTypes.CannonGameType.String(), } OutputRootFlag = &cli.StringFlag{ Name: "output-root", diff --git a/op-challenger/cmd/main_test.go b/op-challenger/cmd/main_test.go index 0163da5c091..2e5ae1652b3 100644 --- a/op-challenger/cmd/main_test.go +++ b/op-challenger/cmd/main_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum/go-ethereum/superchain" "github.com/stretchr/testify/require" @@ -14,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-challenger/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/txmgr" ) @@ -41,13 +41,13 @@ var ( func TestLogLevel(t *testing.T) { t.Run("RejectInvalid", func(t *testing.T) { - verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs(types.TraceTypeAlphabet, "--log.level=foo")) + verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs(gameTypes.AlphabetGameType, "--log.level=foo")) }) for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} { lvl := lvl t.Run("AcceptValid_"+lvl, func(t *testing.T) { - logger, _, err := dryRunWithArgs(addRequiredArgs(types.TraceTypeAlphabet, "--log.level", lvl)) + logger, _, err := dryRunWithArgs(addRequiredArgs(gameTypes.AlphabetGameType, "--log.level", lvl)) require.NoError(t, err) require.NotNil(t, logger) }) @@ -57,30 +57,30 @@ func TestLogLevel(t *testing.T) { func TestL2Experimental(t *testing.T) { t.Run("Valid", func(t *testing.T) { url := "http://example.com:8888" - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--l2-experimental-eth-rpc="+url)) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--l2-experimental-eth-rpc="+url)) require.Equal(t, url, cfg.Cannon.L2Experimental) }) } func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet)) - defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, rollupRpc, l2EthRpc, datadir, types.TraceTypeAlphabet) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType)) + defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, rollupRpc, l2EthRpc, datadir, gameTypes.AlphabetGameType) require.Equal(t, defaultCfg, cfg) } func TestDefaultConfigIsValid(t *testing.T) { - cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, rollupRpc, l2EthRpc, datadir, types.TraceTypeAlphabet) + cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, rollupRpc, l2EthRpc, datadir, gameTypes.AlphabetGameType) require.NoError(t, cfg.Check()) } func TestL1ETHRPCAddress(t *testing.T) { t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag l1-eth-rpc is required", addRequiredArgsExcept(types.TraceTypeAlphabet, "--l1-eth-rpc")) + verifyArgsInvalid(t, "flag l1-eth-rpc is required", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--l1-eth-rpc")) }) t.Run("Valid", func(t *testing.T) { url := "http://example.com:8888" - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--l1-eth-rpc", "--l1-eth-rpc="+url)) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--l1-eth-rpc", "--l1-eth-rpc="+url)) require.Equal(t, url, cfg.L1EthRpc) require.Equal(t, url, cfg.TxMgrConfig.L1RPCURL) }) @@ -88,145 +88,154 @@ func TestL1ETHRPCAddress(t *testing.T) { func TestL1Beacon(t *testing.T) { t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag l1-beacon is required", addRequiredArgsExcept(types.TraceTypeAlphabet, "--l1-beacon")) + verifyArgsInvalid(t, "flag l1-beacon is required", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--l1-beacon")) }) t.Run("Valid", func(t *testing.T) { url := "http://example.com:8888" - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--l1-beacon", "--l1-beacon="+url)) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--l1-beacon", "--l1-beacon="+url)) require.Equal(t, url, cfg.L1Beacon) }) } func TestOpSupervisor(t *testing.T) { t.Run("RequiredForSuperCannon", func(t *testing.T) { - verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(types.TraceTypeSuperCannon, "--supervisor-rpc")) + verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(gameTypes.SuperCannonGameType, "--supervisor-rpc")) }) t.Run("RequiredForSuperPermissioned", func(t *testing.T) { - verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(types.TraceTypeSuperPermissioned, "--supervisor-rpc")) + verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(gameTypes.SuperPermissionedGameType, "--supervisor-rpc")) }) t.Run("RequiredForSuperCannonKona", func(t *testing.T) { - verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(types.TraceTypeSuperCannonKona, "--supervisor-rpc")) + verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(gameTypes.SuperCannonKonaGameType, "--supervisor-rpc")) }) t.Run("RequiredForSuperAsteriscKona", func(t *testing.T) { - verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(types.TraceTypeSuperAsteriscKona, "--supervisor-rpc")) + verifyArgsInvalid(t, "flag supervisor-rpc is required", addRequiredArgsExcept(gameTypes.SuperAsteriscKonaGameType, "--supervisor-rpc")) }) - for _, traceType := range types.TraceTypes { - traceType := traceType - if traceType == types.TraceTypeSuperCannon || traceType == types.TraceTypeSuperPermissioned || traceType == types.TraceTypeSuperAsteriscKona || traceType == types.TraceTypeSuperCannonKona { + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType + if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType || gameType == gameTypes.SuperAsteriscKonaGameType || gameType == gameTypes.SuperCannonKonaGameType { continue } - t.Run("NotRequiredForTraceType-"+traceType.String(), func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--supervisor-rpc")) + t.Run("NotRequiredForGameType-"+gameType.String(), func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(gameType, "--supervisor-rpc")) }) } t.Run("Valid-SuperCannon", func(t *testing.T) { url := "http://localhost/supervisor" - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeSuperCannon, "--supervisor-rpc", "--supervisor-rpc", url)) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.SuperCannonGameType, "--supervisor-rpc", "--supervisor-rpc", url)) require.Equal(t, url, cfg.SupervisorRPC) }) t.Run("Valid-SuperPermissioned", func(t *testing.T) { url := "http://localhost/supervisor" - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeSuperPermissioned, "--supervisor-rpc", "--supervisor-rpc", url)) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.SuperPermissionedGameType, "--supervisor-rpc", "--supervisor-rpc", url)) require.Equal(t, url, cfg.SupervisorRPC) }) t.Run("Valid-SuperCannonKona", func(t *testing.T) { url := "http://localhost/supervisor" - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeSuperCannonKona, "--supervisor-rpc", "--supervisor-rpc", url)) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.SuperCannonKonaGameType, "--supervisor-rpc", "--supervisor-rpc", url)) require.Equal(t, url, cfg.SupervisorRPC) }) t.Run("Valid-SuperAsteriscKona", func(t *testing.T) { url := "http://localhost/supervisor" - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeSuperAsteriscKona, "--supervisor-rpc", "--supervisor-rpc", url)) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.SuperAsteriscKonaGameType, "--supervisor-rpc", "--supervisor-rpc", url)) require.Equal(t, url, cfg.SupervisorRPC) }) } -func TestTraceType(t *testing.T) { +func TestGameTypes(t *testing.T) { t.Run("Default", func(t *testing.T) { - expectedDefault := []types.TraceType{types.TraceTypeCannon, types.TraceTypeAsteriscKona, types.TraceTypeCannonKona} - cfg := configForArgs(t, addRequiredArgsForMultipleTracesExcept(expectedDefault, "--trace-type")) - require.Equal(t, expectedDefault, cfg.TraceTypes) + expectedDefault := []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.AsteriscKonaGameType, gameTypes.CannonKonaGameType} + cfg := configForArgs(t, addRequiredArgsForMultipleGameTypesExcept(expectedDefault, "--game-types")) + require.Equal(t, expectedDefault, cfg.GameTypes) }) - for _, traceType := range types.TraceTypes { - traceType := traceType - t.Run("Valid_"+traceType.String(), func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType)) - require.Equal(t, []types.TraceType{traceType}, cfg.TraceTypes) + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType + t.Run("Valid_"+gameType.String(), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs(gameType)) + require.Equal(t, []gameTypes.GameType{gameType}, cfg.GameTypes) }) } t.Run("Invalid", func(t *testing.T) { - verifyArgsInvalid(t, "unknown trace type: \"foo\"", addRequiredArgsExcept(types.TraceTypeAlphabet, "--trace-type", "--trace-type=foo")) + verifyArgsInvalid(t, "unknown game type: \"foo\"", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-types", "--game-types=foo")) }) + + // Check we provide an alias for --trace-type to preserve backwards compatibility + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType + t.Run("TraceTypeAlias-"+gameType.String(), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--game-types", "--trace-type", gameType.String())) + require.Equal(t, []gameTypes.GameType{gameType}, cfg.GameTypes) + }) + } } -func TestMultipleTraceTypes(t *testing.T) { +func TestMultipleGameTypes(t *testing.T) { t.Run("WithAllOptions", func(t *testing.T) { - argsMap := requiredArgs(types.TraceTypeCannon) + argsMap := requiredArgs(gameTypes.CannonGameType) // Add Asterisc required flags addRequiredAsteriscArgs(argsMap) args := toArgList(argsMap) - // Add extra trace types (cannon is already specified) + // Add extra game types (cannon is already specified) args = append(args, - "--trace-type", types.TraceTypeAlphabet.String()) + "--game-types", gameTypes.AlphabetGameType.String()) args = append(args, - "--trace-type", types.TraceTypePermissioned.String()) + "--game-types", gameTypes.PermissionedGameType.String()) args = append(args, - "--trace-type", types.TraceTypeAsterisc.String()) + "--game-types", gameTypes.AsteriscGameType.String()) cfg := configForArgs(t, args) - require.Equal(t, []types.TraceType{types.TraceTypeCannon, types.TraceTypeAlphabet, types.TraceTypePermissioned, types.TraceTypeAsterisc}, cfg.TraceTypes) + require.Equal(t, []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.AlphabetGameType, gameTypes.PermissionedGameType, gameTypes.AsteriscGameType}, cfg.GameTypes) }) t.Run("WithSomeOptions", func(t *testing.T) { - argsMap := requiredArgs(types.TraceTypeCannon) + argsMap := requiredArgs(gameTypes.CannonGameType) args := toArgList(argsMap) - // Add extra trace types (cannon is already specified) + // Add extra game types (cannon is already specified) args = append(args, - "--trace-type", types.TraceTypeAlphabet.String()) + "--game-types", gameTypes.AlphabetGameType.String()) cfg := configForArgs(t, args) - require.Equal(t, []types.TraceType{types.TraceTypeCannon, types.TraceTypeAlphabet}, cfg.TraceTypes) + require.Equal(t, []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.AlphabetGameType}, cfg.GameTypes) }) t.Run("SpecifySameOptionMultipleTimes", func(t *testing.T) { - argsMap := requiredArgs(types.TraceTypeCannon) + argsMap := requiredArgs(gameTypes.CannonGameType) args := toArgList(argsMap) - // Add cannon trace type again - args = append(args, "--trace-type", types.TraceTypeCannon.String()) + // Add cannon game type again + args = append(args, "--game-types", gameTypes.CannonGameType.String()) // We're fine with the same option being listed multiple times, just deduplicate them. cfg := configForArgs(t, args) - require.Equal(t, []types.TraceType{types.TraceTypeCannon}, cfg.TraceTypes) + require.Equal(t, []gameTypes.GameType{gameTypes.CannonGameType}, cfg.GameTypes) }) } func TestGameFactoryAddress(t *testing.T) { t.Run("RequiredWhenNetworkNotSupplied", func(t *testing.T) { - verifyArgsInvalid(t, "flag game-factory-address or network is required", addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address")) + verifyArgsInvalid(t, "flag game-factory-address or network is required", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address")) }) t.Run("RequiredWhenMultipleNetworksSuppliedWithDifferentFactories", func(t *testing.T) { - verifyArgsInvalid(t, "specified networks use different dispute game factories, flag game-factory-address required", addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--network", "op-sepolia,op-mainnet")) + verifyArgsInvalid(t, "specified networks use different dispute game factories, flag game-factory-address required", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address", "--network", "op-sepolia,op-mainnet")) }) t.Run("Valid", func(t *testing.T) { addr := common.Address{0xbb, 0xcc, 0xdd} - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--game-factory-address="+addr.Hex())) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address", "--game-factory-address="+addr.Hex())) require.Equal(t, addr, cfg.GameFactoryAddress) }) t.Run("Invalid", func(t *testing.T) { - verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--game-factory-address=foo")) + verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address", "--game-factory-address=foo")) }) t.Run("OverridesNetwork", func(t *testing.T) { addr := common.Address{0xbb, 0xcc, 0xdd} - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--game-factory-address", addr.Hex(), "--network", "op-sepolia")) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address", "--game-factory-address", addr.Hex(), "--network", "op-sepolia")) require.Equal(t, addr, cfg.GameFactoryAddress) }) } @@ -238,17 +247,17 @@ func TestNetwork(t *testing.T) { require.NoError(t, err) opSepoliaCfg, err := opSepolia.Config() require.NoError(t, err) - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--network=op-sepolia")) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address", "--network=op-sepolia")) require.EqualValues(t, *opSepoliaCfg.Addresses.DisputeGameFactoryProxy, cfg.GameFactoryAddress) }) t.Run("UnknownNetwork", func(t *testing.T) { - verifyArgsInvalid(t, "unknown chain: not-a-network", addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--network=not-a-network")) + verifyArgsInvalid(t, "unknown chain: not-a-network", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address", "--network=not-a-network")) }) t.Run("ChainIDAllowedWhenGameFactoryAddressSupplied", func(t *testing.T) { addr := common.Address{0xbb, 0xcc, 0xdd} - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-factory-address", "--network=1234", "--game-factory-address="+addr.Hex())) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-factory-address", "--network=1234", "--game-factory-address="+addr.Hex())) require.Equal(t, addr, cfg.GameFactoryAddress) require.Equal(t, []string{"1234"}, cfg.Cannon.Networks) }) @@ -256,31 +265,31 @@ func TestNetwork(t *testing.T) { func TestGameAllowlist(t *testing.T) { t.Run("Optional", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-allowlist")) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-allowlist")) require.NoError(t, cfg.Check()) }) t.Run("Valid", func(t *testing.T) { addr := common.Address{0xbb, 0xcc, 0xdd} - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-allowlist", "--game-allowlist="+addr.Hex())) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-allowlist", "--game-allowlist="+addr.Hex())) require.Contains(t, cfg.GameAllowlist, addr) }) t.Run("Invalid", func(t *testing.T) { - verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept(types.TraceTypeAlphabet, "--game-allowlist", "--game-allowlist=foo")) + verifyArgsInvalid(t, "invalid address: foo", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--game-allowlist", "--game-allowlist=foo")) }) } func TestTxManagerFlagsSupported(t *testing.T) { // Not a comprehensive list of flags, just enough to sanity check the txmgr.CLIFlags were defined - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--"+txmgr.NumConfirmationsFlagName, "7")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--"+txmgr.NumConfirmationsFlagName, "7")) require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations) } func TestMaxConcurrency(t *testing.T) { t.Run("Valid", func(t *testing.T) { expected := uint(345) - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--max-concurrency", "345")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--max-concurrency", "345")) require.Equal(t, expected, cfg.MaxConcurrency) }) @@ -288,26 +297,26 @@ func TestMaxConcurrency(t *testing.T) { verifyArgsInvalid( t, "invalid value \"abc\" for flag -max-concurrency", - addRequiredArgs(types.TraceTypeAlphabet, "--max-concurrency", "abc")) + addRequiredArgs(gameTypes.AlphabetGameType, "--max-concurrency", "abc")) }) t.Run("Zero", func(t *testing.T) { verifyArgsInvalid( t, "max-concurrency must not be 0", - addRequiredArgs(types.TraceTypeAlphabet, "--max-concurrency", "0")) + addRequiredArgs(gameTypes.AlphabetGameType, "--max-concurrency", "0")) }) } func TestMaxPendingTx(t *testing.T) { t.Run("Valid", func(t *testing.T) { expected := uint64(345) - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--max-pending-tx", "345")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--max-pending-tx", "345")) require.Equal(t, expected, cfg.MaxPendingTx) }) t.Run("Zero", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--max-pending-tx", "0")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--max-pending-tx", "0")) require.Equal(t, uint64(0), cfg.MaxPendingTx) }) @@ -315,19 +324,19 @@ func TestMaxPendingTx(t *testing.T) { verifyArgsInvalid( t, "invalid value \"abc\" for flag -max-pending-tx", - addRequiredArgs(types.TraceTypeAlphabet, "--max-pending-tx", "abc")) + addRequiredArgs(gameTypes.AlphabetGameType, "--max-pending-tx", "abc")) }) } func TestPollInterval(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeCannon)) + cfg := configForArgs(t, addRequiredArgs(gameTypes.CannonGameType)) require.Equal(t, config.DefaultPollInterval, cfg.PollInterval) }) t.Run("Valid", func(t *testing.T) { expected := 100 * time.Second - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--http-poll-interval", "100s")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--http-poll-interval", "100s")) require.Equal(t, expected, cfg.PollInterval) }) @@ -335,18 +344,18 @@ func TestPollInterval(t *testing.T) { verifyArgsInvalid( t, "invalid value \"abc\" for flag -http-poll-interval", - addRequiredArgs(types.TraceTypeAlphabet, "--http-poll-interval", "abc")) + addRequiredArgs(gameTypes.AlphabetGameType, "--http-poll-interval", "abc")) }) } func TestMinUpdateInterval(t *testing.T) { t.Run("DefaultsToZero", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeCannon)) + cfg := configForArgs(t, addRequiredArgs(gameTypes.CannonGameType)) require.Equal(t, time.Duration(0), cfg.MinUpdateInterval) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--min-update-interval", "10m")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--min-update-interval", "10m")) require.Equal(t, 10*time.Minute, cfg.MinUpdateInterval) }) @@ -354,175 +363,175 @@ func TestMinUpdateInterval(t *testing.T) { verifyArgsInvalid( t, "invalid value \"abc\" for flag -min-update-interval", - addRequiredArgs(types.TraceTypeAlphabet, "--min-update-interval", "abc")) + addRequiredArgs(gameTypes.AlphabetGameType, "--min-update-interval", "abc")) }) } func TestAsteriscOpProgramRequiredArgs(t *testing.T) { - traceType := types.TraceTypeAsterisc - t.Run(fmt.Sprintf("TestAsteriscServer-%v", traceType), func(t *testing.T) { + gameType := gameTypes.AsteriscGameType + t.Run(fmt.Sprintf("TestAsteriscServer-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-server")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-server")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag asterisc-server is required", addRequiredArgsExcept(traceType, "--asterisc-server")) + verifyArgsInvalid(t, "flag asterisc-server is required", addRequiredArgsExcept(gameType, "--asterisc-server")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-server", "--asterisc-server=./op-program")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--asterisc-server", "--asterisc-server=./op-program")) require.Equal(t, "./op-program", cfg.Asterisc.Server) }) }) - t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestate-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestate-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-prestate")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-prestate")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-prestate")) + verifyArgsInvalid(t, "flag prestates-url/asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExcept(gameType, "--asterisc-prestate")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-prestate", "--asterisc-prestate=./pre.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--asterisc-prestate", "--asterisc-prestate=./pre.json")) require.Equal(t, "./pre.json", cfg.AsteriscAbsolutePreState) }) }) - t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", gameType), func(t *testing.T) { allPrestateOptions := []string{"--prestates-url", "--asterisc-prestates-url", "--asterisc-prestate"} t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExceptArr(types.TraceTypeAlphabet, allPrestateOptions)) + configForArgs(t, addRequiredArgsExceptArr(gameTypes.AlphabetGameType, allPrestateOptions)) }) t.Run("NotRequiredIfAsteriscPrestatesBaseURLSet", func(t *testing.T) { - configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-prestates-url=http://localhost/foo")) + configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--asterisc-prestates-url=http://localhost/foo")) }) t.Run("AsteriscPrestatesBaseURLTakesPrecedence", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) + cfg := configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--asterisc-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) require.Equal(t, "http://localhost/foo", cfg.AsteriscAbsolutePreStateBaseURL.String()) }) t.Run("RequiredIfAsteriscPrestatesBaseURLNotSet", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExceptArr(traceType, allPrestateOptions)) + verifyArgsInvalid(t, "flag prestates-url/asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExceptArr(gameType, allPrestateOptions)) }) t.Run("Invalid", func(t *testing.T) { - verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=:foo/bar")) + verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(gameType, allPrestateOptions, "--prestates-url=:foo/bar")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=http://localhost/foo")) + cfg := configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--prestates-url=http://localhost/foo")) require.Equal(t, "http://localhost/foo", cfg.AsteriscAbsolutePreStateBaseURL.String()) }) }) - t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestateBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestateBaseURL-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-prestates-url")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-prestates-url")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-prestate")) + verifyArgsInvalid(t, "flag prestates-url/asterisc-prestates-url or asterisc-prestate is required", addRequiredArgsExcept(gameType, "--asterisc-prestate")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-prestates-url", "--asterisc-prestates-url=http://localhost/bar")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--asterisc-prestates-url", "--asterisc-prestates-url=http://localhost/bar")) require.Equal(t, "http://localhost/bar", cfg.AsteriscAbsolutePreStateBaseURL.String()) }) }) } func TestAsteriscKonaRequiredArgs(t *testing.T) { - traceType := types.TraceTypeAsteriscKona - t.Run(fmt.Sprintf("TestAsteriscServer-%v", traceType), func(t *testing.T) { + gameType := gameTypes.AsteriscKonaGameType + t.Run(fmt.Sprintf("TestAsteriscServer-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-kona-server")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-kona-server")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag asterisc-kona-server is required", addRequiredArgsExcept(traceType, "--asterisc-kona-server")) + verifyArgsInvalid(t, "flag asterisc-kona-server is required", addRequiredArgsExcept(gameType, "--asterisc-kona-server")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-kona-server", "--asterisc-kona-server=./kona-host")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--asterisc-kona-server", "--asterisc-kona-server=./kona-host")) require.Equal(t, "./kona-host", cfg.AsteriscKona.Server) }) }) - t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestate-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestate-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-kona-prestate")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-kona-prestate")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-kona-prestate")) + verifyArgsInvalid(t, "flag prestates-url/asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(gameType, "--asterisc-kona-prestate")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-kona-prestate", "--asterisc-kona-prestate=./pre.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--asterisc-kona-prestate", "--asterisc-kona-prestate=./pre.json")) require.Equal(t, "./pre.json", cfg.AsteriscKonaAbsolutePreState) }) }) - t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestateBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscAbsolutePrestateBaseURL-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-kona-prestates-url")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-kona-prestates-url")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(traceType, "--asterisc-kona-prestate")) + verifyArgsInvalid(t, "flag prestates-url/asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExcept(gameType, "--asterisc-kona-prestate")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-kona-prestates-url", "--asterisc-kona-prestates-url=http://localhost/bar")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--asterisc-kona-prestates-url", "--asterisc-kona-prestates-url=http://localhost/bar")) require.Equal(t, "http://localhost/bar", cfg.AsteriscKonaAbsolutePreStateBaseURL.String()) }) }) - t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", gameType), func(t *testing.T) { allPrestateOptions := []string{"--prestates-url", "--asterisc-kona-prestates-url", "--asterisc-kona-prestate"} t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExceptArr(types.TraceTypeAlphabet, allPrestateOptions)) + configForArgs(t, addRequiredArgsExceptArr(gameTypes.AlphabetGameType, allPrestateOptions)) }) t.Run("NotRequiredIfAsteriscKonaPrestatesBaseURLSet", func(t *testing.T) { - configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-kona-prestates-url=http://localhost/foo")) + configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--asterisc-kona-prestates-url=http://localhost/foo")) }) t.Run("AsteriscKonaPrestatesBaseURLTakesPrecedence", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--asterisc-kona-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) + cfg := configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--asterisc-kona-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) require.Equal(t, "http://localhost/foo", cfg.AsteriscKonaAbsolutePreStateBaseURL.String()) }) t.Run("RequiredIfAsteriscKonaPrestatesBaseURLNotSet", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExceptArr(traceType, allPrestateOptions)) + verifyArgsInvalid(t, "flag prestates-url/asterisc-kona-prestates-url or asterisc-kona-prestate is required", addRequiredArgsExceptArr(gameType, allPrestateOptions)) }) t.Run("Invalid", func(t *testing.T) { - verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=:foo/bar")) + verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(gameType, allPrestateOptions, "--prestates-url=:foo/bar")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=http://localhost/foo")) + cfg := configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--prestates-url=http://localhost/foo")) require.Equal(t, "http://localhost/foo", cfg.AsteriscKonaAbsolutePreStateBaseURL.String()) }) }) } // validateCustomNetworkFlagsProhibitedWithNetworkFlag ensures custom network flags are not used simultaneously with the network flag. -// It validates disallowed flag combinations for a given trace type and trace type prefix configuration. -func validateCustomNetworkFlagsProhibitedWithNetworkFlag(t *testing.T, traceType types.TraceType, traceTypeForFlagPrefix types.TraceType, customNetworkFlag string) { - expectedError := fmt.Sprintf("flag network can not be used with rollup-config/%v-rollup-config, l2-genesis/%v-l2-genesis, l1-genesis/%v-l1-genesis or %v", traceTypeForFlagPrefix, traceTypeForFlagPrefix, traceTypeForFlagPrefix, customNetworkFlag) +// It validates disallowed flag combinations for a given game type and game type prefix configuration. +func validateCustomNetworkFlagsProhibitedWithNetworkFlag(t *testing.T, gameType gameTypes.GameType, gameTypeForFlagPrefix gameTypes.GameType, customNetworkFlag string) { + expectedError := fmt.Sprintf("flag network can not be used with rollup-config/%v-rollup-config, l2-genesis/%v-l2-genesis, l1-genesis/%v-l1-genesis or %v", gameTypeForFlagPrefix, gameTypeForFlagPrefix, gameTypeForFlagPrefix, customNetworkFlag) // Test the custom l2 flag - t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndCustomL2Flag-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndCustomL2Flag-%v", gameType), func(t *testing.T) { verifyArgsInvalid( t, expectedError, - addRequiredArgs(traceType, fmt.Sprintf("--%v=true", customNetworkFlag))) + addRequiredArgs(gameType, fmt.Sprintf("--%v=true", customNetworkFlag))) }) // Now test flags with trace-specific permutations @@ -538,114 +547,114 @@ func validateCustomNetworkFlagsProhibitedWithNetworkFlag(t *testing.T, traceType postFix = "-withTraceSpecificPrefix" } - t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAnd%v-%v%v", testName, traceType, postFix), func(t *testing.T) { + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAnd%v-%v%v", testName, gameType, postFix), func(t *testing.T) { var prefix string if withTraceSpecificPrefix { - prefix = fmt.Sprintf("%v-", traceTypeForFlagPrefix) + prefix = fmt.Sprintf("%v-", gameTypeForFlagPrefix) } flagName := fmt.Sprintf("%v%v", prefix, flag) verifyArgsInvalid( t, expectedError, - addRequiredArgs(traceType, fmt.Sprintf("--%v=somevalue.json", flagName))) + addRequiredArgs(gameType, fmt.Sprintf("--%v=somevalue.json", flagName))) }) } } } func TestAsteriscBaseRequiredArgs(t *testing.T) { - for _, traceType := range []types.TraceType{types.TraceTypeAsterisc, types.TraceTypeAsteriscKona} { - traceType := traceType - t.Run(fmt.Sprintf("TestAsteriscBin-%v", traceType), func(t *testing.T) { + for _, gameType := range []gameTypes.GameType{gameTypes.AsteriscGameType, gameTypes.AsteriscKonaGameType} { + gameType := gameType + t.Run(fmt.Sprintf("TestAsteriscBin-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-bin")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-bin")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag asterisc-bin is required", addRequiredArgsExcept(traceType, "--asterisc-bin")) + verifyArgsInvalid(t, "flag asterisc-bin is required", addRequiredArgsExcept(gameType, "--asterisc-bin")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--asterisc-bin", "--asterisc-bin=./asterisc")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--asterisc-bin", "--asterisc-bin=./asterisc")) require.Equal(t, "./asterisc", cfg.Asterisc.VmBin) }) }) - t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestL2Rpc-%v", gameType), func(t *testing.T) { t.Run("RequiredForAsteriscTrace", func(t *testing.T) { - verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(traceType, "--l2-eth-rpc")) + verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(gameType, "--l2-eth-rpc")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType)) + cfg := configForArgs(t, addRequiredArgs(gameType)) require.Equal(t, []string{l2EthRpc}, cfg.L2Rpcs) }) }) - t.Run(fmt.Sprintf("TestAsteriscSnapshotFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscSnapshotFreq-%v", gameType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType)) + cfg := configForArgs(t, addRequiredArgs(gameType)) require.Equal(t, config.DefaultAsteriscSnapshotFreq, cfg.Asterisc.SnapshotFreq) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType, "--asterisc-snapshot-freq=1234")) + cfg := configForArgs(t, addRequiredArgs(gameType, "--asterisc-snapshot-freq=1234")) require.Equal(t, uint(1234), cfg.Asterisc.SnapshotFreq) }) t.Run("Invalid", func(t *testing.T) { verifyArgsInvalid(t, "invalid value \"abc\" for flag -asterisc-snapshot-freq", - addRequiredArgs(traceType, "--asterisc-snapshot-freq=abc")) + addRequiredArgs(gameType, "--asterisc-snapshot-freq=abc")) }) }) - t.Run(fmt.Sprintf("TestAsteriscInfoFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscInfoFreq-%v", gameType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType)) + cfg := configForArgs(t, addRequiredArgs(gameType)) require.Equal(t, config.DefaultAsteriscInfoFreq, cfg.Asterisc.InfoFreq) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType, "--asterisc-info-freq=1234")) + cfg := configForArgs(t, addRequiredArgs(gameType, "--asterisc-info-freq=1234")) require.Equal(t, uint(1234), cfg.Asterisc.InfoFreq) }) t.Run("Invalid", func(t *testing.T) { verifyArgsInvalid(t, "invalid value \"abc\" for flag -asterisc-info-freq", - addRequiredArgs(traceType, "--asterisc-info-freq=abc")) + addRequiredArgs(gameType, "--asterisc-info-freq=abc")) }) }) - t.Run(fmt.Sprintf("TestRequireEitherNetworkOrRollupAndGenesis-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestRequireEitherNetworkOrRollupAndGenesis-%v", gameType), func(t *testing.T) { verifyArgsInvalid( t, - fmt.Sprintf("flag network or rollup-config/%s-rollup-config and l2-genesis/%s-l2-genesis is required", traceType, traceType), - addRequiredArgsExcept(traceType, "--network")) + fmt.Sprintf("flag network or rollup-config/%s-rollup-config and l2-genesis/%s-l2-genesis is required", gameType, gameType), + addRequiredArgsExcept(gameType, "--network")) verifyArgsInvalid( t, - fmt.Sprintf("flag network or rollup-config/%s-rollup-config and l2-genesis/%s-l2-genesis is required", traceType, traceType), - addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json")) + fmt.Sprintf("flag network or rollup-config/%s-rollup-config and l2-genesis/%s-l2-genesis is required", gameType, gameType), + addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json")) verifyArgsInvalid( t, - fmt.Sprintf("flag network or rollup-config/%s-rollup-config and l2-genesis/%s-l2-genesis is required", traceType, traceType), - addRequiredArgsExcept(traceType, "--network", "--l2-genesis=gensis.json")) + fmt.Sprintf("flag network or rollup-config/%s-rollup-config and l2-genesis/%s-l2-genesis is required", gameType, gameType), + addRequiredArgsExcept(gameType, "--network", "--l2-genesis=gensis.json")) }) - validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, traceType, types.TraceTypeAsteriscKona, "asterisc-kona-l2-custom") + validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, gameType, gameTypes.AsteriscKonaGameType, "asterisc-kona-l2-custom") - t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestNetwork-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--network")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--network")) }) t.Run("NotRequiredWhenRollupAndGenesisSpecified", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--network", + configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) }) t.Run("NotRequiredWhenNetworkSpecified", func(t *testing.T) { - args := requiredArgs(traceType) + args := requiredArgs(gameType) delete(args, "--network") delete(args, "--game-factory-address") args["--network"] = "op-sepolia" @@ -654,29 +663,29 @@ func TestAsteriscBaseRequiredArgs(t *testing.T) { }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--network", testNetwork)) require.Equal(t, []string{testNetwork}, cfg.Asterisc.Networks) }) }) - t.Run(fmt.Sprintf("TestAsteriscRollupConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscRollupConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-rollup-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-rollup-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) require.Equal(t, []string{"rollup.json"}, cfg.Asterisc.RollupConfigPaths) }) }) - t.Run(fmt.Sprintf("TestL2Genesis-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestL2Genesis-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--l2-genesis")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--l2-genesis")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) require.Equal(t, []string{"genesis.json"}, cfg.Asterisc.L2GenesisPaths) }) }) @@ -684,77 +693,77 @@ func TestAsteriscBaseRequiredArgs(t *testing.T) { } func TestAlphabetRequiredArgs(t *testing.T) { - t.Run(fmt.Sprintf("TestL2Rpc-%v", types.TraceTypeAlphabet), func(t *testing.T) { + t.Run(fmt.Sprintf("TestL2Rpc-%v", gameTypes.AlphabetGameType), func(t *testing.T) { t.Run("RequiredForAlphabetTrace", func(t *testing.T) { - verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(types.TraceTypeAlphabet, "--l2-eth-rpc")) + verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(gameTypes.AlphabetGameType, "--l2-eth-rpc")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet)) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType)) require.Equal(t, []string{l2EthRpc}, cfg.L2Rpcs) }) }) } func TestCannonCustomConfigArgs(t *testing.T) { - for _, traceType := range []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned} { - traceType := traceType + for _, gameType := range []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.PermissionedGameType} { + gameType := gameType - t.Run(fmt.Sprintf("TestRequireEitherCannonNetworkOrRollupAndGenesis-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestRequireEitherCannonNetworkOrRollupAndGenesis-%v", gameType), func(t *testing.T) { verifyArgsInvalid( t, "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", - addRequiredArgsExcept(traceType, "--network")) + addRequiredArgsExcept(gameType, "--network")) verifyArgsInvalid( t, "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", - addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json")) verifyArgsInvalid( t, "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", - addRequiredArgsExcept(traceType, "--network", "--cannon-l2-genesis=gensis.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-l2-genesis=gensis.json")) }) - validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, traceType, types.TraceTypeCannon, "cannon-l2-custom") + validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, gameType, gameTypes.CannonGameType, "cannon-l2-custom") - t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestNetwork-%v", gameType), func(t *testing.T) { t.Run("NotRequiredWhenRollupAndGenesIsSpecified", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--network", + configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--network", testNetwork)) require.Equal(t, []string{testNetwork}, cfg.Cannon.Networks) }) }) - t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", traceType), func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", gameType), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-l2-custom")) require.True(t, cfg.Cannon.L2Custom) }) - t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-rollup-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-rollup-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) require.Equal(t, []string{"rollup.json"}, cfg.Cannon.RollupConfigPaths) }) }) - t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-l2-genesis")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-l2-genesis")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) require.Equal(t, []string{"genesis.json"}, cfg.Cannon.L2GenesisPaths) }) }) @@ -762,49 +771,49 @@ func TestCannonCustomConfigArgs(t *testing.T) { } func TestSuperCannonCustomConfigArgs(t *testing.T) { - for _, traceType := range []types.TraceType{types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned} { - traceType := traceType + for _, gameType := range []gameTypes.GameType{gameTypes.SuperCannonGameType, gameTypes.SuperPermissionedGameType} { + gameType := gameType - t.Run(fmt.Sprintf("TestRequireEitherCannonNetworkOrRollupAndGenesisAndDepset-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestRequireEitherCannonNetworkOrRollupAndGenesisAndDepset-%v", gameType), func(t *testing.T) { expectedErrorMessage := "flag network or rollup-config/cannon-rollup-config, l2-genesis/cannon-l2-genesis and depset-config/cannon-depset-config is required" // Missing all verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network")) + addRequiredArgsExcept(gameType, "--network")) // Missing l2-genesis verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-depset-config=depset.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-depset-config=depset.json")) // Missing rollup-config verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--cannon-l2-genesis=gensis.json", "--cannon-depset-config=depset.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-l2-genesis=gensis.json", "--cannon-depset-config=depset.json")) // Missing depset-config verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=gensis.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=gensis.json")) }) - validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, traceType, types.TraceTypeCannon, "cannon-l2-custom") + validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, gameType, gameTypes.CannonGameType, "cannon-l2-custom") - t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestNetwork-%v", gameType), func(t *testing.T) { t.Run("NotRequiredWhenRollupGenesisAndDepsetIsSpecified", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--network", + configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--network", testNetwork)) require.Equal(t, []string{testNetwork}, cfg.Cannon.Networks) }) }) - t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", traceType), func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", gameType), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json", @@ -812,36 +821,36 @@ func TestSuperCannonCustomConfigArgs(t *testing.T) { require.True(t, cfg.Cannon.L2Custom) }) - t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-rollup-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-rollup-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) require.Equal(t, []string{"rollup.json"}, cfg.Cannon.RollupConfigPaths) }) }) - t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-l2-genesis")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-l2-genesis")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) require.Equal(t, []string{"genesis.json"}, cfg.Cannon.L2GenesisPaths) }) }) - t.Run(fmt.Sprintf("TestCannonDepsetConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonDepsetConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-depset-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-depset-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) require.Equal(t, "depset.json", cfg.Cannon.DepsetConfigPath) }) }) @@ -849,48 +858,48 @@ func TestSuperCannonCustomConfigArgs(t *testing.T) { } func TestSuperCannonKonaCustomConfigArgs(t *testing.T) { - traceType := types.TraceTypeSuperCannonKona + gameType := gameTypes.SuperCannonKonaGameType - t.Run(fmt.Sprintf("TestRequireEitherCannonKonaNetworkOrRollupAndGenesisAndDepset-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestRequireEitherCannonKonaNetworkOrRollupAndGenesisAndDepset-%v", gameType), func(t *testing.T) { expectedErrorMessage := "flag network or rollup-config/cannon-kona-rollup-config, l2-genesis/cannon-kona-l2-genesis and depset-config/cannon-kona-depset-config is required" // Missing all verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network")) + addRequiredArgsExcept(gameType, "--network")) // Missing l2-genesis verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-depset-config=depset.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-depset-config=depset.json")) // Missing rollup-config verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--cannon-kona-l2-genesis=gensis.json", "--cannon-kona-depset-config=depset.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-kona-l2-genesis=gensis.json", "--cannon-kona-depset-config=depset.json")) // Missing depset-config verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=gensis.json")) + addRequiredArgsExcept(gameType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=gensis.json")) }) - validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, traceType, types.TraceTypeCannonKona, "cannon-kona-l2-custom") + validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, gameType, gameTypes.CannonKonaGameType, "cannon-kona-l2-custom") - t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestNetwork-%v", gameType), func(t *testing.T) { t.Run("NotRequiredWhenRollupGenesisAndDepsetIsSpecified", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--network", + configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=genesis.json", "--cannon-kona-depset-config=depset.json")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--network", testNetwork)) require.Equal(t, []string{testNetwork}, cfg.CannonKona.Networks) }) }) - t.Run(fmt.Sprintf("TestSetCannonKonaL2ChainId-%v", traceType), func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + t.Run(fmt.Sprintf("TestSetCannonKonaL2ChainId-%v", gameType), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=genesis.json", "--cannon-kona-depset-config=depset.json", @@ -898,85 +907,85 @@ func TestSuperCannonKonaCustomConfigArgs(t *testing.T) { require.True(t, cfg.CannonKona.L2Custom) }) - t.Run(fmt.Sprintf("TestCannonKonaRollupConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonKonaRollupConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-kona-rollup-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-kona-rollup-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=genesis.json", "--cannon-kona-depset-config=depset.json")) require.Equal(t, []string{"rollup.json"}, cfg.CannonKona.RollupConfigPaths) }) }) - t.Run(fmt.Sprintf("TestCannonKonaL2Genesis-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonKonaL2Genesis-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-kona-l2-genesis")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-kona-l2-genesis")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=genesis.json", "--cannon-kona-depset-config=depset.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=genesis.json", "--cannon-kona-depset-config=depset.json")) require.Equal(t, []string{"genesis.json"}, cfg.CannonKona.L2GenesisPaths) }) }) - t.Run(fmt.Sprintf("TestCannonKonaDepsetConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonKonaDepsetConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-kona-depset-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-kona-depset-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=genesis.json", "--cannon-kona-depset-config=depset.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--cannon-kona-rollup-config=rollup.json", "--cannon-kona-l2-genesis=genesis.json", "--cannon-kona-depset-config=depset.json")) require.Equal(t, "depset.json", cfg.CannonKona.DepsetConfigPath) }) }) } func TestSuperAsteriscKonaCustomConfigArgs(t *testing.T) { - for _, traceType := range []types.TraceType{types.TraceTypeSuperAsteriscKona} { - traceType := traceType + for _, gameType := range []gameTypes.GameType{gameTypes.SuperAsteriscKonaGameType} { + gameType := gameType - t.Run(fmt.Sprintf("TestRequireEitherAsteriscKonaNetworkOrRollupAndGenesisAndDepset-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestRequireEitherAsteriscKonaNetworkOrRollupAndGenesisAndDepset-%v", gameType), func(t *testing.T) { expectedErrorMessage := "flag network or rollup-config/asterisc-kona-rollup-config, l2-genesis/asterisc-kona-l2-genesis and depset-config/asterisc-kona-depset-config is required" // Missing all verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network")) + addRequiredArgsExcept(gameType, "--network")) // Missing l2-genesis verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-depset-config=depset.json")) + addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-depset-config=depset.json")) // Missing rollup-config verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--asterisc-kona-l2-genesis=gensis.json", "--asterisc-kona-depset-config=depset.json")) + addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-l2-genesis=gensis.json", "--asterisc-kona-depset-config=depset.json")) // Missing depset-config verifyArgsInvalid( t, expectedErrorMessage, - addRequiredArgsExcept(traceType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=gensis.json")) + addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=gensis.json")) }) - validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, traceType, types.TraceTypeAsteriscKona, "asterisc-kona-l2-custom") + validateCustomNetworkFlagsProhibitedWithNetworkFlag(t, gameType, gameTypes.AsteriscKonaGameType, "asterisc-kona-l2-custom") - t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestNetwork-%v", gameType), func(t *testing.T) { t.Run("NotRequiredWhenRollupGenesisAndDepsetIsSpecified", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--network", + configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=genesis.json", "--asterisc-kona-depset-config=depset.json")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--network", testNetwork)) require.Equal(t, []string{testNetwork}, cfg.AsteriscKona.Networks) }) }) - t.Run(fmt.Sprintf("TestSetAsteriscL2ChainId-%v", traceType), func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + t.Run(fmt.Sprintf("TestSetAsteriscL2ChainId-%v", gameType), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=genesis.json", "--asterisc-kona-depset-config=depset.json", @@ -984,36 +993,36 @@ func TestSuperAsteriscKonaCustomConfigArgs(t *testing.T) { require.True(t, cfg.AsteriscKona.L2Custom) }) - t.Run(fmt.Sprintf("TestAsteriscRollupConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscRollupConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-kona-rollup-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-kona-rollup-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=genesis.json", "--asterisc-kona-depset-config=depset.json")) require.Equal(t, []string{"rollup.json"}, cfg.AsteriscKona.RollupConfigPaths) }) }) - t.Run(fmt.Sprintf("TestAsteriscL2Genesis-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscL2Genesis-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-kona-l2-genesis")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-kona-l2-genesis")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=genesis.json", "--asterisc-kona-depset-config=depset.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=genesis.json", "--asterisc-kona-depset-config=depset.json")) require.Equal(t, []string{"genesis.json"}, cfg.AsteriscKona.L2GenesisPaths) }) }) - t.Run(fmt.Sprintf("TestAsteriscDepsetConfig-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscDepsetConfig-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--asterisc-kona-depset-config")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--asterisc-kona-depset-config")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=genesis.json", "--asterisc-kona-depset-config=depset.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--asterisc-kona-rollup-config=rollup.json", "--asterisc-kona-l2-genesis=genesis.json", "--asterisc-kona-depset-config=depset.json")) require.Equal(t, "depset.json", cfg.AsteriscKona.DepsetConfigPath) }) }) @@ -1021,167 +1030,167 @@ func TestSuperAsteriscKonaCustomConfigArgs(t *testing.T) { } func TestCannonRequiredArgs(t *testing.T) { - for _, traceType := range []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned, types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned} { - traceType := traceType - t.Run(fmt.Sprintf("TestCannonBin-%v", traceType), func(t *testing.T) { + for _, gameType := range []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.PermissionedGameType, gameTypes.SuperCannonGameType, gameTypes.SuperPermissionedGameType} { + gameType := gameType + t.Run(fmt.Sprintf("TestCannonBin-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-bin")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-bin")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag cannon-bin is required", addRequiredArgsExcept(traceType, "--cannon-bin")) + verifyArgsInvalid(t, "flag cannon-bin is required", addRequiredArgsExcept(gameType, "--cannon-bin")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-bin", "--cannon-bin=./cannon")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--cannon-bin", "--cannon-bin=./cannon")) require.Equal(t, "./cannon", cfg.Cannon.VmBin) }) }) - t.Run(fmt.Sprintf("TestCannonServer-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonServer-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-server")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-server")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag cannon-server is required", addRequiredArgsExcept(traceType, "--cannon-server")) + verifyArgsInvalid(t, "flag cannon-server is required", addRequiredArgsExcept(gameType, "--cannon-server")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-server", "--cannon-server=./op-program")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--cannon-server", "--cannon-server=./op-program")) require.Equal(t, "./op-program", cfg.Cannon.Server) }) }) - t.Run(fmt.Sprintf("TestCannonAbsolutePrestate-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonAbsolutePrestate-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-prestate")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-prestate")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate")) + verifyArgsInvalid(t, "flag prestates-url/cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(gameType, "--cannon-prestate")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-prestate", "--cannon-prestate=./pre.json")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--cannon-prestate", "--cannon-prestate=./pre.json")) require.Equal(t, "./pre.json", cfg.CannonAbsolutePreState) }) }) - t.Run(fmt.Sprintf("TestCannonPrestatesBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonPrestatesBaseURL-%v", gameType), func(t *testing.T) { t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-prestates-url")) + configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--cannon-prestates-url")) }) t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(traceType, "--cannon-prestate")) + verifyArgsInvalid(t, "flag prestates-url/cannon-prestates-url or cannon-prestate is required", addRequiredArgsExcept(gameType, "--cannon-prestate")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--cannon-prestates-url", "--cannon-prestates-url=http://localhost/foo")) + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--cannon-prestates-url", "--cannon-prestates-url=http://localhost/foo")) require.Equal(t, "http://localhost/foo", cfg.CannonAbsolutePreStateBaseURL.String()) }) }) - t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestPrestateBaseURL-%v", gameType), func(t *testing.T) { allPrestateOptions := []string{"--prestates-url", "--cannon-prestates-url", "--cannon-prestate"} t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExceptArr(types.TraceTypeAlphabet, allPrestateOptions)) + configForArgs(t, addRequiredArgsExceptArr(gameTypes.AlphabetGameType, allPrestateOptions)) }) t.Run("NotRequiredIfCannonPrestatesBaseURLSet", func(t *testing.T) { - configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--cannon-prestates-url=http://localhost/foo")) + configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--cannon-prestates-url=http://localhost/foo")) }) t.Run("CannonPrestatesBaseURLTakesPrecedence", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--cannon-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) + cfg := configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--cannon-prestates-url=http://localhost/foo", "--prestates-url=http://localhost/bar")) require.Equal(t, "http://localhost/foo", cfg.CannonAbsolutePreStateBaseURL.String()) }) t.Run("RequiredIfCannonPrestatesBaseURLNotSet", func(t *testing.T) { - verifyArgsInvalid(t, "flag prestates-url/cannon-prestates-url or cannon-prestate is required", addRequiredArgsExceptArr(traceType, allPrestateOptions)) + verifyArgsInvalid(t, "flag prestates-url/cannon-prestates-url or cannon-prestate is required", addRequiredArgsExceptArr(gameType, allPrestateOptions)) }) t.Run("Invalid", func(t *testing.T) { - verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=:foo/bar")) + verifyArgsInvalid(t, "invalid prestates-url (:foo/bar)", addRequiredArgsExceptArr(gameType, allPrestateOptions, "--prestates-url=:foo/bar")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExceptArr(traceType, allPrestateOptions, "--prestates-url=http://localhost/foo")) + cfg := configForArgs(t, addRequiredArgsExceptArr(gameType, allPrestateOptions, "--prestates-url=http://localhost/foo")) require.Equal(t, "http://localhost/foo", cfg.CannonAbsolutePreStateBaseURL.String()) }) }) - t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestL2Rpc-%v", gameType), func(t *testing.T) { t.Run("RequiredForCannonTrace", func(t *testing.T) { - verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(traceType, "--l2-eth-rpc")) + verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(gameType, "--l2-eth-rpc")) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType)) + cfg := configForArgs(t, addRequiredArgs(gameType)) require.Equal(t, []string{l2EthRpc}, cfg.L2Rpcs) }) }) - t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", gameType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType)) + cfg := configForArgs(t, addRequiredArgs(gameType)) require.Equal(t, config.DefaultCannonSnapshotFreq, cfg.Cannon.SnapshotFreq) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType, "--cannon-snapshot-freq=1234")) + cfg := configForArgs(t, addRequiredArgs(gameType, "--cannon-snapshot-freq=1234")) require.Equal(t, uint(1234), cfg.Cannon.SnapshotFreq) }) t.Run("Invalid", func(t *testing.T) { verifyArgsInvalid(t, "invalid value \"abc\" for flag -cannon-snapshot-freq", - addRequiredArgs(traceType, "--cannon-snapshot-freq=abc")) + addRequiredArgs(gameType, "--cannon-snapshot-freq=abc")) }) }) - t.Run(fmt.Sprintf("TestCannonInfoFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonInfoFreq-%v", gameType), func(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType)) + cfg := configForArgs(t, addRequiredArgs(gameType)) require.Equal(t, config.DefaultCannonInfoFreq, cfg.Cannon.InfoFreq) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(traceType, "--cannon-info-freq=1234")) + cfg := configForArgs(t, addRequiredArgs(gameType, "--cannon-info-freq=1234")) require.Equal(t, uint(1234), cfg.Cannon.InfoFreq) }) t.Run("Invalid", func(t *testing.T) { verifyArgsInvalid(t, "invalid value \"abc\" for flag -cannon-info-freq", - addRequiredArgs(traceType, "--cannon-info-freq=abc")) + addRequiredArgs(gameType, "--cannon-info-freq=abc")) }) }) } } func TestDepsetConfig(t *testing.T) { - for _, traceType := range types.TraceTypes { - if traceType == types.TraceTypeSuperCannon || traceType == types.TraceTypeSuperPermissioned { - t.Run("Required-"+traceType.String(), func(t *testing.T) { + for _, gameType := range gameTypes.SupportedGameTypes { + if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType { + t.Run("Required-"+gameType.String(), func(t *testing.T) { verifyArgsInvalid(t, "flag network or rollup-config/cannon-rollup-config, l2-genesis/cannon-l2-genesis and depset-config/cannon-depset-config is required", - addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) + addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) }) - } else if traceType == types.TraceTypeSuperCannonKona { - t.Run("Required-"+traceType.String(), func(t *testing.T) { + } else if gameType == gameTypes.SuperCannonKonaGameType { + t.Run("Required-"+gameType.String(), func(t *testing.T) { verifyArgsInvalid(t, "flag network or rollup-config/cannon-kona-rollup-config, l2-genesis/cannon-kona-l2-genesis and depset-config/cannon-kona-depset-config is required", - addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) + addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) }) - } else if traceType == types.TraceTypeSuperAsteriscKona { - t.Run("Required-"+traceType.String(), func(t *testing.T) { + } else if gameType == gameTypes.SuperAsteriscKonaGameType { + t.Run("Required-"+gameType.String(), func(t *testing.T) { verifyArgsInvalid(t, "flag network or rollup-config/asterisc-kona-rollup-config, l2-genesis/asterisc-kona-l2-genesis and depset-config/asterisc-kona-depset-config is required", - addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) + addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) }) } else { - t.Run("NotRequired-"+traceType.String(), func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) + t.Run("NotRequired-"+gameType.String(), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(gameType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) require.Equal(t, "", cfg.Cannon.DepsetConfigPath) }) } @@ -1189,89 +1198,89 @@ func TestDepsetConfig(t *testing.T) { } func TestDataDir(t *testing.T) { - for _, traceType := range types.TraceTypes { - traceType := traceType + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType - t.Run(fmt.Sprintf("RequiredFor-%v", traceType), func(t *testing.T) { - verifyArgsInvalid(t, "flag datadir is required", addRequiredArgsExcept(traceType, "--datadir")) + t.Run(fmt.Sprintf("RequiredFor-%v", gameType), func(t *testing.T) { + verifyArgsInvalid(t, "flag datadir is required", addRequiredArgsExcept(gameType, "--datadir")) }) } t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeCannon, "--datadir", "--datadir=/foo/bar/cannon")) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.CannonGameType, "--datadir", "--datadir=/foo/bar/cannon")) require.Equal(t, "/foo/bar/cannon", cfg.Datadir) }) } func TestRollupRpc(t *testing.T) { - for _, traceType := range types.TraceTypes { - traceType := traceType + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType - if traceType == types.TraceTypeSuperCannon || traceType == types.TraceTypeSuperPermissioned || traceType == types.TraceTypeSuperAsteriscKona || traceType == types.TraceTypeSuperCannonKona { - t.Run(fmt.Sprintf("NotRequiredFor-%v", traceType), func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--rollup-rpc")) + if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType || gameType == gameTypes.SuperAsteriscKonaGameType || gameType == gameTypes.SuperCannonKonaGameType { + t.Run(fmt.Sprintf("NotRequiredFor-%v", gameType), func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(gameType, "--rollup-rpc")) }) } else { - t.Run(fmt.Sprintf("RequiredFor-%v", traceType), func(t *testing.T) { - verifyArgsInvalid(t, "flag rollup-rpc is required", addRequiredArgsExcept(traceType, "--rollup-rpc")) + t.Run(fmt.Sprintf("RequiredFor-%v", gameType), func(t *testing.T) { + verifyArgsInvalid(t, "flag rollup-rpc is required", addRequiredArgsExcept(gameType, "--rollup-rpc")) }) } } t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeCannon)) + cfg := configForArgs(t, addRequiredArgs(gameTypes.CannonGameType)) require.Equal(t, rollupRpc, cfg.RollupRpc) }) } func TestGameWindow(t *testing.T) { t.Run("UsesDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet)) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType)) require.Equal(t, config.DefaultGameWindow, cfg.GameWindow) }) t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--game-window=1m")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--game-window=1m")) require.Equal(t, time.Minute, cfg.GameWindow) }) t.Run("ParsesDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--game-window=672h")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--game-window=672h")) require.Equal(t, config.DefaultGameWindow, cfg.GameWindow) }) } func TestUnsafeAllowInvalidPrestate(t *testing.T) { t.Run("DefaultsToFalse", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--unsafe-allow-invalid-prestate")) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--unsafe-allow-invalid-prestate")) require.False(t, cfg.AllowInvalidPrestate) }) t.Run("EnabledWithNoValue", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeCannon, "--unsafe-allow-invalid-prestate")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.CannonGameType, "--unsafe-allow-invalid-prestate")) require.True(t, cfg.AllowInvalidPrestate) }) t.Run("EnabledWithTrue", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeCannon, "--unsafe-allow-invalid-prestate=true")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.CannonGameType, "--unsafe-allow-invalid-prestate=true")) require.True(t, cfg.AllowInvalidPrestate) }) t.Run("DisabledWithFalse", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeCannon, "--unsafe-allow-invalid-prestate=false")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.CannonGameType, "--unsafe-allow-invalid-prestate=false")) require.False(t, cfg.AllowInvalidPrestate) }) } func TestAdditionalBondClaimants(t *testing.T) { t.Run("DefaultsToEmpty", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--additional-bond-claimants")) + cfg := configForArgs(t, addRequiredArgsExcept(gameTypes.AlphabetGameType, "--additional-bond-claimants")) require.Empty(t, cfg.AdditionalBondClaimants) }) t.Run("Valid-Single", func(t *testing.T) { claimant := common.Address{0xaa} - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--additional-bond-claimants", claimant.Hex())) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--additional-bond-claimants", claimant.Hex())) require.Contains(t, cfg.AdditionalBondClaimants, claimant) require.Len(t, cfg.AdditionalBondClaimants, 1) }) @@ -1280,7 +1289,7 @@ func TestAdditionalBondClaimants(t *testing.T) { claimant1 := common.Address{0xaa} claimant2 := common.Address{0xbb} claimant3 := common.Address{0xcc} - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--additional-bond-claimants", fmt.Sprintf("%v,%v,%v", claimant1.Hex(), claimant2.Hex(), claimant3.Hex()))) require.Contains(t, cfg.AdditionalBondClaimants, claimant1) require.Contains(t, cfg.AdditionalBondClaimants, claimant2) @@ -1290,25 +1299,25 @@ func TestAdditionalBondClaimants(t *testing.T) { t.Run("Invalid-Single", func(t *testing.T) { verifyArgsInvalid(t, "invalid additional claimant", - addRequiredArgs(types.TraceTypeAlphabet, "--additional-bond-claimants", "nope")) + addRequiredArgs(gameTypes.AlphabetGameType, "--additional-bond-claimants", "nope")) }) t.Run("Invalid-Multiple", func(t *testing.T) { claimant1 := common.Address{0xaa} claimant2 := common.Address{0xbb} verifyArgsInvalid(t, "invalid additional claimant", - addRequiredArgs(types.TraceTypeAlphabet, "--additional-bond-claimants", fmt.Sprintf("%v,nope,%v", claimant1.Hex(), claimant2.Hex()))) + addRequiredArgs(gameTypes.AlphabetGameType, "--additional-bond-claimants", fmt.Sprintf("%v,nope,%v", claimant1.Hex(), claimant2.Hex()))) }) } func TestSignerTLS(t *testing.T) { t.Run("EnabledByDefault", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet)) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType)) require.True(t, cfg.TxMgrConfig.SignerCLIConfig.TLSConfig.Enabled) }) t.Run("Disabled", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgs(types.TraceTypeAlphabet, "--signer.tls.enabled=false")) + cfg := configForArgs(t, addRequiredArgs(gameTypes.AlphabetGameType, "--signer.tls.enabled=false")) require.False(t, cfg.TxMgrConfig.SignerCLIConfig.TLSConfig.Enabled) }) } @@ -1340,35 +1349,35 @@ func dryRunWithArgs(cliArgs []string) (log.Logger, config.Config, error) { return logger, *cfg, err } -func addRequiredArgs(traceType types.TraceType, args ...string) []string { - req := requiredArgs(traceType) +func addRequiredArgs(gameType gameTypes.GameType, args ...string) []string { + req := requiredArgs(gameType) combined := toArgList(req) return append(combined, args...) } -func addRequiredArgsExcept(traceType types.TraceType, name string, optionalArgs ...string) []string { - req := requiredArgs(traceType) +func addRequiredArgsExcept(gameType gameTypes.GameType, name string, optionalArgs ...string) []string { + req := requiredArgs(gameType) delete(req, name) return append(toArgList(req), optionalArgs...) } -func addRequiredArgsForMultipleTracesExcept(traceType []types.TraceType, name string, optionalArgs ...string) []string { - req := requiredArgsMultiple(traceType) +func addRequiredArgsForMultipleGameTypesExcept(gameType []gameTypes.GameType, name string, optionalArgs ...string) []string { + req := requiredArgsMultiple(gameType) delete(req, name) return append(toArgList(req), optionalArgs...) } -func addRequiredArgsExceptArr(traceType types.TraceType, names []string, optionalArgs ...string) []string { - req := requiredArgs(traceType) +func addRequiredArgsExceptArr(gameType gameTypes.GameType, names []string, optionalArgs ...string) []string { + req := requiredArgs(gameType) for _, name := range names { delete(req, name) } return append(toArgList(req), optionalArgs...) } -func requiredArgsMultiple(traceType []types.TraceType) map[string]string { +func requiredArgsMultiple(gameType []gameTypes.GameType) map[string]string { args := make(map[string]string) - for _, t := range traceType { + for _, t := range gameType { for name, value := range requiredArgs(t) { args[name] = value } @@ -1376,31 +1385,31 @@ func requiredArgsMultiple(traceType []types.TraceType) map[string]string { return args } -func requiredArgs(traceType types.TraceType) map[string]string { +func requiredArgs(gameType gameTypes.GameType) map[string]string { args := map[string]string{ "--l1-eth-rpc": l1EthRpc, "--l1-beacon": l1Beacon, "--l2-eth-rpc": l2EthRpc, "--game-factory-address": gameFactoryAddressValue, - "--trace-type": traceType.String(), + "--game-types": gameType.String(), "--datadir": datadir, } - switch traceType { - case types.TraceTypeCannon, types.TraceTypePermissioned: + switch gameType { + case gameTypes.CannonGameType, gameTypes.PermissionedGameType: addRequiredCannonArgs(args) - case types.TraceTypeCannonKona: + case gameTypes.CannonKonaGameType: addRequiredCannonKonaArgs(args) - case types.TraceTypeAsterisc: + case gameTypes.AsteriscGameType: addRequiredAsteriscArgs(args) - case types.TraceTypeAsteriscKona: + case gameTypes.AsteriscKonaGameType: addRequiredAsteriscKonaArgs(args) - case types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned: + case gameTypes.SuperCannonGameType, gameTypes.SuperPermissionedGameType: addRequiredSuperCannonArgs(args) - case types.TraceTypeSuperCannonKona: + case gameTypes.SuperCannonKonaGameType: addRequiredSuperCannonKonaArgs(args) - case types.TraceTypeSuperAsteriscKona: + case gameTypes.SuperAsteriscKonaGameType: addRequiredSuperAsteriscKonaArgs(args) - case types.TraceTypeAlphabet, types.TraceTypeFast: + case gameTypes.AlphabetGameType, gameTypes.FastGameType: addRequiredOutputRootArgs(args) } return args diff --git a/op-challenger/cmd/run_trace.go b/op-challenger/cmd/run_trace.go index 07e74fdd79d..e2fee2e800f 100644 --- a/op-challenger/cmd/run_trace.go +++ b/op-challenger/cmd/run_trace.go @@ -4,11 +4,10 @@ import ( "context" "errors" "fmt" - "slices" "strings" "github.com/ethereum-optimism/optimism/op-challenger/flags" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/runner" opservice "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/cliapp" @@ -17,7 +16,6 @@ import ( ) var ( - ErrUnknownTraceType = errors.New("unknown trace type") ErrInvalidPrestateHash = errors.New("invalid prestate hash") ) @@ -40,9 +38,9 @@ func RunTrace(ctx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, er return nil, err } if len(runConfigs) == 0 { - // Default to running on-chain version of each enabled trace type - for _, traceType := range cfg.TraceTypes { - runConfigs = append(runConfigs, runner.RunConfig{TraceType: traceType}) + // Default to running on-chain version of each enabled game type + for _, gameType := range cfg.GameTypes { + runConfigs = append(runConfigs, runner.RunConfig{GameType: gameType}) } } return runner.NewRunner(logger, cfg, runConfigs), nil @@ -63,11 +61,11 @@ var RunTraceCommand = &cli.Command{ var ( RunTraceRunFlag = &cli.StringSliceFlag{ Name: "run", - Usage: "Specify a trace to run. Format is traceType/name/prestateHash where " + - "traceType is the trace type to use with the prestate (e.g cannon or asterisc-kona), " + + Usage: "Specify a trace to run. Format is gameType/name/prestateHash where " + + "gameType is the game type to use with the prestate (e.g cannon or asterisc-kona), " + "name is an arbitrary name for the prestate to use when reporting metrics and" + "prestateHash is the hex encoded absolute prestate commitment to use. " + - "If name is omitted the trace type name is used." + + "If name is omitted the game type name is used." + "If the prestateHash is omitted, the absolute prestate hash used for new games on-chain.", EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "RUN"), } @@ -91,14 +89,15 @@ func parseRunArg(arg string) (runner.RunConfig, error) { if len(opts) == 0 { return runner.RunConfig{}, fmt.Errorf("invalid run config %q", arg) } - cfg.TraceType = types.TraceType(opts[0]) - if !slices.Contains(types.TraceTypes, cfg.TraceType) { - return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", ErrUnknownTraceType, opts[0], arg) + gameType, err := gameTypes.SupportedGameTypeFromString(opts[0]) + if err != nil { + return runner.RunConfig{}, fmt.Errorf("%w %q for run config %q", err, opts[0], arg) } + cfg.GameType = gameType if len(opts) > 1 { cfg.Name = opts[1] } else { - cfg.Name = cfg.TraceType.String() + cfg.Name = cfg.GameType.String() } if len(opts) > 2 { if strings.HasPrefix(opts[2], "0x") { diff --git a/op-challenger/cmd/run_trace_test.go b/op-challenger/cmd/run_trace_test.go index 0e2512dd1a1..a2b5b664446 100644 --- a/op-challenger/cmd/run_trace_test.go +++ b/op-challenger/cmd/run_trace_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/runner" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -16,13 +16,13 @@ func TestParseRunArg(t *testing.T) { expected runner.RunConfig err error }{ - {arg: "unknown/test1/0x1234", err: ErrUnknownTraceType}, - {arg: "cannon", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: types.TraceTypeCannon.String()}}, - {arg: "asterisc", expected: runner.RunConfig{TraceType: types.TraceTypeAsterisc, Name: types.TraceTypeAsterisc.String()}}, - {arg: "cannon/test1", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1"}}, - {arg: "cannon/test1/0x1234", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1", Prestate: common.HexToHash("0x1234")}}, + {arg: "unknown/test1/0x1234", err: gameTypes.ErrUnknownGameType}, + {arg: "cannon", expected: runner.RunConfig{GameType: gameTypes.CannonGameType, Name: gameTypes.CannonGameType.String()}}, + {arg: "asterisc", expected: runner.RunConfig{GameType: gameTypes.AsteriscGameType, Name: gameTypes.AsteriscGameType.String()}}, + {arg: "cannon/test1", expected: runner.RunConfig{GameType: gameTypes.CannonGameType, Name: "test1"}}, + {arg: "cannon/test1/0x1234", expected: runner.RunConfig{GameType: gameTypes.CannonGameType, Name: "test1", Prestate: common.HexToHash("0x1234")}}, {arg: "cannon/test1/0xinvalid", err: ErrInvalidPrestateHash}, - {arg: "cannon/test1/develop.bin.gz", expected: runner.RunConfig{TraceType: types.TraceTypeCannon, Name: "test1", PrestateFilename: "develop.bin.gz"}}, + {arg: "cannon/test1/develop.bin.gz", expected: runner.RunConfig{GameType: gameTypes.CannonGameType, Name: "test1", PrestateFilename: "develop.bin.gz"}}, } for _, test := range tests { test := test diff --git a/op-challenger/config/config.go b/op-challenger/config/config.go index f73c51ebf69..469fba793f2 100644 --- a/op-challenger/config/config.go +++ b/op-challenger/config/config.go @@ -9,7 +9,7 @@ import ( "time" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" "github.com/ethereum-optimism/optimism/op-service/txmgr" @@ -17,7 +17,7 @@ import ( ) var ( - ErrMissingTraceType = errors.New("no supported trace types specified") + ErrMissingGameType = errors.New("no supported game types specified") ErrMissingDatadir = errors.New("missing datadir") ErrMaxConcurrencyZero = errors.New("max concurrency must not be 0") ErrMissingL2Rpc = errors.New("missing L2 rpc url") @@ -80,7 +80,7 @@ type Config struct { SelectiveClaimResolution bool // Whether to only resolve claims for the claimants in AdditionalBondClaimants union [TxSender.From()] - TraceTypes []types.TraceType // Type of traces supported + GameTypes []gameTypes.GameType // Type of games supported RollupRpc string // L2 Rollup RPC Url SupervisorRPC string // L2 supervisor RPC URL @@ -129,7 +129,7 @@ func NewInteropConfig( supervisorRpc string, l2Rpcs []string, datadir string, - supportedTraceTypes ...types.TraceType, + supportedGameTypes ...gameTypes.GameType, ) Config { return Config{ L1EthRpc: l1EthRpc, @@ -140,7 +140,7 @@ func NewInteropConfig( MaxConcurrency: uint(runtime.NumCPU()), PollInterval: DefaultPollInterval, - TraceTypes: supportedTraceTypes, + GameTypes: supportedGameTypes, MaxPendingTx: DefaultMaxPendingTx, @@ -151,7 +151,7 @@ func NewInteropConfig( Datadir: datadir, Cannon: vm.Config{ - VmType: types.TraceTypeCannon, + VmType: gameTypes.CannonGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: l2Rpcs, @@ -161,7 +161,7 @@ func NewInteropConfig( BinarySnapshots: true, }, CannonKona: vm.Config{ - VmType: types.TraceTypeCannonKona, + VmType: gameTypes.CannonKonaGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: l2Rpcs, @@ -171,7 +171,7 @@ func NewInteropConfig( BinarySnapshots: true, }, Asterisc: vm.Config{ - VmType: types.TraceTypeAsterisc, + VmType: gameTypes.AsteriscGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: l2Rpcs, @@ -180,7 +180,7 @@ func NewInteropConfig( BinarySnapshots: true, }, AsteriscKona: vm.Config{ - VmType: types.TraceTypeAsteriscKona, + VmType: gameTypes.AsteriscKonaGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: l2Rpcs, @@ -199,7 +199,7 @@ func NewConfig( l2RollupRpc string, l2EthRpc string, datadir string, - supportedTraceTypes ...types.TraceType, + supportedGameTypes ...gameTypes.GameType, ) Config { return Config{ L1EthRpc: l1EthRpc, @@ -210,7 +210,7 @@ func NewConfig( MaxConcurrency: uint(runtime.NumCPU()), PollInterval: DefaultPollInterval, - TraceTypes: supportedTraceTypes, + GameTypes: supportedGameTypes, MaxPendingTx: DefaultMaxPendingTx, @@ -221,7 +221,7 @@ func NewConfig( Datadir: datadir, Cannon: vm.Config{ - VmType: types.TraceTypeCannon, + VmType: gameTypes.CannonGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: []string{l2EthRpc}, @@ -231,7 +231,7 @@ func NewConfig( BinarySnapshots: true, }, CannonKona: vm.Config{ - VmType: types.TraceTypeCannonKona, + VmType: gameTypes.CannonKonaGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: []string{l2EthRpc}, @@ -241,7 +241,7 @@ func NewConfig( BinarySnapshots: true, }, Asterisc: vm.Config{ - VmType: types.TraceTypeAsterisc, + VmType: gameTypes.AsteriscGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: []string{l2EthRpc}, @@ -250,7 +250,7 @@ func NewConfig( BinarySnapshots: true, }, AsteriscKona: vm.Config{ - VmType: types.TraceTypeAsteriscKona, + VmType: gameTypes.AsteriscKonaGameType, L1: l1EthRpc, L1Beacon: l1BeaconApi, L2s: []string{l2EthRpc}, @@ -262,8 +262,8 @@ func NewConfig( } } -func (c Config) TraceTypeEnabled(t types.TraceType) bool { - return slices.Contains(c.TraceTypes, t) +func (c Config) GameTypeEnabled(t gameTypes.GameType) bool { + return slices.Contains(c.GameTypes, t) } func (c Config) Check() error { @@ -279,8 +279,8 @@ func (c Config) Check() error { if c.GameFactoryAddress == (common.Address{}) { return ErrMissingGameFactoryAddress } - if len(c.TraceTypes) == 0 { - return ErrMissingTraceType + if len(c.GameTypes) == 0 { + return ErrMissingGameType } if c.Datadir == "" { return ErrMissingDatadir @@ -288,7 +288,7 @@ func (c Config) Check() error { if c.MaxConcurrency == 0 { return ErrMaxConcurrencyZero } - if c.TraceTypeEnabled(types.TraceTypeSuperCannon) || c.TraceTypeEnabled(types.TraceTypeSuperPermissioned) { + if c.GameTypeEnabled(gameTypes.SuperCannonGameType) || c.GameTypeEnabled(gameTypes.SuperPermissionedGameType) { if c.SupervisorRPC == "" { return ErrMissingSupervisorRpc } @@ -300,7 +300,7 @@ func (c Config) Check() error { return err } } - if c.TraceTypeEnabled(types.TraceTypeCannon) || c.TraceTypeEnabled(types.TraceTypePermissioned) { + if c.GameTypeEnabled(gameTypes.CannonGameType) || c.GameTypeEnabled(gameTypes.PermissionedGameType) { if c.RollupRpc == "" { return ErrMissingRollupRpc } @@ -308,7 +308,7 @@ func (c Config) Check() error { return err } } - if c.TraceTypeEnabled(types.TraceTypeSuperCannonKona) { + if c.GameTypeEnabled(gameTypes.SuperCannonKonaGameType) { if c.SupervisorRPC == "" { return ErrMissingSupervisorRpc } @@ -320,7 +320,7 @@ func (c Config) Check() error { return err } } - if c.TraceTypeEnabled(types.TraceTypeCannonKona) { + if c.GameTypeEnabled(gameTypes.CannonKonaGameType) { if c.RollupRpc == "" { return ErrMissingRollupRpc } @@ -328,7 +328,7 @@ func (c Config) Check() error { return err } } - if c.TraceTypeEnabled(types.TraceTypeAsterisc) { + if c.GameTypeEnabled(gameTypes.AsteriscGameType) { if c.RollupRpc == "" { return ErrMissingRollupRpc } @@ -345,7 +345,7 @@ func (c Config) Check() error { return ErrMissingAsteriscInfoFreq } } - if c.TraceTypeEnabled(types.TraceTypeAsteriscKona) { + if c.GameTypeEnabled(gameTypes.AsteriscKonaGameType) { if c.RollupRpc == "" { return ErrMissingRollupRpc } @@ -353,7 +353,7 @@ func (c Config) Check() error { return err } } - if c.TraceTypeEnabled(types.TraceTypeSuperAsteriscKona) { + if c.GameTypeEnabled(gameTypes.SuperAsteriscKonaGameType) { if c.SupervisorRPC == "" { return ErrMissingSupervisorRpc } @@ -365,7 +365,7 @@ func (c Config) Check() error { return err } } - if c.TraceTypeEnabled(types.TraceTypeAlphabet) || c.TraceTypeEnabled(types.TraceTypeFast) { + if c.GameTypeEnabled(gameTypes.AlphabetGameType) || c.GameTypeEnabled(gameTypes.FastGameType) { if c.RollupRpc == "" { return ErrMissingRollupRpc } diff --git a/op-challenger/config/config_test.go b/op-challenger/config/config_test.go index a681b9d0451..3b1b0107b48 100644 --- a/op-challenger/config/config_test.go +++ b/op-challenger/config/config_test.go @@ -10,10 +10,10 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/txmgr" ) @@ -50,17 +50,17 @@ var ( validCannonKonaAbsolutePreStateBaseURL, _ = url.Parse("http://localhost/bar/") ) -var singleCannonTraceTypes = []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned} -var superCannonTraceTypes = []types.TraceType{types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned} -var allCannonTraceTypes []types.TraceType -var cannonKonaTraceTypes = []types.TraceType{types.TraceTypeCannonKona, types.TraceTypeSuperCannonKona} -var asteriscTraceTypes = []types.TraceType{types.TraceTypeAsterisc} -var asteriscKonaTraceTypes = []types.TraceType{types.TraceTypeAsteriscKona} -var superAsteriscKonaTraceTypes = []types.TraceType{types.TraceTypeSuperAsteriscKona} +var singleCannonGameTypes = []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.PermissionedGameType} +var superCannonGameTypes = []gameTypes.GameType{gameTypes.SuperCannonGameType, gameTypes.SuperPermissionedGameType} +var allCannonGameTypes []gameTypes.GameType +var cannonKonaGameTypes = []gameTypes.GameType{gameTypes.CannonKonaGameType, gameTypes.SuperCannonKonaGameType} +var asteriscGameTypes = []gameTypes.GameType{gameTypes.AsteriscGameType} +var asteriscKonaGameTypes = []gameTypes.GameType{gameTypes.AsteriscKonaGameType} +var superAsteriscKonaGameTypes = []gameTypes.GameType{gameTypes.SuperAsteriscKonaGameType} func init() { - allCannonTraceTypes = append(allCannonTraceTypes, singleCannonTraceTypes...) - allCannonTraceTypes = append(allCannonTraceTypes, superCannonTraceTypes...) + allCannonGameTypes = append(allCannonGameTypes, singleCannonGameTypes...) + allCannonGameTypes = append(allCannonGameTypes, superCannonGameTypes...) } func ensureExists(path string) error { @@ -150,34 +150,34 @@ func applyValidConfigForSuperAsteriscKona(t *testing.T, cfg *Config) { applyValidConfigForAsteriscKona(t, cfg) } -func validConfig(t *testing.T, traceType types.TraceType) Config { - cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validRollupRpc, validL2Rpc, validDatadir, traceType) - if traceType == types.TraceTypeSuperCannon || traceType == types.TraceTypeSuperPermissioned { +func validConfig(t *testing.T, gameType gameTypes.GameType) Config { + cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validRollupRpc, validL2Rpc, validDatadir, gameType) + if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType { applyValidConfigForSuperCannon(t, &cfg) } - if traceType == types.TraceTypeCannon || traceType == types.TraceTypePermissioned { + if gameType == gameTypes.CannonGameType || gameType == gameTypes.PermissionedGameType { applyValidConfigForCannon(t, &cfg) } - if traceType == types.TraceTypeCannonKona { + if gameType == gameTypes.CannonKonaGameType { applyValidConfigForCannonKona(t, &cfg) } - if traceType == types.TraceTypeSuperCannonKona { + if gameType == gameTypes.SuperCannonKonaGameType { applyValidConfigForSuperCannonKona(t, &cfg) } - if traceType == types.TraceTypeAsterisc { + if gameType == gameTypes.AsteriscGameType { applyValidConfigForAsterisc(t, &cfg) } - if traceType == types.TraceTypeAsteriscKona { + if gameType == gameTypes.AsteriscKonaGameType { applyValidConfigForAsteriscKona(t, &cfg) } - if traceType == types.TraceTypeSuperAsteriscKona { + if gameType == gameTypes.SuperAsteriscKonaGameType { applyValidConfigForSuperAsteriscKona(t, &cfg) } return cfg } -func validConfigWithNoNetworks(t *testing.T, traceType types.TraceType) Config { - cfg := validConfig(t, traceType) +func validConfigWithNoNetworks(t *testing.T, gameType gameTypes.GameType) Config { + cfg := validConfig(t, gameType) mutateVmConfig := func(cfg *vm.Config) { cfg.Networks = nil @@ -186,16 +186,16 @@ func validConfigWithNoNetworks(t *testing.T, traceType types.TraceType) Config { cfg.L1GenesisPath = "bar.json" cfg.DepsetConfigPath = "foo.json" } - if slices.Contains(allCannonTraceTypes, traceType) { + if slices.Contains(allCannonGameTypes, gameType) { mutateVmConfig(&cfg.Cannon) } - if slices.Contains(cannonKonaTraceTypes, traceType) { + if slices.Contains(cannonKonaGameTypes, gameType) { mutateVmConfig(&cfg.CannonKona) } - if slices.Contains(asteriscTraceTypes, traceType) { + if slices.Contains(asteriscGameTypes, gameType) { mutateVmConfig(&cfg.Asterisc) } - if slices.Contains(asteriscKonaTraceTypes, traceType) { + if slices.Contains(asteriscKonaGameTypes, gameType) { mutateVmConfig(&cfg.AsteriscKona) } return cfg @@ -203,10 +203,10 @@ func validConfigWithNoNetworks(t *testing.T, traceType types.TraceType) Config { // TestValidConfigIsValid checks that the config provided by validConfig is actually valid func TestValidConfigIsValid(t *testing.T) { - for _, traceType := range types.TraceTypes { - traceType := traceType - t.Run(traceType.String(), func(t *testing.T) { - err := validConfig(t, traceType).Check() + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType + t.Run(gameType.String(), func(t *testing.T) { + err := validConfig(t, gameType).Check() require.NoError(t, err) }) } @@ -214,160 +214,160 @@ func TestValidConfigIsValid(t *testing.T) { func TestTxMgrConfig(t *testing.T) { t.Run("Invalid", func(t *testing.T) { - config := validConfig(t, types.TraceTypeCannon) + config := validConfig(t, gameTypes.CannonGameType) config.TxMgrConfig = txmgr.CLIConfig{} require.Equal(t, config.Check().Error(), "must provide a L1 RPC url") }) } func TestL1EthRpcRequired(t *testing.T) { - config := validConfig(t, types.TraceTypeCannon) + config := validConfig(t, gameTypes.CannonGameType) config.L1EthRpc = "" require.ErrorIs(t, config.Check(), ErrMissingL1EthRPC) } func TestL1BeaconRequired(t *testing.T) { - config := validConfig(t, types.TraceTypeCannon) + config := validConfig(t, gameTypes.CannonGameType) config.L1Beacon = "" require.ErrorIs(t, config.Check(), ErrMissingL1Beacon) } func TestGameFactoryAddressRequired(t *testing.T) { - config := validConfig(t, types.TraceTypeCannon) + config := validConfig(t, gameTypes.CannonGameType) config.GameFactoryAddress = common.Address{} require.ErrorIs(t, config.Check(), ErrMissingGameFactoryAddress) } func TestSelectiveClaimResolutionNotRequired(t *testing.T) { - config := validConfig(t, types.TraceTypeCannon) + config := validConfig(t, gameTypes.CannonGameType) require.Equal(t, false, config.SelectiveClaimResolution) require.NoError(t, config.Check()) } func TestGameAllowlistNotRequired(t *testing.T) { - config := validConfig(t, types.TraceTypeCannon) + config := validConfig(t, gameTypes.CannonGameType) config.GameAllowlist = []common.Address{} require.NoError(t, config.Check()) } func TestCannonRequiredArgs(t *testing.T) { - for _, traceType := range allCannonTraceTypes { - traceType := traceType + for _, gameType := range allCannonGameTypes { + gameType := gameType - t.Run(fmt.Sprintf("TestCannonBinRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonBinRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.Cannon.VmBin = "" require.ErrorIs(t, config.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestCannonServerRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonServerRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.Cannon.Server = "" require.ErrorIs(t, config.Check(), vm.ErrMissingServer) }) - t.Run(fmt.Sprintf("TestCannonAbsolutePreStateOrBaseURLRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonAbsolutePreStateOrBaseURLRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonAbsolutePreState = "" config.CannonAbsolutePreStateBaseURL = nil require.ErrorIs(t, config.Check(), ErrMissingCannonAbsolutePreState) }) - t.Run(fmt.Sprintf("TestCannonAbsolutePreState-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonAbsolutePreState-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonAbsolutePreState = validCannonAbsolutePreState config.CannonAbsolutePreStateBaseURL = nil require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestCannonAbsolutePreStateBaseURL-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonAbsolutePreStateBaseURL-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonAbsolutePreState = "" config.CannonAbsolutePreStateBaseURL = validCannonAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestAllowSupplyingBothCannonAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAllowSupplyingBothCannonAbsolutePreStateAndBaseURL-%v", gameType), func(t *testing.T) { // Since the prestate baseURL might be inherited from the --prestate-urls option, allow overriding it with a specific prestate - config := validConfig(t, traceType) + config := validConfig(t, gameType) config.CannonAbsolutePreState = validCannonAbsolutePreState config.CannonAbsolutePreStateBaseURL = validCannonAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestL2RpcRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.L2Rpcs = nil require.ErrorIs(t, config.Check(), ErrMissingL2Rpc) }) - t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonSnapshotFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.Cannon.SnapshotFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingCannonSnapshotFreq) }) }) - t.Run(fmt.Sprintf("TestCannonInfoFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonInfoFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.Cannon.InfoFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingCannonInfoFreq) }) }) - t.Run(fmt.Sprintf("TestCannonNetworkOrRollupConfigRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestCannonNetworkOrRollupConfigRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.Cannon.RollupConfigPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingRollupConfig) }) - t.Run(fmt.Sprintf("TestCannonNetworkOrL2GenesisRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestCannonNetworkOrL2GenesisRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.Cannon.L2GenesisPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingL2Genesis) }) - t.Run(fmt.Sprintf("TestMaySpecifyNetworkAndCustomConfigs-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestMaySpecifyNetworkAndCustomConfigs-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.Networks = []string{validCannonNetwork} cfg.Cannon.RollupConfigPaths = []string{"foo.json"} cfg.Cannon.L2GenesisPaths = []string{"genesis.json"} require.NoError(t, cfg.Check()) }) - t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.Networks = []string{"unknown"} require.ErrorIs(t, cfg.Check(), vm.ErrNetworkUnknown) }) - t.Run(fmt.Sprintf("TestNetworkMayBeAnyChainID-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkMayBeAnyChainID-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.Networks = []string{"467294"} require.NoError(t, cfg.Check()) }) - t.Run(fmt.Sprintf("TestNetworkInvalidWhenNotEntirelyNumeric-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkInvalidWhenNotEntirelyNumeric-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.Networks = []string{"467294a"} require.ErrorIs(t, cfg.Check(), vm.ErrNetworkUnknown) }) - t.Run(fmt.Sprintf("TestDebugInfoEnabled-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestDebugInfoEnabled-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) require.True(t, cfg.Cannon.DebugInfo) }) - t.Run(fmt.Sprintf("TestVMBinExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestVMBinExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.VmBin = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestServerExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestServerExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.Server = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingServer) }) @@ -375,123 +375,123 @@ func TestCannonRequiredArgs(t *testing.T) { } func TestCannonKonaRequiredArgs(t *testing.T) { - for _, traceType := range cannonKonaTraceTypes { - traceType := traceType + for _, gameType := range cannonKonaGameTypes { + gameType := gameType - t.Run(fmt.Sprintf("TestCannonKonaBinRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonKonaBinRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonKona.VmBin = "" require.ErrorIs(t, config.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestCannonKonaServerRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonKonaServerRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonKona.Server = "" require.ErrorIs(t, config.Check(), vm.ErrMissingServer) }) - t.Run(fmt.Sprintf("TestCannonKonaAbsolutePreStateOrBaseURLRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonKonaAbsolutePreStateOrBaseURLRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonKonaAbsolutePreState = "" config.CannonKonaAbsolutePreStateBaseURL = nil require.ErrorIs(t, config.Check(), ErrMissingCannonKonaAbsolutePreState) }) - t.Run(fmt.Sprintf("TestCannonKonaAbsolutePreState-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonKonaAbsolutePreState-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonKonaAbsolutePreState = validCannonAbsolutePreState config.CannonKonaAbsolutePreStateBaseURL = nil require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestCannonKonaAbsolutePreStateBaseURL-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestCannonKonaAbsolutePreStateBaseURL-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.CannonKonaAbsolutePreState = "" config.CannonKonaAbsolutePreStateBaseURL = validCannonAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestAllowSupplyingBothCannonKonaAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAllowSupplyingBothCannonKonaAbsolutePreStateAndBaseURL-%v", gameType), func(t *testing.T) { // Since the prestate baseURL might be inherited from the --prestate-urls option, allow overriding it with a specific prestate - config := validConfig(t, traceType) + config := validConfig(t, gameType) config.CannonKonaAbsolutePreState = validCannonAbsolutePreState config.CannonKonaAbsolutePreStateBaseURL = validCannonAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestL2RpcRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.L2Rpcs = nil require.ErrorIs(t, config.Check(), ErrMissingL2Rpc) }) - t.Run(fmt.Sprintf("TestCannonKonaSnapshotFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonKonaSnapshotFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.CannonKona.SnapshotFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingCannonKonaSnapshotFreq) }) }) - t.Run(fmt.Sprintf("TestCannonKonaInfoFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestCannonKonaInfoFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.CannonKona.InfoFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingCannonKonaInfoFreq) }) }) - t.Run(fmt.Sprintf("TestCannonKonaNetworkOrRollupConfigRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestCannonKonaNetworkOrRollupConfigRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.CannonKona.RollupConfigPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingRollupConfig) }) - t.Run(fmt.Sprintf("TestCannonKonaNetworkOrL2GenesisRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestCannonKonaNetworkOrL2GenesisRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.CannonKona.L2GenesisPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingL2Genesis) }) - t.Run(fmt.Sprintf("TestMaySpecifyNetworkAndCustomConfigs-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestMaySpecifyNetworkAndCustomConfigs-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.CannonKona.Networks = []string{validCannonNetwork} cfg.CannonKona.RollupConfigPaths = []string{"foo.json"} cfg.CannonKona.L2GenesisPaths = []string{"genesis.json"} require.NoError(t, cfg.Check()) }) - t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.CannonKona.Networks = []string{"unknown"} require.ErrorIs(t, cfg.Check(), vm.ErrNetworkUnknown) }) - t.Run(fmt.Sprintf("TestNetworkMayBeAnyChainID-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkMayBeAnyChainID-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.CannonKona.Networks = []string{"467294"} require.NoError(t, cfg.Check()) }) - t.Run(fmt.Sprintf("TestNetworkInvalidWhenNotEntirelyNumeric-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkInvalidWhenNotEntirelyNumeric-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.CannonKona.Networks = []string{"467294a"} require.ErrorIs(t, cfg.Check(), vm.ErrNetworkUnknown) }) - t.Run(fmt.Sprintf("TestDebugInfoEnabled-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestDebugInfoEnabled-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) require.True(t, cfg.CannonKona.DebugInfo) }) - t.Run(fmt.Sprintf("TestVMBinExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestVMBinExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.CannonKona.VmBin = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestServerExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestServerExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.CannonKona.Server = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingServer) }) @@ -499,10 +499,10 @@ func TestCannonKonaRequiredArgs(t *testing.T) { } func TestDepsetConfig(t *testing.T) { - for _, traceType := range superCannonTraceTypes { - traceType := traceType - t.Run(fmt.Sprintf("TestCannonNetworkOrDepsetConfigRequired-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + for _, gameType := range superCannonGameTypes { + gameType := gameType + t.Run(fmt.Sprintf("TestCannonNetworkOrDepsetConfigRequired-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.Networks = nil cfg.Cannon.RollupConfigPaths = []string{"foo.json"} cfg.Cannon.L2GenesisPaths = []string{"genesis.json"} @@ -511,10 +511,10 @@ func TestDepsetConfig(t *testing.T) { }) } - for _, traceType := range superAsteriscKonaTraceTypes { - traceType := traceType - t.Run(fmt.Sprintf("TestAsteriscNetworkOrDepsetConfigRequired-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + for _, gameType := range superAsteriscKonaGameTypes { + gameType := gameType + t.Run(fmt.Sprintf("TestAsteriscNetworkOrDepsetConfigRequired-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.AsteriscKona.Networks = nil cfg.AsteriscKona.RollupConfigPaths = []string{"foo.json"} cfg.AsteriscKona.L2GenesisPaths = []string{"genesis.json"} @@ -523,10 +523,10 @@ func TestDepsetConfig(t *testing.T) { }) } - for _, traceType := range singleCannonTraceTypes { - traceType := traceType - t.Run(fmt.Sprintf("TestDepsetConfigNotRequired-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + for _, gameType := range singleCannonGameTypes { + gameType := gameType + t.Run(fmt.Sprintf("TestDepsetConfigNotRequired-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Cannon.Networks = nil cfg.Cannon.RollupConfigPaths = []string{"foo.json"} cfg.Cannon.L1GenesisPath = "bar.json" @@ -536,10 +536,10 @@ func TestDepsetConfig(t *testing.T) { }) } - for _, traceType := range asteriscKonaTraceTypes { - traceType := traceType - t.Run(fmt.Sprintf("TestDepsetConfigNotRequired-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + for _, gameType := range asteriscKonaGameTypes { + gameType := gameType + t.Run(fmt.Sprintf("TestDepsetConfigNotRequired-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.AsteriscKona.Networks = nil cfg.AsteriscKona.RollupConfigPaths = []string{"foo.json"} cfg.AsteriscKona.L1GenesisPath = "bar.json" @@ -551,111 +551,111 @@ func TestDepsetConfig(t *testing.T) { } func TestAsteriscRequiredArgs(t *testing.T) { - for _, traceType := range asteriscTraceTypes { - traceType := traceType + for _, gameType := range asteriscGameTypes { + gameType := gameType - t.Run(fmt.Sprintf("TestAsteriscBinRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscBinRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.Asterisc.VmBin = "" require.ErrorIs(t, config.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestAsteriscServerRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscServerRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.Asterisc.Server = "" require.ErrorIs(t, config.Check(), vm.ErrMissingServer) }) - t.Run(fmt.Sprintf("TestAsteriscAbsolutePreStateOrBaseURLRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscAbsolutePreStateOrBaseURLRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscAbsolutePreState = "" config.AsteriscAbsolutePreStateBaseURL = nil require.ErrorIs(t, config.Check(), ErrMissingAsteriscAbsolutePreState) }) - t.Run(fmt.Sprintf("TestAsteriscAbsolutePreState-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscAbsolutePreState-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscAbsolutePreState = validAsteriscAbsolutePreState config.AsteriscAbsolutePreStateBaseURL = nil require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestAsteriscAbsolutePreStateBaseURL-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscAbsolutePreStateBaseURL-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscAbsolutePreState = "" config.AsteriscAbsolutePreStateBaseURL = validAsteriscAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestAllowSupplingBothAsteriscAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAllowSupplingBothAsteriscAbsolutePreStateAndBaseURL-%v", gameType), func(t *testing.T) { // Since the prestate base URL might be inherited from the --prestate-urls option, allow overriding it with a specific prestate - config := validConfig(t, traceType) + config := validConfig(t, gameType) config.AsteriscAbsolutePreState = validAsteriscAbsolutePreState config.AsteriscAbsolutePreStateBaseURL = validAsteriscAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestL2RpcRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.L2Rpcs = nil require.ErrorIs(t, config.Check(), ErrMissingL2Rpc) }) - t.Run(fmt.Sprintf("TestAsteriscSnapshotFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscSnapshotFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.Asterisc.SnapshotFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscSnapshotFreq) }) }) - t.Run(fmt.Sprintf("TestAsteriscInfoFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscInfoFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.Asterisc.InfoFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscInfoFreq) }) }) - t.Run(fmt.Sprintf("TestAsteriscNetworkOrRollupConfigRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscNetworkOrRollupConfigRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.Asterisc.RollupConfigPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingRollupConfig) }) - t.Run(fmt.Sprintf("TestAsteriscNetworkOrL2GenesisRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscNetworkOrL2GenesisRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.Asterisc.L2GenesisPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingL2Genesis) }) - t.Run(fmt.Sprintf("MaySpecifyNetworkAndCustomConfigs-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("MaySpecifyNetworkAndCustomConfigs-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Asterisc.Networks = []string{validAsteriscNetwork} cfg.Asterisc.RollupConfigPaths = []string{"foo.json"} cfg.Asterisc.L2GenesisPaths = []string{"genesis.json"} require.NoError(t, cfg.Check()) }) - t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Asterisc.Networks = []string{"unknown"} require.ErrorIs(t, cfg.Check(), vm.ErrNetworkUnknown) }) - t.Run(fmt.Sprintf("TestDebugInfoDisabled-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestDebugInfoDisabled-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) require.False(t, cfg.Asterisc.DebugInfo) }) - t.Run(fmt.Sprintf("TestVMBinExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestVMBinExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Asterisc.VmBin = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestServerExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestServerExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.Asterisc.Server = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingServer) }) @@ -663,111 +663,111 @@ func TestAsteriscRequiredArgs(t *testing.T) { } func TestAsteriscKonaRequiredArgs(t *testing.T) { - for _, traceType := range asteriscKonaTraceTypes { - traceType := traceType + for _, gameType := range asteriscKonaGameTypes { + gameType := gameType - t.Run(fmt.Sprintf("TestAsteriscKonaBinRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscKonaBinRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscKona.VmBin = "" require.ErrorIs(t, config.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestAsteriscKonaServerRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscKonaServerRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscKona.Server = "" require.ErrorIs(t, config.Check(), vm.ErrMissingServer) }) - t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreStateOrBaseURLRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreStateOrBaseURLRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscKonaAbsolutePreState = "" config.AsteriscKonaAbsolutePreStateBaseURL = nil require.ErrorIs(t, config.Check(), ErrMissingAsteriscKonaAbsolutePreState) }) - t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreState-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreState-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscKonaAbsolutePreState = validAsteriscKonaAbsolutePreState config.AsteriscKonaAbsolutePreStateBaseURL = nil require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreStateBaseURL-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscKonaAbsolutePreStateBaseURL-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.AsteriscKonaAbsolutePreState = "" config.AsteriscKonaAbsolutePreStateBaseURL = validAsteriscKonaAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestAllowSupplyingBothAsteriscKonaAbsolutePreStateAndBaseURL-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAllowSupplyingBothAsteriscKonaAbsolutePreStateAndBaseURL-%v", gameType), func(t *testing.T) { // Since the prestate base URL might be inherited from the --prestate-urls option, allow overriding it with a specific prestate - config := validConfig(t, traceType) + config := validConfig(t, gameType) config.AsteriscKonaAbsolutePreState = validAsteriscKonaAbsolutePreState config.AsteriscKonaAbsolutePreStateBaseURL = validAsteriscKonaAbsolutePreStateBaseURL require.NoError(t, config.Check()) }) - t.Run(fmt.Sprintf("TestL2RpcRequired-%v", traceType), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestL2RpcRequired-%v", gameType), func(t *testing.T) { + config := validConfig(t, gameType) config.L2Rpcs = nil require.ErrorIs(t, config.Check(), ErrMissingL2Rpc) }) - t.Run(fmt.Sprintf("TestAsteriscKonaSnapshotFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscKonaSnapshotFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.AsteriscKona.SnapshotFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscKonaSnapshotFreq) }) }) - t.Run(fmt.Sprintf("TestAsteriscKonaInfoFreq-%v", traceType), func(t *testing.T) { + t.Run(fmt.Sprintf("TestAsteriscKonaInfoFreq-%v", gameType), func(t *testing.T) { t.Run("MustNotBeZero", func(t *testing.T) { - cfg := validConfig(t, traceType) + cfg := validConfig(t, gameType) cfg.AsteriscKona.InfoFreq = 0 require.ErrorIs(t, cfg.Check(), ErrMissingAsteriscKonaInfoFreq) }) }) - t.Run(fmt.Sprintf("TestAsteriscKonaNetworkOrRollupConfigRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscKonaNetworkOrRollupConfigRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.AsteriscKona.RollupConfigPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingRollupConfig) }) - t.Run(fmt.Sprintf("TestAsteriscKonaNetworkOrL2GenesisRequired-%v", traceType), func(t *testing.T) { - cfg := validConfigWithNoNetworks(t, traceType) + t.Run(fmt.Sprintf("TestAsteriscKonaNetworkOrL2GenesisRequired-%v", gameType), func(t *testing.T) { + cfg := validConfigWithNoNetworks(t, gameType) cfg.AsteriscKona.L2GenesisPaths = nil require.ErrorIs(t, cfg.Check(), vm.ErrMissingL2Genesis) }) - t.Run(fmt.Sprintf("MaySpecifyNetworkAndCustomConfig-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("MaySpecifyNetworkAndCustomConfig-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.AsteriscKona.Networks = []string{validAsteriscKonaNetwork} cfg.AsteriscKona.RollupConfigPaths = []string{"foo.json"} cfg.AsteriscKona.L2GenesisPaths = []string{"genesis.json"} require.NoError(t, cfg.Check()) }) - t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestNetworkMustBeValid-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.AsteriscKona.Networks = []string{"unknown"} require.ErrorIs(t, cfg.Check(), vm.ErrNetworkUnknown) }) - t.Run(fmt.Sprintf("TestDebugInfoDisabled-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestDebugInfoDisabled-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) require.False(t, cfg.AsteriscKona.DebugInfo) }) - t.Run(fmt.Sprintf("TestVMBinExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestVMBinExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.AsteriscKona.VmBin = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingBin) }) - t.Run(fmt.Sprintf("TestServerExists-%v", traceType), func(t *testing.T) { - cfg := validConfig(t, traceType) + t.Run(fmt.Sprintf("TestServerExists-%v", gameType), func(t *testing.T) { + cfg := validConfig(t, gameType) cfg.AsteriscKona.Server = nonExistingFile require.ErrorIs(t, cfg.Check(), vm.ErrMissingServer) }) @@ -775,39 +775,39 @@ func TestAsteriscKonaRequiredArgs(t *testing.T) { } func TestDatadirRequired(t *testing.T) { - config := validConfig(t, types.TraceTypeAlphabet) + config := validConfig(t, gameTypes.AlphabetGameType) config.Datadir = "" require.ErrorIs(t, config.Check(), ErrMissingDatadir) } func TestMaxConcurrency(t *testing.T) { t.Run("Required", func(t *testing.T) { - config := validConfig(t, types.TraceTypeAlphabet) + config := validConfig(t, gameTypes.AlphabetGameType) config.MaxConcurrency = 0 require.ErrorIs(t, config.Check(), ErrMaxConcurrencyZero) }) t.Run("DefaultToNumberOfCPUs", func(t *testing.T) { - config := validConfig(t, types.TraceTypeAlphabet) + config := validConfig(t, gameTypes.AlphabetGameType) require.EqualValues(t, runtime.NumCPU(), config.MaxConcurrency) }) } func TestHttpPollInterval(t *testing.T) { t.Run("Default", func(t *testing.T) { - config := validConfig(t, types.TraceTypeAlphabet) + config := validConfig(t, gameTypes.AlphabetGameType) require.EqualValues(t, DefaultPollInterval, config.PollInterval) }) } func TestRollupRpcRequired(t *testing.T) { - for _, traceType := range types.TraceTypes { - traceType := traceType - if traceType == types.TraceTypeSuperCannon || traceType == types.TraceTypeSuperPermissioned || traceType == types.TraceTypeSuperAsteriscKona || traceType == types.TraceTypeSuperCannonKona { + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType + if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType || gameType == gameTypes.SuperAsteriscKonaGameType || gameType == gameTypes.SuperCannonKonaGameType { continue } - t.Run(traceType.String(), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run(gameType.String(), func(t *testing.T) { + config := validConfig(t, gameType) config.RollupRpc = "" require.ErrorIs(t, config.Check(), ErrMissingRollupRpc) }) @@ -816,42 +816,42 @@ func TestRollupRpcRequired(t *testing.T) { func TestRollupRpcNotRequiredForInterop(t *testing.T) { t.Run("SuperCannon", func(t *testing.T) { - config := validConfig(t, types.TraceTypeSuperCannon) + config := validConfig(t, gameTypes.SuperCannonGameType) config.RollupRpc = "" require.NoError(t, config.Check()) }) t.Run("SuperPermissioned", func(t *testing.T) { - config := validConfig(t, types.TraceTypeSuperPermissioned) + config := validConfig(t, gameTypes.SuperPermissionedGameType) config.RollupRpc = "" require.NoError(t, config.Check()) }) t.Run("SuperCannonKona", func(t *testing.T) { - config := validConfig(t, types.TraceTypeSuperCannonKona) + config := validConfig(t, gameTypes.SuperCannonKonaGameType) config.RollupRpc = "" require.NoError(t, config.Check()) }) t.Run("SuperAsteriscKona", func(t *testing.T) { - config := validConfig(t, types.TraceTypeSuperAsteriscKona) + config := validConfig(t, gameTypes.SuperAsteriscKonaGameType) config.RollupRpc = "" require.NoError(t, config.Check()) }) } func TestSupervisorRpc(t *testing.T) { - for _, traceType := range types.TraceTypes { - traceType := traceType - if traceType == types.TraceTypeSuperCannon || traceType == types.TraceTypeSuperPermissioned || traceType == types.TraceTypeSuperAsteriscKona || traceType == types.TraceTypeSuperCannonKona { - t.Run("RequiredFor"+traceType.String(), func(t *testing.T) { - config := validConfig(t, traceType) + for _, gameType := range gameTypes.SupportedGameTypes { + gameType := gameType + if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperPermissionedGameType || gameType == gameTypes.SuperAsteriscKonaGameType || gameType == gameTypes.SuperCannonKonaGameType { + t.Run("RequiredFor"+gameType.String(), func(t *testing.T) { + config := validConfig(t, gameType) config.SupervisorRPC = "" require.ErrorIs(t, config.Check(), ErrMissingSupervisorRpc) }) } else { - t.Run("NotRequiredFor"+traceType.String(), func(t *testing.T) { - config := validConfig(t, traceType) + t.Run("NotRequiredFor"+gameType.String(), func(t *testing.T) { + config := validConfig(t, gameType) config.SupervisorRPC = "" require.NoError(t, config.Check()) }) @@ -859,9 +859,9 @@ func TestSupervisorRpc(t *testing.T) { } } -func TestRequireConfigForMultipleTraceTypesForCannon(t *testing.T) { - cfg := validConfig(t, types.TraceTypeCannon) - cfg.TraceTypes = []types.TraceType{types.TraceTypeCannon, types.TraceTypeAlphabet} +func TestRequireConfigForMultipleGameTypesForCannon(t *testing.T) { + cfg := validConfig(t, gameTypes.CannonGameType) + cfg.GameTypes = []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.AlphabetGameType} // Set all required options and check its valid cfg.RollupRpc = validRollupRpc require.NoError(t, cfg.Check()) @@ -877,9 +877,9 @@ func TestRequireConfigForMultipleTraceTypesForCannon(t *testing.T) { require.ErrorIs(t, cfg.Check(), ErrMissingRollupRpc) } -func TestRequireConfigForMultipleTraceTypesForAsterisc(t *testing.T) { - cfg := validConfig(t, types.TraceTypeAsterisc) - cfg.TraceTypes = []types.TraceType{types.TraceTypeAsterisc, types.TraceTypeAlphabet} +func TestRequireConfigForMultipleGameTypesForAsterisc(t *testing.T) { + cfg := validConfig(t, gameTypes.AsteriscGameType) + cfg.GameTypes = []gameTypes.GameType{gameTypes.AsteriscGameType, gameTypes.AlphabetGameType} // Set all required options and check its valid cfg.RollupRpc = validRollupRpc require.NoError(t, cfg.Check()) @@ -895,11 +895,11 @@ func TestRequireConfigForMultipleTraceTypesForAsterisc(t *testing.T) { require.ErrorIs(t, cfg.Check(), ErrMissingRollupRpc) } -func TestRequireConfigForMultipleTraceTypesForCannonAndAsterisc(t *testing.T) { - cfg := validConfig(t, types.TraceTypeCannon) +func TestRequireConfigForMultipleGameTypesForCannonAndAsterisc(t *testing.T) { + cfg := validConfig(t, gameTypes.CannonGameType) applyValidConfigForAsterisc(t, &cfg) - cfg.TraceTypes = []types.TraceType{types.TraceTypeCannon, types.TraceTypeAsterisc, types.TraceTypeAlphabet, types.TraceTypeFast} + cfg.GameTypes = []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.AsteriscGameType, gameTypes.AlphabetGameType, gameTypes.FastGameType} // Set all required options and check its valid cfg.RollupRpc = validRollupRpc require.NoError(t, cfg.Check()) diff --git a/op-challenger/flags/flags.go b/op-challenger/flags/flags.go index dfb07af5e32..1a9c4c2efd2 100644 --- a/op-challenger/flags/flags.go +++ b/op-challenger/flags/flags.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/flags" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -27,19 +27,24 @@ import ( const EnvVarPrefix = "OP_CHALLENGER" -func prefixEnvVars(name string) []string { - return opservice.PrefixEnvVar(EnvVarPrefix, name) +func prefixEnvVars(names ...string) []string { + + envs := make([]string, 0, len(names)) + for _, name := range names { + envs = append(envs, EnvVarPrefix+"_"+name) + } + return envs } var ( - faultDisputeVMs = []types.TraceType{ - types.TraceTypeCannon, - types.TraceTypeCannonKona, - types.TraceTypeAsterisc, - types.TraceTypeAsteriscKona, - types.TraceTypeSuperCannon, - types.TraceTypeSuperCannonKona, - types.TraceTypeSuperAsteriscKona, + faultDisputeVMs = []gameTypes.GameType{ + gameTypes.CannonGameType, + gameTypes.CannonKonaGameType, + gameTypes.AsteriscGameType, + gameTypes.AsteriscKonaGameType, + gameTypes.SuperCannonGameType, + gameTypes.SuperCannonKonaGameType, + gameTypes.SuperAsteriscKonaGameType, } // Required Flags L1EthRpcFlag = &cli.StringFlag{ @@ -78,11 +83,12 @@ var ( "If empty, the challenger will play all games.", EnvVars: prefixEnvVars("GAME_ALLOWLIST"), } - TraceTypeFlag = &cli.StringSliceFlag{ - Name: "trace-type", - Usage: "The trace types to support. Valid options: " + openum.EnumString(types.TraceTypes), - EnvVars: prefixEnvVars("TRACE_TYPE"), - Value: cli.NewStringSlice(types.TraceTypeCannon.String(), types.TraceTypeAsteriscKona.String(), types.TraceTypeCannonKona.String()), + GameTypesFlag = &cli.StringSliceFlag{ + Name: "game-types", + Aliases: []string{"trace-type"}, // For backwards compatibility + Usage: "The game types to support. Valid options: " + openum.EnumStringer(gameTypes.SupportedGameTypes), + EnvVars: prefixEnvVars("GAME_TYPES", "TRACE_TYPE"), + Value: cli.NewStringSlice(gameTypes.CannonGameType.String(), gameTypes.AsteriscKonaGameType.String(), gameTypes.CannonKonaGameType.String()), } DatadirFlag = &cli.StringFlag{ Name: "datadir", @@ -103,7 +109,7 @@ var ( } L2ExperimentalEthRpcFlag = &cli.StringFlag{ Name: "l2-experimental-eth-rpc", - Usage: "L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required with execution witness support) (cannon/asterisc trace type only)", + Usage: "L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required with execution witness support) (cannon/asterisc game type only)", EnvVars: prefixEnvVars("L2_EXPERIMENTAL_ETH_RPC"), } MaxPendingTransactionsFlag = &cli.Uint64Flag{ @@ -128,40 +134,40 @@ var ( Usage: "List of addresses to claim bonds for, in addition to the configured transaction sender", EnvVars: prefixEnvVars("ADDITIONAL_BOND_CLAIMANTS"), } - PreStatesURLFlag = NewVMFlag("prestates-url", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, traceTypeInfo string) cli.Flag { + PreStatesURLFlag = NewVMFlag("prestates-url", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, gameTypeInfo string) cli.Flag { return &cli.StringFlag{ Name: name, Usage: "Base URL to absolute prestates to use when generating trace data. " + "Prestates in this directory should be name as .bin.gz .json.gz or .json " + - traceTypeInfo, + gameTypeInfo, EnvVars: envVars, } }) - RollupConfigFlag = NewVMFlag("rollup-config", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, traceTypeInfo string) cli.Flag { + RollupConfigFlag = NewVMFlag("rollup-config", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, gameTypeInfo string) cli.Flag { return &cli.StringSliceFlag{ Name: name, - Usage: "Rollup chain parameters " + traceTypeInfo, + Usage: "Rollup chain parameters " + gameTypeInfo, EnvVars: envVars, } }) - L2GenesisFlag = NewVMFlag("l2-genesis", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, traceTypeInfo string) cli.Flag { + L2GenesisFlag = NewVMFlag("l2-genesis", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, gameTypeInfo string) cli.Flag { return &cli.StringSliceFlag{ Name: name, - Usage: "Paths to the op-geth genesis file " + traceTypeInfo, + Usage: "Paths to the op-geth genesis file " + gameTypeInfo, EnvVars: envVars, } }) - L1GenesisFlag = NewVMFlag("l1-genesis", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, traceTypeInfo string) cli.Flag { + L1GenesisFlag = NewVMFlag("l1-genesis", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, gameTypeInfo string) cli.Flag { return &cli.StringFlag{ Name: name, Usage: "Path to the L1 genesis file. Only required if the L1 is not mainnet, sepolia, holesky, or hoodi.", EnvVars: envVars, } }) - DepsetConfigFlag = NewVMFlag("depset-config", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, traceTypeInfo string) cli.Flag { + DepsetConfigFlag = NewVMFlag("depset-config", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, gameTypeInfo string) cli.Flag { return &cli.StringFlag{ Name: name, - Usage: "Interop dependency set config file " + traceTypeInfo, + Usage: "Interop dependency set config file " + gameTypeInfo, EnvVars: envVars, } }) @@ -175,39 +181,39 @@ var ( } CannonBinFlag = &cli.StringFlag{ Name: "cannon-bin", - Usage: "Path to cannon executable to use when generating trace data (cannon trace type only)", + Usage: "Path to cannon executable to use when generating trace data (cannon game type only)", EnvVars: prefixEnvVars("CANNON_BIN"), } CannonServerFlag = &cli.StringFlag{ Name: "cannon-server", - Usage: "Path to executable to use as pre-image oracle server when generating trace data (cannon trace type only)", + Usage: "Path to executable to use as pre-image oracle server when generating trace data (cannon game type only)", EnvVars: prefixEnvVars("CANNON_SERVER"), } CannonPreStateFlag = &cli.StringFlag{ Name: "cannon-prestate", - Usage: "Path to absolute prestate to use when generating trace data (cannon trace type only)", + Usage: "Path to absolute prestate to use when generating trace data (cannon game type only)", EnvVars: prefixEnvVars("CANNON_PRESTATE"), } CannonSnapshotFreqFlag = &cli.UintFlag{ Name: "cannon-snapshot-freq", - Usage: "Frequency of cannon snapshots to generate in VM steps (cannon trace type only)", + Usage: "Frequency of cannon snapshots to generate in VM steps (cannon game type only)", EnvVars: prefixEnvVars("CANNON_SNAPSHOT_FREQ"), Value: config.DefaultCannonSnapshotFreq, } CannonInfoFreqFlag = &cli.UintFlag{ Name: "cannon-info-freq", - Usage: "Frequency of cannon info log messages to generate in VM steps (cannon trace type only)", + Usage: "Frequency of cannon info log messages to generate in VM steps (cannon game type only)", EnvVars: prefixEnvVars("CANNON_INFO_FREQ"), Value: config.DefaultCannonInfoFreq, } CannonKonaServerFlag = &cli.StringFlag{ Name: "cannon-kona-server", - Usage: "Path to kona executable to use as pre-image oracle server when generating trace data (cannon-kona trace type only)", + Usage: "Path to kona executable to use as pre-image oracle server when generating trace data (cannon-kona game type only)", EnvVars: prefixEnvVars("CANNON_KONA_SERVER"), } CannonKonaPreStateFlag = &cli.StringFlag{ Name: "cannon-kona-prestate", - Usage: "Path to absolute prestate to use when generating trace data (cannon-kona trace type only)", + Usage: "Path to absolute prestate to use when generating trace data (cannon-kona game type only)", EnvVars: prefixEnvVars("CANNON_KONA_PRESTATE"), } CannonKonaL2CustomFlag = &cli.BoolFlag{ @@ -220,17 +226,17 @@ var ( } AsteriscBinFlag = &cli.StringFlag{ Name: "asterisc-bin", - Usage: "Path to asterisc executable to use when generating trace data (asterisc trace type only)", + Usage: "Path to asterisc executable to use when generating trace data (asterisc game type only)", EnvVars: prefixEnvVars("ASTERISC_BIN"), } AsteriscServerFlag = &cli.StringFlag{ Name: "asterisc-server", - Usage: "Path to executable to use as pre-image oracle server when generating trace data (asterisc trace type only)", + Usage: "Path to executable to use as pre-image oracle server when generating trace data (asterisc game type only)", EnvVars: prefixEnvVars("ASTERISC_SERVER"), } AsteriscKonaServerFlag = &cli.StringFlag{ Name: "asterisc-kona-server", - Usage: "Path to kona executable to use as pre-image oracle server when generating trace data (asterisc-kona trace type only)", + Usage: "Path to kona executable to use as pre-image oracle server when generating trace data (asterisc-kona game type only)", EnvVars: prefixEnvVars("ASTERISC_KONA_SERVER"), } AsteriscKonaL2CustomFlag = &cli.BoolFlag{ @@ -243,23 +249,23 @@ var ( } AsteriscPreStateFlag = &cli.StringFlag{ Name: "asterisc-prestate", - Usage: "Path to absolute prestate to use when generating trace data (asterisc trace type only)", + Usage: "Path to absolute prestate to use when generating trace data (asterisc game type only)", EnvVars: prefixEnvVars("ASTERISC_PRESTATE"), } AsteriscKonaPreStateFlag = &cli.StringFlag{ Name: "asterisc-kona-prestate", - Usage: "Path to absolute prestate to use when generating trace data (asterisc-kona trace type only)", + Usage: "Path to absolute prestate to use when generating trace data (asterisc-kona game type only)", EnvVars: prefixEnvVars("ASTERISC_KONA_PRESTATE"), } AsteriscSnapshotFreqFlag = &cli.UintFlag{ Name: "asterisc-snapshot-freq", - Usage: "Frequency of asterisc snapshots to generate in VM steps (asterisc trace type only)", + Usage: "Frequency of asterisc snapshots to generate in VM steps (asterisc game type only)", EnvVars: prefixEnvVars("ASTERISC_SNAPSHOT_FREQ"), Value: config.DefaultAsteriscSnapshotFreq, } AsteriscInfoFreqFlag = &cli.UintFlag{ Name: "asterisc-info-freq", - Usage: "Frequency of asterisc info log messages to generate in VM steps (asterisc trace type only)", + Usage: "Frequency of asterisc info log messages to generate in VM steps (asterisc game type only)", EnvVars: prefixEnvVars("ASTERISC_INFO_FREQ"), Value: config.DefaultAsteriscInfoFreq, } @@ -307,7 +313,7 @@ var optionalFlags = []cli.Flag{ RollupRpcFlag, NetworkFlag, FactoryAddressFlag, - TraceTypeFlag, + GameTypesFlag, MaxConcurrencyFlag, SupervisorRpcFlag, L2EthRpcFlag, @@ -367,13 +373,13 @@ func checkOutputProviderFlags(ctx *cli.Context) error { func CheckCannonBaseFlags(ctx *cli.Context) error { if ctx.IsSet(flags.NetworkFlagName) && - (RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) || L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon) || L1GenesisFlag.IsSet(ctx, types.TraceTypeCannon) || ctx.Bool(CannonL2CustomFlag.Name)) { + (RollupConfigFlag.IsSet(ctx, gameTypes.CannonGameType) || L2GenesisFlag.IsSet(ctx, gameTypes.CannonGameType) || L1GenesisFlag.IsSet(ctx, gameTypes.CannonGameType) || ctx.Bool(CannonL2CustomFlag.Name)) { return fmt.Errorf("flag %v can not be used with %v, %v, %v or %v", - flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(types.TraceTypeCannon), L2GenesisFlag.EitherFlagName(types.TraceTypeCannon), L1GenesisFlag.EitherFlagName(types.TraceTypeCannon), CannonL2CustomFlag.Name) + flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(gameTypes.CannonGameType), L2GenesisFlag.EitherFlagName(gameTypes.CannonGameType), L1GenesisFlag.EitherFlagName(gameTypes.CannonGameType), CannonL2CustomFlag.Name) } - if ctx.Bool(CannonL2CustomFlag.Name) && !(RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) && L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon)) { + if ctx.Bool(CannonL2CustomFlag.Name) && !(RollupConfigFlag.IsSet(ctx, gameTypes.CannonGameType) && L2GenesisFlag.IsSet(ctx, gameTypes.CannonGameType)) { return fmt.Errorf("flag %v and %v must be set when %v is true", - RollupConfigFlag.EitherFlagName(types.TraceTypeCannon), L2GenesisFlag.EitherFlagName(types.TraceTypeCannon), CannonL2CustomFlag.Name) + RollupConfigFlag.EitherFlagName(gameTypes.CannonGameType), L2GenesisFlag.EitherFlagName(gameTypes.CannonGameType), CannonL2CustomFlag.Name) } if !ctx.IsSet(CannonBinFlag.Name) { return fmt.Errorf("flag %s is required", CannonBinFlag.Name) @@ -381,8 +387,8 @@ func CheckCannonBaseFlags(ctx *cli.Context) error { if !ctx.IsSet(CannonServerFlag.Name) { return fmt.Errorf("flag %s is required", CannonServerFlag.Name) } - if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeCannon) && !ctx.IsSet(CannonPreStateFlag.Name) { - return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(types.TraceTypeCannon), CannonPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, gameTypes.CannonGameType) && !ctx.IsSet(CannonPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(gameTypes.CannonGameType), CannonPreStateFlag.Name) } return nil } @@ -392,12 +398,12 @@ func CheckSuperCannonFlags(ctx *cli.Context) error { return fmt.Errorf("flag %v is required", SupervisorRpcFlag.Name) } if !ctx.IsSet(flags.NetworkFlagName) && - !(RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) && L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon) && DepsetConfigFlag.IsSet(ctx, types.TraceTypeCannon)) { + !(RollupConfigFlag.IsSet(ctx, gameTypes.CannonGameType) && L2GenesisFlag.IsSet(ctx, gameTypes.CannonGameType) && DepsetConfigFlag.IsSet(ctx, gameTypes.CannonGameType)) { return fmt.Errorf("flag %v or %v, %v and %v is required", flags.NetworkFlagName, - RollupConfigFlag.EitherFlagName(types.TraceTypeCannon), - L2GenesisFlag.EitherFlagName(types.TraceTypeCannon), - DepsetConfigFlag.EitherFlagName(types.TraceTypeCannon)) + RollupConfigFlag.EitherFlagName(gameTypes.CannonGameType), + L2GenesisFlag.EitherFlagName(gameTypes.CannonGameType), + DepsetConfigFlag.EitherFlagName(gameTypes.CannonGameType)) } if err := CheckCannonBaseFlags(ctx); err != nil { return err @@ -410,14 +416,14 @@ func CheckSuperCannonKonaFlags(ctx *cli.Context) error { return fmt.Errorf("flag %v is required", SupervisorRpcFlag.Name) } if !ctx.IsSet(flags.NetworkFlagName) && - !(RollupConfigFlag.IsSet(ctx, types.TraceTypeCannonKona) && L2GenesisFlag.IsSet(ctx, types.TraceTypeCannonKona) && DepsetConfigFlag.IsSet(ctx, types.TraceTypeCannonKona)) { + !(RollupConfigFlag.IsSet(ctx, gameTypes.CannonKonaGameType) && L2GenesisFlag.IsSet(ctx, gameTypes.CannonKonaGameType) && DepsetConfigFlag.IsSet(ctx, gameTypes.CannonKonaGameType)) { return fmt.Errorf("flag %v or %v, %v and %v is required", flags.NetworkFlagName, - RollupConfigFlag.EitherFlagName(types.TraceTypeCannonKona), - L2GenesisFlag.EitherFlagName(types.TraceTypeCannonKona), - DepsetConfigFlag.EitherFlagName(types.TraceTypeCannonKona)) + RollupConfigFlag.EitherFlagName(gameTypes.CannonKonaGameType), + L2GenesisFlag.EitherFlagName(gameTypes.CannonKonaGameType), + DepsetConfigFlag.EitherFlagName(gameTypes.CannonKonaGameType)) } - if err := CheckCannonKonaBaseFlags(ctx, types.TraceTypeCannonKona); err != nil { + if err := CheckCannonKonaBaseFlags(ctx, gameTypes.CannonKonaGameType); err != nil { return err } return nil @@ -428,9 +434,9 @@ func CheckCannonFlags(ctx *cli.Context) error { return err } if !ctx.IsSet(flags.NetworkFlagName) && - !(RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) && L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon)) { + !(RollupConfigFlag.IsSet(ctx, gameTypes.CannonGameType) && L2GenesisFlag.IsSet(ctx, gameTypes.CannonGameType)) { return fmt.Errorf("flag %v or %v and %v is required", - flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(types.TraceTypeCannon), L2GenesisFlag.EitherFlagName(types.TraceTypeCannon)) + flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(gameTypes.CannonGameType), L2GenesisFlag.EitherFlagName(gameTypes.CannonGameType)) } if err := CheckCannonBaseFlags(ctx); err != nil { return err @@ -438,16 +444,16 @@ func CheckCannonFlags(ctx *cli.Context) error { return nil } -func CheckCannonKonaBaseFlags(ctx *cli.Context, traceType types.TraceType) error { +func CheckCannonKonaBaseFlags(ctx *cli.Context, gameType gameTypes.GameType) error { if !ctx.IsSet(flags.NetworkFlagName) && - !(RollupConfigFlag.IsSet(ctx, traceType) && L2GenesisFlag.IsSet(ctx, traceType)) { + !(RollupConfigFlag.IsSet(ctx, gameType) && L2GenesisFlag.IsSet(ctx, gameType)) { return fmt.Errorf("flag %v or %v and %v is required", - flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(traceType), L2GenesisFlag.EitherFlagName(traceType)) + flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(gameType), L2GenesisFlag.EitherFlagName(gameType)) } if ctx.IsSet(flags.NetworkFlagName) && - (RollupConfigFlag.IsSet(ctx, types.TraceTypeCannonKona) || L2GenesisFlag.IsSet(ctx, types.TraceTypeCannonKona) || L1GenesisFlag.IsSet(ctx, types.TraceTypeCannonKona) || ctx.Bool(CannonKonaL2CustomFlag.Name)) { + (RollupConfigFlag.IsSet(ctx, gameTypes.CannonKonaGameType) || L2GenesisFlag.IsSet(ctx, gameTypes.CannonKonaGameType) || L1GenesisFlag.IsSet(ctx, gameTypes.CannonKonaGameType) || ctx.Bool(CannonKonaL2CustomFlag.Name)) { return fmt.Errorf("flag %v can not be used with %v, %v, %v or %v", - flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(types.TraceTypeCannonKona), L2GenesisFlag.EitherFlagName(types.TraceTypeCannonKona), L1GenesisFlag.EitherFlagName(types.TraceTypeCannonKona), CannonKonaL2CustomFlag.Name) + flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(gameTypes.CannonKonaGameType), L2GenesisFlag.EitherFlagName(gameTypes.CannonKonaGameType), L1GenesisFlag.EitherFlagName(gameTypes.CannonKonaGameType), CannonKonaL2CustomFlag.Name) } if !ctx.IsSet(CannonBinFlag.Name) { return fmt.Errorf("flag %s is required", CannonBinFlag.Name) @@ -459,28 +465,28 @@ func CheckCannonKonaFlags(ctx *cli.Context) error { if err := checkOutputProviderFlags(ctx); err != nil { return err } - if err := CheckCannonKonaBaseFlags(ctx, types.TraceTypeCannonKona); err != nil { + if err := CheckCannonKonaBaseFlags(ctx, gameTypes.CannonKonaGameType); err != nil { return err } if !ctx.IsSet(CannonKonaServerFlag.Name) { return fmt.Errorf("flag %s is required", CannonKonaServerFlag.Name) } - if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeCannonKona) && !ctx.IsSet(CannonKonaPreStateFlag.Name) { - return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(types.TraceTypeCannonKona), CannonKonaPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, gameTypes.CannonKonaGameType) && !ctx.IsSet(CannonKonaPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(gameTypes.CannonKonaGameType), CannonKonaPreStateFlag.Name) } return nil } -func CheckAsteriscBaseFlags(ctx *cli.Context, traceType types.TraceType) error { +func CheckAsteriscBaseFlags(ctx *cli.Context, gameType gameTypes.GameType) error { if !ctx.IsSet(flags.NetworkFlagName) && - !(RollupConfigFlag.IsSet(ctx, traceType) && L2GenesisFlag.IsSet(ctx, traceType)) { + !(RollupConfigFlag.IsSet(ctx, gameType) && L2GenesisFlag.IsSet(ctx, gameType)) { return fmt.Errorf("flag %v or %v and %v is required", - flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(traceType), L2GenesisFlag.EitherFlagName(traceType)) + flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(gameType), L2GenesisFlag.EitherFlagName(gameType)) } if ctx.IsSet(flags.NetworkFlagName) && - (RollupConfigFlag.IsSet(ctx, types.TraceTypeAsteriscKona) || L2GenesisFlag.IsSet(ctx, types.TraceTypeAsteriscKona) || L1GenesisFlag.IsSet(ctx, types.TraceTypeAsteriscKona) || ctx.Bool(AsteriscKonaL2CustomFlag.Name)) { + (RollupConfigFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType) || L2GenesisFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType) || L1GenesisFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType) || ctx.Bool(AsteriscKonaL2CustomFlag.Name)) { return fmt.Errorf("flag %v can not be used with %v, %v, %v or %v", - flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(types.TraceTypeAsteriscKona), L2GenesisFlag.EitherFlagName(types.TraceTypeAsteriscKona), L1GenesisFlag.EitherFlagName(types.TraceTypeAsteriscKona), AsteriscKonaL2CustomFlag.Name) + flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(gameTypes.AsteriscKonaGameType), L2GenesisFlag.EitherFlagName(gameTypes.AsteriscKonaGameType), L1GenesisFlag.EitherFlagName(gameTypes.AsteriscKonaGameType), AsteriscKonaL2CustomFlag.Name) } if !ctx.IsSet(AsteriscBinFlag.Name) { return fmt.Errorf("flag %s is required", AsteriscBinFlag.Name) @@ -492,14 +498,14 @@ func CheckAsteriscFlags(ctx *cli.Context) error { if err := checkOutputProviderFlags(ctx); err != nil { return err } - if err := CheckAsteriscBaseFlags(ctx, types.TraceTypeAsterisc); err != nil { + if err := CheckAsteriscBaseFlags(ctx, gameTypes.AsteriscGameType); err != nil { return err } if !ctx.IsSet(AsteriscServerFlag.Name) { return fmt.Errorf("flag %s is required", AsteriscServerFlag.Name) } - if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeAsterisc) && !ctx.IsSet(AsteriscPreStateFlag.Name) { - return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(types.TraceTypeAsterisc), AsteriscPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, gameTypes.AsteriscGameType) && !ctx.IsSet(AsteriscPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(gameTypes.AsteriscGameType), AsteriscPreStateFlag.Name) } return nil } @@ -508,14 +514,14 @@ func CheckAsteriscKonaFlags(ctx *cli.Context) error { if err := checkOutputProviderFlags(ctx); err != nil { return err } - if err := CheckAsteriscBaseFlags(ctx, types.TraceTypeAsteriscKona); err != nil { + if err := CheckAsteriscBaseFlags(ctx, gameTypes.AsteriscKonaGameType); err != nil { return err } if !ctx.IsSet(AsteriscKonaServerFlag.Name) { return fmt.Errorf("flag %s is required", AsteriscKonaServerFlag.Name) } - if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeAsteriscKona) && !ctx.IsSet(AsteriscKonaPreStateFlag.Name) { - return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(types.TraceTypeAsteriscKona), AsteriscKonaPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType) && !ctx.IsSet(AsteriscKonaPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(gameTypes.AsteriscKonaGameType), AsteriscKonaPreStateFlag.Name) } return nil } @@ -525,26 +531,26 @@ func CheckSuperAsteriscKonaFlags(ctx *cli.Context) error { return fmt.Errorf("flag %v is required", SupervisorRpcFlag.Name) } if !ctx.IsSet(flags.NetworkFlagName) && - !(RollupConfigFlag.IsSet(ctx, types.TraceTypeAsteriscKona) && L2GenesisFlag.IsSet(ctx, types.TraceTypeAsteriscKona) && DepsetConfigFlag.IsSet(ctx, types.TraceTypeAsteriscKona)) { + !(RollupConfigFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType) && L2GenesisFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType) && DepsetConfigFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType)) { return fmt.Errorf("flag %v or %v, %v and %v is required", flags.NetworkFlagName, - RollupConfigFlag.EitherFlagName(types.TraceTypeAsteriscKona), - L2GenesisFlag.EitherFlagName(types.TraceTypeAsteriscKona), - DepsetConfigFlag.EitherFlagName(types.TraceTypeAsteriscKona)) + RollupConfigFlag.EitherFlagName(gameTypes.AsteriscKonaGameType), + L2GenesisFlag.EitherFlagName(gameTypes.AsteriscKonaGameType), + DepsetConfigFlag.EitherFlagName(gameTypes.AsteriscKonaGameType)) } - if err := CheckAsteriscBaseFlags(ctx, types.TraceTypeAsteriscKona); err != nil { + if err := CheckAsteriscBaseFlags(ctx, gameTypes.AsteriscKonaGameType); err != nil { return err } if !ctx.IsSet(AsteriscKonaServerFlag.Name) { return fmt.Errorf("flag %s is required", AsteriscKonaServerFlag.Name) } - if !PreStatesURLFlag.IsSet(ctx, types.TraceTypeAsteriscKona) && !ctx.IsSet(AsteriscKonaPreStateFlag.Name) { - return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(types.TraceTypeAsteriscKona), AsteriscKonaPreStateFlag.Name) + if !PreStatesURLFlag.IsSet(ctx, gameTypes.AsteriscKonaGameType) && !ctx.IsSet(AsteriscKonaPreStateFlag.Name) { + return fmt.Errorf("flag %s or %s is required", PreStatesURLFlag.EitherFlagName(gameTypes.AsteriscKonaGameType), AsteriscKonaPreStateFlag.Name) } return nil } -func CheckRequired(ctx *cli.Context, traceTypes []types.TraceType) error { +func CheckRequired(ctx *cli.Context, types []gameTypes.GameType) error { for _, f := range requiredFlags { if !ctx.IsSet(f.Names()[0]) { return fmt.Errorf("flag %s is required", f.Names()[0]) @@ -553,59 +559,59 @@ func CheckRequired(ctx *cli.Context, traceTypes []types.TraceType) error { if !ctx.IsSet(L2EthRpcFlag.Name) { return fmt.Errorf("flag %s is required", L2EthRpcFlag.Name) } - for _, traceType := range traceTypes { - switch traceType { - case types.TraceTypeCannon, types.TraceTypePermissioned: + for _, gameType := range types { + switch gameType { + case gameTypes.CannonGameType, gameTypes.PermissionedGameType: if err := CheckCannonFlags(ctx); err != nil { return err } - case types.TraceTypeCannonKona: + case gameTypes.CannonKonaGameType: if err := CheckCannonKonaFlags(ctx); err != nil { return err } - case types.TraceTypeAsterisc: + case gameTypes.AsteriscGameType: if err := CheckAsteriscFlags(ctx); err != nil { return err } - case types.TraceTypeAsteriscKona: + case gameTypes.AsteriscKonaGameType: if err := CheckAsteriscKonaFlags(ctx); err != nil { return err } - case types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned: + case gameTypes.SuperCannonGameType, gameTypes.SuperPermissionedGameType: if err := CheckSuperCannonFlags(ctx); err != nil { return err } - case types.TraceTypeSuperCannonKona: + case gameTypes.SuperCannonKonaGameType: if err := CheckSuperCannonKonaFlags(ctx); err != nil { return err } - case types.TraceTypeSuperAsteriscKona: + case gameTypes.SuperAsteriscKonaGameType: if err := CheckSuperAsteriscKonaFlags(ctx); err != nil { return err } - case types.TraceTypeAlphabet, types.TraceTypeFast: + case gameTypes.AlphabetGameType, gameTypes.FastGameType: if err := checkOutputProviderFlags(ctx); err != nil { return err } default: - return fmt.Errorf("invalid trace type %v. must be one of %v", traceType, types.TraceTypes) + return fmt.Errorf("invalid game type %v. must be one of %v", gameType, gameTypes.SupportedGameTypes) } } return nil } -func parseTraceTypes(ctx *cli.Context) ([]types.TraceType, error) { - var traceTypes []types.TraceType - for _, typeName := range ctx.StringSlice(TraceTypeFlag.Name) { - traceType := new(types.TraceType) - if err := traceType.Set(typeName); err != nil { +func parseGameTypes(ctx *cli.Context) ([]gameTypes.GameType, error) { + var result []gameTypes.GameType + for _, typeName := range ctx.StringSlice(GameTypesFlag.Name) { + gameType, err := gameTypes.SupportedGameTypeFromString(typeName) + if err != nil { return nil, err } - if !slices.Contains(traceTypes, *traceType) { - traceTypes = append(traceTypes, *traceType) + if !slices.Contains(result, gameType) { + result = append(result, gameType) } } - return traceTypes, nil + return result, nil } type ChainAddressesSource func(network string) (superchain.AddressesConfig, error) @@ -656,11 +662,11 @@ func FactoryAddressForNetworks(networks []string, addressSource ChainAddressesSo // NewConfigFromCLI parses the Config from the provided flags or environment variables. func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, error) { - traceTypes, err := parseTraceTypes(ctx) + enabledGameTypes, err := parseGameTypes(ctx) if err != nil { return nil, err } - if err := CheckRequired(ctx, traceTypes); err != nil { + if err := CheckRequired(ctx, enabledGameTypes); err != nil { return nil, err } gameFactoryAddress, err := FactoryAddress(ctx) @@ -697,30 +703,30 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro } } - getPrestatesUrl := func(traceType types.TraceType) (*url.URL, error) { + getPrestatesUrl := func(gameType gameTypes.GameType) (*url.URL, error) { var preStatesURL *url.URL - if PreStatesURLFlag.IsSet(ctx, traceType) { - val := PreStatesURLFlag.String(ctx, traceType) + if PreStatesURLFlag.IsSet(ctx, gameType) { + val := PreStatesURLFlag.String(ctx, gameType) preStatesURL, err = url.Parse(val) if err != nil { - return nil, fmt.Errorf("invalid %v (%v): %w", PreStatesURLFlag.SourceFlagName(ctx, traceType), val, err) + return nil, fmt.Errorf("invalid %v (%v): %w", PreStatesURLFlag.SourceFlagName(ctx, gameType), val, err) } } return preStatesURL, nil } - cannonPreStatesURL, err := getPrestatesUrl(types.TraceTypeCannon) + cannonPreStatesURL, err := getPrestatesUrl(gameTypes.CannonGameType) if err != nil { return nil, err } - cannonKonaPreStatesURL, err := getPrestatesUrl(types.TraceTypeCannonKona) + cannonKonaPreStatesURL, err := getPrestatesUrl(gameTypes.CannonKonaGameType) if err != nil { return nil, err } - asteriscPreStatesURL, err := getPrestatesUrl(types.TraceTypeAsterisc) + asteriscPreStatesURL, err := getPrestatesUrl(gameTypes.AsteriscGameType) if err != nil { return nil, err } - asteriscKonaPreStatesURL, err := getPrestatesUrl(types.TraceTypeAsteriscKona) + asteriscKonaPreStatesURL, err := getPrestatesUrl(gameTypes.AsteriscKonaGameType) if err != nil { return nil, err } @@ -733,7 +739,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro // Required Flags L1EthRpc: l1EthRpc, L1Beacon: l1Beacon, - TraceTypes: traceTypes, + GameTypes: enabledGameTypes, GameFactoryAddress: gameFactoryAddress, GameAllowlist: allowedGames, GameWindow: ctx.Duration(GameWindowFlag.Name), @@ -746,7 +752,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro RollupRpc: ctx.String(RollupRpcFlag.Name), SupervisorRPC: ctx.String(SupervisorRpcFlag.Name), Cannon: vm.Config{ - VmType: types.TraceTypeCannon, + VmType: gameTypes.CannonGameType, L1: l1EthRpc, L1Beacon: l1Beacon, L2s: l2Rpcs, @@ -755,10 +761,10 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro Server: ctx.String(CannonServerFlag.Name), Networks: networks, L2Custom: ctx.Bool(CannonL2CustomFlag.Name), - RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, types.TraceTypeCannon), - L1GenesisPath: L1GenesisFlag.String(ctx, types.TraceTypeCannon), - L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, types.TraceTypeCannon), - DepsetConfigPath: DepsetConfigFlag.String(ctx, types.TraceTypeCannon), + RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, gameTypes.CannonGameType), + L1GenesisPath: L1GenesisFlag.String(ctx, gameTypes.CannonGameType), + L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, gameTypes.CannonGameType), + DepsetConfigPath: DepsetConfigFlag.String(ctx, gameTypes.CannonGameType), SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), DebugInfo: true, @@ -767,7 +773,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), CannonAbsolutePreStateBaseURL: cannonPreStatesURL, CannonKona: vm.Config{ - VmType: types.TraceTypeCannonKona, + VmType: gameTypes.CannonKonaGameType, L1: l1EthRpc, L1Beacon: l1Beacon, L2s: l2Rpcs, @@ -776,10 +782,10 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro Server: ctx.String(CannonKonaServerFlag.Name), Networks: networks, L2Custom: ctx.Bool(CannonKonaL2CustomFlag.Name), - RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, types.TraceTypeCannonKona), - L1GenesisPath: L1GenesisFlag.String(ctx, types.TraceTypeCannonKona), - L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, types.TraceTypeCannonKona), - DepsetConfigPath: DepsetConfigFlag.String(ctx, types.TraceTypeCannonKona), + RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, gameTypes.CannonKonaGameType), + L1GenesisPath: L1GenesisFlag.String(ctx, gameTypes.CannonKonaGameType), + L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, gameTypes.CannonKonaGameType), + DepsetConfigPath: DepsetConfigFlag.String(ctx, gameTypes.CannonKonaGameType), SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), DebugInfo: true, @@ -789,7 +795,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro CannonKonaAbsolutePreStateBaseURL: cannonKonaPreStatesURL, Datadir: ctx.String(DatadirFlag.Name), Asterisc: vm.Config{ - VmType: types.TraceTypeAsterisc, + VmType: gameTypes.AsteriscGameType, L1: l1EthRpc, L1Beacon: l1Beacon, L2s: l2Rpcs, @@ -797,10 +803,10 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro VmBin: ctx.String(AsteriscBinFlag.Name), Server: ctx.String(AsteriscServerFlag.Name), Networks: networks, - RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, types.TraceTypeAsterisc), - L1GenesisPath: L1GenesisFlag.String(ctx, types.TraceTypeAsterisc), - L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, types.TraceTypeAsterisc), - DepsetConfigPath: DepsetConfigFlag.String(ctx, types.TraceTypeAsterisc), + RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, gameTypes.AsteriscGameType), + L1GenesisPath: L1GenesisFlag.String(ctx, gameTypes.AsteriscGameType), + L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, gameTypes.AsteriscGameType), + DepsetConfigPath: DepsetConfigFlag.String(ctx, gameTypes.AsteriscGameType), SnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), BinarySnapshots: true, @@ -808,7 +814,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro AsteriscAbsolutePreState: ctx.String(AsteriscPreStateFlag.Name), AsteriscAbsolutePreStateBaseURL: asteriscPreStatesURL, AsteriscKona: vm.Config{ - VmType: types.TraceTypeAsteriscKona, + VmType: gameTypes.AsteriscKonaGameType, L1: l1EthRpc, L1Beacon: l1Beacon, L2s: l2Rpcs, @@ -817,10 +823,10 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro Server: ctx.String(AsteriscKonaServerFlag.Name), Networks: networks, L2Custom: ctx.Bool(AsteriscKonaL2CustomFlag.Name), - RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, types.TraceTypeAsteriscKona), - L1GenesisPath: L1GenesisFlag.String(ctx, types.TraceTypeAsteriscKona), - L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, types.TraceTypeAsteriscKona), - DepsetConfigPath: DepsetConfigFlag.String(ctx, types.TraceTypeAsteriscKona), + RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, gameTypes.AsteriscKonaGameType), + L1GenesisPath: L1GenesisFlag.String(ctx, gameTypes.AsteriscKonaGameType), + L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, gameTypes.AsteriscKonaGameType), + DepsetConfigPath: DepsetConfigFlag.String(ctx, gameTypes.AsteriscKonaGameType), SnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), BinarySnapshots: true, diff --git a/op-challenger/flags/flags_test.go b/op-challenger/flags/flags_test.go index ca6ae847b7c..f2a7f636ac8 100644 --- a/op-challenger/flags/flags_test.go +++ b/op-challenger/flags/flags_test.go @@ -77,6 +77,7 @@ func TestEnvVarFormat(t *testing.T) { txmgr.FeeLimitMultiplierFlagName, txmgr.TxSendTimeoutFlagName, txmgr.TxNotInMempoolTimeoutFlagName, + GameTypesFlag.Name, // Has multiple env vars for backwards compatibility } t.Run(flagName, func(t *testing.T) { @@ -95,6 +96,11 @@ func TestEnvVarFormat(t *testing.T) { } } +func TestGameTypesFlagEnvVars(t *testing.T) { + envFlags := GameTypesFlag.GetEnvVars() + require.Equal(t, []string{opservice.FlagNameToEnvVarName(GameTypesFlag.Name, "OP_CHALLENGER"), "OP_CHALLENGER_TRACE_TYPE"}, envFlags) +} + func TestResponseDelayFlag(t *testing.T) { t.Run("IncludedInOptionalFlags", func(t *testing.T) { require.Contains(t, optionalFlags, ResponseDelayFlag, "ResponseDelayFlag should be in optionalFlags") diff --git a/op-challenger/flags/vm_flag.go b/op-challenger/flags/vm_flag.go index c7612467bdb..3177641da78 100644 --- a/op-challenger/flags/vm_flag.go +++ b/op-challenger/flags/vm_flag.go @@ -3,23 +3,23 @@ package flags import ( "fmt" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" opservice "github.com/ethereum-optimism/optimism/op-service" "github.com/urfave/cli/v2" ) -type FlagCreator func(name string, envVars []string, traceTypeInfo string) cli.Flag +type FlagCreator func(name string, envVars []string, gameTypeInfo string) cli.Flag // VMFlag defines a set of flags to set a VM specific option. Provides a flag to set the default plus flags to // override the default on a per VM basis. type VMFlag struct { - vms []types.TraceType + vms []gameTypes.GameType name string envVarPrefix string flagCreator FlagCreator } -func NewVMFlag(name string, envVarPrefix string, vms []types.TraceType, flagCreator FlagCreator) *VMFlag { +func NewVMFlag(name string, envVarPrefix string, vms []gameTypes.GameType, flagCreator FlagCreator) *VMFlag { return &VMFlag{ name: name, envVarPrefix: envVarPrefix, @@ -36,7 +36,7 @@ func (f *VMFlag) Flags() []cli.Flag { for _, vm := range f.vms { name := f.TraceSpecificFlagName(vm) envVar := opservice.FlagNameToEnvVarName(name, f.envVarPrefix) - flags = append(flags, f.flagCreator(name, []string{envVar}, fmt.Sprintf("(%v trace type only)", vm))) + flags = append(flags, f.flagCreator(name, []string{envVar}, fmt.Sprintf("(%v game type only)", vm))) } return flags } @@ -45,11 +45,11 @@ func (f *VMFlag) DefaultName() string { return f.name } -func (f *VMFlag) IsSet(ctx *cli.Context, vm types.TraceType) bool { +func (f *VMFlag) IsSet(ctx *cli.Context, vm gameTypes.GameType) bool { return ctx.IsSet(f.TraceSpecificFlagName(vm)) || ctx.IsSet(f.name) } -func (f *VMFlag) String(ctx *cli.Context, vm types.TraceType) string { +func (f *VMFlag) String(ctx *cli.Context, vm gameTypes.GameType) string { val := ctx.String(f.TraceSpecificFlagName(vm)) if val == "" { val = ctx.String(f.name) @@ -57,7 +57,7 @@ func (f *VMFlag) String(ctx *cli.Context, vm types.TraceType) string { return val } -func (f *VMFlag) StringSlice(ctx *cli.Context, vm types.TraceType) []string { +func (f *VMFlag) StringSlice(ctx *cli.Context, vm gameTypes.GameType) []string { val := ctx.StringSlice(f.TraceSpecificFlagName(vm)) if len(val) == 0 { val = ctx.StringSlice(f.name) @@ -65,7 +65,7 @@ func (f *VMFlag) StringSlice(ctx *cli.Context, vm types.TraceType) []string { return val } -func (f *VMFlag) SourceFlagName(ctx *cli.Context, vm types.TraceType) string { +func (f *VMFlag) SourceFlagName(ctx *cli.Context, vm gameTypes.GameType) string { vmFlag := f.TraceSpecificFlagName(vm) if ctx.IsSet(vmFlag) { return vmFlag @@ -73,10 +73,10 @@ func (f *VMFlag) SourceFlagName(ctx *cli.Context, vm types.TraceType) string { return f.name } -func (f *VMFlag) EitherFlagName(vm types.TraceType) string { +func (f *VMFlag) EitherFlagName(vm gameTypes.GameType) string { return fmt.Sprintf("%s/%s", f.DefaultName(), f.TraceSpecificFlagName(vm)) } -func (f *VMFlag) TraceSpecificFlagName(vm types.TraceType) string { +func (f *VMFlag) TraceSpecificFlagName(vm gameTypes.GameType) string { return fmt.Sprintf("%v-%v", vm, f.name) } diff --git a/op-challenger/game/client/provider.go b/op-challenger/game/client/provider.go new file mode 100644 index 00000000000..d827cae977f --- /dev/null +++ b/op-challenger/game/client/provider.go @@ -0,0 +1,107 @@ +package client + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-service/dial" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +var ErrNotInSync = errors.New("local node too far behind") + +type Provider struct { + ctx context.Context + logger log.Logger + cfg *config.Config + l1Client *ethclient.Client + caller *batching.MultiCaller + + l2EL *ethclient.Client + rollupClient *sources.RollupClient + syncValidator *RollupSyncStatusValidator + supervisorClient *sources.SupervisorClient + toClose []func() +} + +func NewProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client *ethclient.Client) *Provider { + return &Provider{ + ctx: ctx, + logger: logger, + cfg: cfg, + l1Client: l1Client, + caller: batching.NewMultiCaller(l1Client.Client(), batching.DefaultBatchSize), + } +} + +func (c *Provider) Close() { + for _, closeFunc := range c.toClose { + closeFunc() + } +} + +func (c *Provider) L1Client() *ethclient.Client { + return c.l1Client +} + +func (c *Provider) MultiCaller() *batching.MultiCaller { + return c.caller +} + +func (c *Provider) SingleChainClients() (*ethclient.Client, *sources.RollupClient, *RollupSyncStatusValidator, error) { + headers, err := c.L2HeaderSource() + if err != nil { + return nil, nil, nil, err + } + rollup, syncValidator, err := c.RollupClients() + if err != nil { + return nil, nil, nil, err + } + return headers, rollup, syncValidator, nil +} + +func (c *Provider) L2HeaderSource() (*ethclient.Client, error) { + if c.l2EL != nil { + return c.l2EL, nil + } + if len(c.cfg.L2Rpcs) != 1 { + return nil, fmt.Errorf("incorrect number of L2 RPCs configured, expected 1 but got %d", len(c.cfg.L2Rpcs)) + } + + l2Client, err := ethclient.DialContext(c.ctx, c.cfg.L2Rpcs[0]) + if err != nil { + return nil, fmt.Errorf("dial l2 client %v: %w", c.cfg.L2Rpcs[0], err) + } + c.l2EL = l2Client + c.toClose = append(c.toClose, l2Client.Close) + return l2Client, nil +} + +func (c *Provider) RollupClients() (*sources.RollupClient, *RollupSyncStatusValidator, error) { + if c.rollupClient != nil { + return c.rollupClient, c.syncValidator, nil + } + rollupClient, err := dial.DialRollupClientWithTimeout(c.ctx, c.logger, c.cfg.RollupRpc) + if err != nil { + return nil, nil, fmt.Errorf("dial rollup client %v: %w", c.cfg.RollupRpc, err) + } + c.rollupClient = rollupClient + c.syncValidator = NewRollupSyncStatusValidator(rollupClient) + c.toClose = append(c.toClose, rollupClient.Close) + return rollupClient, c.syncValidator, nil +} + +func (c *Provider) SuperchainClients() (*sources.SupervisorClient, *SupervisorSyncValidator, error) { + supervisorClient, err := dial.DialSupervisorClientWithTimeout(c.ctx, c.logger, c.cfg.SupervisorRPC) + if err != nil { + return nil, nil, fmt.Errorf("failed to dial supervisor: %w", err) + } + c.supervisorClient = supervisorClient + c.toClose = append(c.toClose, supervisorClient.Close) + return supervisorClient, NewSupervisorSyncValidator(supervisorClient), nil +} diff --git a/op-challenger/game/client/rollup_sync.go b/op-challenger/game/client/rollup_sync.go new file mode 100644 index 00000000000..2b0d47c2214 --- /dev/null +++ b/op-challenger/game/client/rollup_sync.go @@ -0,0 +1,33 @@ +package client + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type syncStatusProvider interface { + SyncStatus(context.Context) (*eth.SyncStatus, error) +} + +type RollupSyncStatusValidator struct { + statusProvider syncStatusProvider +} + +func NewRollupSyncStatusValidator(statusProvider syncStatusProvider) *RollupSyncStatusValidator { + return &RollupSyncStatusValidator{ + statusProvider: statusProvider, + } +} + +func (s *RollupSyncStatusValidator) ValidateNodeSynced(ctx context.Context, gameL1Head eth.BlockID) error { + syncStatus, err := s.statusProvider.SyncStatus(ctx) + if err != nil { + return fmt.Errorf("failed to retrieve local node sync status: %w", err) + } + if syncStatus.CurrentL1.Number <= gameL1Head.Number { + return fmt.Errorf("%w require L1 block above %v but at %v", ErrNotInSync, gameL1Head.Number, syncStatus.CurrentL1.Number) + } + return nil +} diff --git a/op-challenger/game/fault/sync_test.go b/op-challenger/game/client/rollup_sync_test.go similarity index 89% rename from op-challenger/game/fault/sync_test.go rename to op-challenger/game/client/rollup_sync_test.go index 59a98957c1d..8914a1956f4 100644 --- a/op-challenger/game/fault/sync_test.go +++ b/op-challenger/game/client/rollup_sync_test.go @@ -1,11 +1,10 @@ -package fault +package client import ( "context" "errors" "testing" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/stretchr/testify/require" ) @@ -35,7 +34,7 @@ func TestSyncStatusProvider(t *testing.T) { }, }, statusReqErr: nil, - expected: types.ErrNotInSync, + expected: ErrNotInSync, }, { name: "CurrentL1EqualToGameL1Head", @@ -46,7 +45,7 @@ func TestSyncStatusProvider(t *testing.T) { }, }, statusReqErr: nil, - expected: types.ErrNotInSync, + expected: ErrNotInSync, }, { name: "CurrentL1AboveGameL1Head", @@ -67,7 +66,7 @@ func TestSyncStatusProvider(t *testing.T) { status: test.syncStatus, err: test.statusReqErr, } - validator := newSyncStatusValidator(provider) + validator := NewRollupSyncStatusValidator(provider) err := validator.ValidateNodeSynced(context.Background(), test.gameL1Head) require.ErrorIs(t, err, test.expected) }) diff --git a/op-challenger/game/client/supervisor.go b/op-challenger/game/client/supervisor.go new file mode 100644 index 00000000000..f4a57ed2519 --- /dev/null +++ b/op-challenger/game/client/supervisor.go @@ -0,0 +1,33 @@ +package client + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type SupervisorSyncStatusProvider interface { + SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) +} + +type SupervisorSyncValidator struct { + syncStatusProvider SupervisorSyncStatusProvider +} + +func NewSupervisorSyncValidator(syncStatusProvider SupervisorSyncStatusProvider) *SupervisorSyncValidator { + return &SupervisorSyncValidator{ + syncStatusProvider: syncStatusProvider, + } +} + +func (s SupervisorSyncValidator) ValidateNodeSynced(ctx context.Context, gameL1Head eth.BlockID) error { + syncStatus, err := s.syncStatusProvider.SyncStatus(ctx) + if err != nil { + return fmt.Errorf("failed to retrieve sync status: %w", err) + } + if syncStatus.MinSyncedL1.Number <= gameL1Head.Number { + return fmt.Errorf("%w require L1 block above %v but at %v", ErrNotInSync, gameL1Head.Number, syncStatus.MinSyncedL1.Number) + } + return nil +} diff --git a/op-challenger/game/fault/trace/super/sync_test.go b/op-challenger/game/client/supervisor_test.go similarity index 76% rename from op-challenger/game/fault/trace/super/sync_test.go rename to op-challenger/game/client/supervisor_test.go index 300b52c90ea..44bf124af27 100644 --- a/op-challenger/game/fault/trace/super/sync_test.go +++ b/op-challenger/game/client/supervisor_test.go @@ -1,16 +1,15 @@ -package super +package client import ( "context" "errors" "testing" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/stretchr/testify/require" ) -func TestSyncStatusProvider(t *testing.T) { +func TestSupervisorSyncStatusProvider(t *testing.T) { requestErr := errors.New("boom") tests := []struct { name string @@ -31,7 +30,7 @@ func TestSyncStatusProvider(t *testing.T) { syncStatus: eth.SupervisorSyncStatus{ MinSyncedL1: eth.L1BlockRef{Number: 99}, }, - expectedError: types.ErrNotInSync, + expectedError: ErrNotInSync, }, { name: "MinSyncedL1EqualToGameHead", @@ -39,7 +38,7 @@ func TestSyncStatusProvider(t *testing.T) { syncStatus: eth.SupervisorSyncStatus{ MinSyncedL1: eth.L1BlockRef{Number: 100}, }, - expectedError: types.ErrNotInSync, + expectedError: ErrNotInSync, }, { name: "InSync", @@ -54,22 +53,22 @@ func TestSyncStatusProvider(t *testing.T) { for _, test := range tests { test := test // capture range variable t.Run(test.name, func(t *testing.T) { - stubProvider := &stubSyncStatusProvider{ + stubProvider := &stubSupervisorSyncStatusProvider{ status: test.syncStatus, err: test.statusReqErr, } - validator := NewSyncValidator(stubProvider) + validator := NewSupervisorSyncValidator(stubProvider) err := validator.ValidateNodeSynced(context.Background(), eth.BlockID{Number: test.gameL1Head}) require.ErrorIs(t, err, test.expectedError, "expected error to be %v, got %v", test.expectedError, err) }) } } -type stubSyncStatusProvider struct { +type stubSupervisorSyncStatusProvider struct { status eth.SupervisorSyncStatus err error } -func (f *stubSyncStatusProvider) SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) { +func (f *stubSupervisorSyncStatusProvider) SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) { return f.status, f.err } diff --git a/op-challenger/game/fault/agent.go b/op-challenger/game/fault/agent.go index c76331ce667..72249268bed 100644 --- a/op-challenger/game/fault/agent.go +++ b/op-challenger/game/fault/agent.go @@ -9,17 +9,41 @@ import ( "sync/atomic" "time" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/solver" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/game/generic" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) +type TxSender interface { + From() common.Address + SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error +} + +type GameContract interface { + generic.GenericGameLoader + preimages.PreimageGameContract + responder.GameContract + claims.BondContract + ClaimLoader + GetStatus(ctx context.Context) (gameTypes.GameStatus, error) + GetMaxGameDepth(ctx context.Context) (types.Depth, error) + GetMaxClockDuration(ctx context.Context) (time.Duration, error) + GetOracle(ctx context.Context) (contracts.PreimageOracleContract, error) + GetL1Head(ctx context.Context) (common.Hash, error) +} + // Responder takes a response action & executes. // For full op-challenger this means executing the transaction on chain. type Responder interface { @@ -31,6 +55,7 @@ type Responder interface { } type ClaimLoader interface { + GetClaimCount(ctx context.Context) (uint64, error) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) GetClockExtension(ctx context.Context) (time.Duration, error) @@ -39,6 +64,8 @@ type ClaimLoader interface { GetOracle(ctx context.Context) (contracts.PreimageOracleContract, error) } +type resourceCreator func(ctx context.Context, logger log.Logger, gameDepth types.Depth, l1Head eth.BlockID, dir string) (types.TraceAccessor, error) + type Agent struct { metrics metrics.Metricer systemClock clock.Clock @@ -56,6 +83,71 @@ type Agent struct { responseCount atomic.Uint64 // Number of responses made in this game } +func AgentCreator( + systemClock clock.Clock, + l1Clock types.ClockReader, + m metrics.Metricer, + dir string, + txSender TxSender, + loader GameContract, + creator resourceCreator, + selective bool, + claimants []common.Address, + responseDelay time.Duration, + responseDelayAfter uint64, +) generic.ActorCreator { + return func(ctx context.Context, logger log.Logger, l1Head eth.BlockID) (generic.Actor, error) { + maxClockDuration, err := loader.GetMaxClockDuration(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch the game duration: %w", err) + } + + gameDepth, err := loader.GetMaxGameDepth(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch the game depth: %w", err) + } + + accessor, err := creator(ctx, logger, gameDepth, l1Head, dir) + if err != nil { + return nil, fmt.Errorf("failed to create trace accessor: %w", err) + } + + oracle, err := loader.GetOracle(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load oracle: %w", err) + } + + minLargePreimageSize, err := oracle.MinLargePreimageSize(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load min large preimage size: %w", err) + } + direct := preimages.NewDirectPreimageUploader(logger, txSender, loader) + large := preimages.NewLargePreimageUploader(logger, l1Clock, txSender, oracle) + uploader := preimages.NewSplitPreimageUploader(direct, large, minLargePreimageSize) + responder, err := responder.NewFaultResponder(logger, txSender, loader, uploader, oracle) + if err != nil { + return nil, fmt.Errorf("failed to create the responder: %w", err) + } + + agent := NewAgent( + m, + systemClock, + l1Clock, + loader, + gameDepth, + maxClockDuration, + accessor, + responder, + logger, + selective, + claimants, + responseDelay, + responseDelayAfter, + ) + return agent, nil + } +} + func NewAgent( m metrics.Metricer, systemClock clock.Clock, @@ -126,6 +218,14 @@ func (a *Agent) Act(ctx context.Context) error { return nil } +func (a *Agent) AdditionalStatus(ctx context.Context) ([]any, error) { + claimCount, err := a.loader.GetClaimCount(ctx) + if err != nil { + return nil, err + } + return []any{"claims", claimCount}, nil +} + func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, game types.Game, action types.Action) { defer wg.Done() actionLog := a.log.New("action", action.Type) diff --git a/op-challenger/game/fault/agent_test.go b/op-challenger/game/fault/agent_test.go index 8f274dbe106..669c5a23df0 100644 --- a/op-challenger/game/fault/agent_test.go +++ b/op-challenger/game/fault/agent_test.go @@ -236,6 +236,10 @@ func (s *stubClaimLoader) IsL2BlockNumberChallenged(_ context.Context, _ rpcbloc return s.blockNumChallenged, nil } +func (s *stubClaimLoader) GetClaimCount(_ context.Context) (uint64, error) { + return uint64(len(s.claims)), nil +} + func (s *stubClaimLoader) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]types.Claim, error) { s.callCount++ if s.callCount > s.maxLoads && s.maxLoads != 0 { diff --git a/op-challenger/game/fault/clients.go b/op-challenger/game/fault/clients.go deleted file mode 100644 index edb18e9bdea..00000000000 --- a/op-challenger/game/fault/clients.go +++ /dev/null @@ -1,83 +0,0 @@ -package fault - -import ( - "context" - "fmt" - - "github.com/ethereum-optimism/optimism/op-challenger/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" - "github.com/ethereum-optimism/optimism/op-service/dial" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" -) - -type clientProvider struct { - ctx context.Context - logger log.Logger - cfg *config.Config - l2HeaderSource utils.L2HeaderSource - rollupClient RollupClient - syncValidator *syncStatusValidator - rootProvider super.RootProvider - toClose []CloseFunc -} - -func (c *clientProvider) Close() { - for _, closeFunc := range c.toClose { - closeFunc() - } -} - -func (c *clientProvider) SingleChainClients() (utils.L2HeaderSource, RollupClient, *syncStatusValidator, error) { - headers, err := c.L2HeaderSource() - if err != nil { - return nil, nil, nil, err - } - rollup, err := c.RollupClient() - if err != nil { - return nil, nil, nil, err - } - return headers, rollup, c.syncValidator, nil -} - -func (c *clientProvider) L2HeaderSource() (utils.L2HeaderSource, error) { - if c.l2HeaderSource != nil { - return c.l2HeaderSource, nil - } - if len(c.cfg.L2Rpcs) != 1 { - return nil, fmt.Errorf("incorrect number of L2 RPCs configured, expected 1 but got %d", len(c.cfg.L2Rpcs)) - } - - l2Client, err := ethclient.DialContext(c.ctx, c.cfg.L2Rpcs[0]) - if err != nil { - return nil, fmt.Errorf("dial l2 client %v: %w", c.cfg.L2Rpcs[0], err) - } - c.l2HeaderSource = l2Client - c.toClose = append(c.toClose, l2Client.Close) - return l2Client, nil -} - -func (c *clientProvider) RollupClient() (RollupClient, error) { - if c.rollupClient != nil { - return c.rollupClient, nil - } - rollupClient, err := dial.DialRollupClientWithTimeout(c.ctx, c.logger, c.cfg.RollupRpc) - if err != nil { - return nil, fmt.Errorf("dial rollup client %v: %w", c.cfg.RollupRpc, err) - } - c.rollupClient = rollupClient - c.syncValidator = newSyncStatusValidator(rollupClient) - c.toClose = append(c.toClose, rollupClient.Close) - return rollupClient, nil -} - -func (c *clientProvider) SuperchainClients() (super.RootProvider, *super.SyncValidator, error) { - supervisorClient, err := dial.DialSupervisorClientWithTimeout(c.ctx, c.logger, c.cfg.SupervisorRPC) - if err != nil { - return nil, nil, fmt.Errorf("failed to dial supervisor: %w", err) - } - c.rootProvider = supervisorClient - c.toClose = append(c.toClose, supervisorClient.Close) - return supervisorClient, super.NewSyncValidator(supervisorClient), nil -} diff --git a/op-challenger/game/fault/contracts/detect.go b/op-challenger/game/fault/contracts/detect.go index 32c8b03ed90..9093e88ebc1 100644 --- a/op-challenger/game/fault/contracts/detect.go +++ b/op-challenger/game/fault/contracts/detect.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/common" @@ -22,26 +22,26 @@ var ( }]`)) ) -func DetectGameType(ctx context.Context, addr common.Address, caller *batching.MultiCaller) (faultTypes.GameType, error) { +func DetectGameType(ctx context.Context, addr common.Address, caller *batching.MultiCaller) (gameTypes.GameType, error) { result, err := caller.SingleCall(ctx, rpcblock.Latest, batching.NewContractCall(gameTypeABI, addr, methodGameType)) if err != nil { - return faultTypes.UnknownGameType, fmt.Errorf("failed to detect game type: %w", err) + return gameTypes.UnknownGameType, fmt.Errorf("failed to detect game type: %w", err) } - gameType := faultTypes.GameType(result.GetUint32(0)) + gameType := gameTypes.GameType(result.GetUint32(0)) switch gameType { - case faultTypes.CannonGameType, - faultTypes.PermissionedGameType, - faultTypes.CannonKonaGameType, - faultTypes.AsteriscGameType, - faultTypes.AlphabetGameType, - faultTypes.FastGameType, - faultTypes.AsteriscKonaGameType, - faultTypes.SuperCannonGameType, - faultTypes.SuperPermissionedGameType, - faultTypes.SuperCannonKonaGameType, - faultTypes.SuperAsteriscKonaGameType: + case gameTypes.CannonGameType, + gameTypes.PermissionedGameType, + gameTypes.CannonKonaGameType, + gameTypes.AsteriscGameType, + gameTypes.AlphabetGameType, + gameTypes.FastGameType, + gameTypes.AsteriscKonaGameType, + gameTypes.SuperCannonGameType, + gameTypes.SuperPermissionedGameType, + gameTypes.SuperCannonKonaGameType, + gameTypes.SuperAsteriscKonaGameType: return gameType, nil default: - return faultTypes.UnknownGameType, fmt.Errorf("unsupported game type: %d", gameType) + return gameTypes.UnknownGameType, fmt.Errorf("unsupported game type: %d", gameType) } } diff --git a/op-challenger/game/fault/contracts/disputegame.go b/op-challenger/game/fault/contracts/disputegame.go index b1e9ee616f2..5dd756d5099 100644 --- a/op-challenger/game/fault/contracts/disputegame.go +++ b/op-challenger/game/fault/contracts/disputegame.go @@ -6,7 +6,6 @@ import ( "time" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" @@ -35,23 +34,23 @@ type DisputeGameContract interface { } func NewDisputeGameContractForGame(ctx context.Context, metrics metrics.ContractMetricer, caller *batching.MultiCaller, game gameTypes.GameMetadata) (DisputeGameContract, error) { - return NewDisputeGameContract(ctx, metrics, caller, types.GameType(game.GameType), game.Proxy) + return NewDisputeGameContract(ctx, metrics, caller, gameTypes.GameType(game.GameType), game.Proxy) } -func NewDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, caller *batching.MultiCaller, gameType types.GameType, addr common.Address) (DisputeGameContract, error) { +func NewDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, caller *batching.MultiCaller, gameType gameTypes.GameType, addr common.Address) (DisputeGameContract, error) { switch gameType { - case types.SuperCannonGameType, types.SuperCannonKonaGameType, types.SuperPermissionedGameType, types.SuperAsteriscKonaGameType: + case gameTypes.SuperCannonGameType, gameTypes.SuperCannonKonaGameType, gameTypes.SuperPermissionedGameType, gameTypes.SuperAsteriscKonaGameType: return NewSuperFaultDisputeGameContract(ctx, metrics, addr, caller) - case types.CannonGameType, - types.PermissionedGameType, - types.CannonKonaGameType, - types.AsteriscGameType, - types.AlphabetGameType, - types.FastGameType, - types.AsteriscKonaGameType: + case gameTypes.CannonGameType, + gameTypes.PermissionedGameType, + gameTypes.CannonKonaGameType, + gameTypes.AsteriscGameType, + gameTypes.AlphabetGameType, + gameTypes.FastGameType, + gameTypes.AsteriscKonaGameType: return NewPreInteropFaultDisputeGameContract(ctx, metrics, addr, caller) - case types.OptimisticZKGameType: + case gameTypes.OptimisticZKGameType: return NewOptimisticZKDisputeGameContract(metrics, addr, caller) default: return nil, ErrUnsupportedGameType diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index 526c7f0280a..8d5f3fa2baa 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -81,7 +81,7 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe return nil, fmt.Errorf("failed to detect game type: %w", err) } switch gameType { - case types.SuperCannonGameType, types.SuperCannonKonaGameType, types.SuperPermissionedGameType, types.SuperAsteriscKonaGameType: + case gameTypes.SuperCannonGameType, gameTypes.SuperCannonKonaGameType, gameTypes.SuperPermissionedGameType, gameTypes.SuperAsteriscKonaGameType: return NewSuperFaultDisputeGameContract(ctx, metrics, addr, caller) default: return NewPreInteropFaultDisputeGameContract(ctx, metrics, addr, caller) diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index 2cc98abc551..9b3948deb53 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -14,7 +14,7 @@ import ( contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" - "github.com/ethereum-optimism/optimism/op-challenger/game/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" @@ -39,7 +39,7 @@ var ( type contractVersion struct { version string - gameType faultTypes.GameType + gameType gameTypes.GameType loadAbi func() *abi.ABI } @@ -52,7 +52,7 @@ func (c contractVersion) String() string { } func (c contractVersion) IsSuperGame() bool { - return c.gameType == faultTypes.SuperCannonGameType || c.gameType == faultTypes.SuperPermissionedGameType || c.gameType == faultTypes.SuperAsteriscKonaGameType + return c.gameType == gameTypes.SuperCannonGameType || c.gameType == gameTypes.SuperPermissionedGameType || c.gameType == gameTypes.SuperAsteriscKonaGameType } const ( @@ -68,47 +68,47 @@ const ( var versions = []contractVersion{ { version: vers080, - gameType: faultTypes.CannonGameType, + gameType: gameTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi020) }, }, { version: vers0180, - gameType: faultTypes.CannonGameType, + gameType: gameTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi0180) }, }, { version: vers111, - gameType: faultTypes.CannonGameType, + gameType: gameTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi111) }, }, { version: vers120, - gameType: faultTypes.CannonGameType, + gameType: gameTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi120) }, }, { version: vers131, - gameType: faultTypes.CannonGameType, + gameType: gameTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi131) }, }, { version: versLatest, - gameType: faultTypes.CannonGameType, + gameType: gameTypes.CannonGameType, loadAbi: snapshots.LoadFaultDisputeGameABI, }, { version: verSuperCannon, - gameType: faultTypes.SuperCannonGameType, + gameType: gameTypes.SuperCannonGameType, loadAbi: snapshots.LoadSuperFaultDisputeGameABI, }, } @@ -126,7 +126,7 @@ func TestSimpleGetters(t *testing.T) { { methodAlias: "status", method: methodStatus, - result: types.GameStatusChallengerWon, + result: gameTypes.GameStatusChallengerWon, call: func(game FaultDisputeGameContract) (any, error) { return game.GetStatus(context.Background()) }, @@ -192,7 +192,7 @@ func TestSimpleGetters(t *testing.T) { { methodAlias: "resolve", method: methodResolve, - result: types.GameStatusInProgress, + result: gameTypes.GameStatusInProgress, call: func(game FaultDisputeGameContract) (any, error) { return game.CallResolve(context.Background()) }, @@ -581,7 +581,7 @@ func TestGetGameMetadata(t *testing.T) { expectedL2BlockNumber := uint64(123) expectedMaxClockDuration := uint64(456) expectedRootClaim := common.Hash{0x01, 0x02} - expectedStatus := types.GameStatusChallengerWon + expectedStatus := gameTypes.GameStatusChallengerWon expectedL2BlockNumberChallenged := true expectedL2BlockNumberChallenger := common.Address{0xee} block := rpcblock.ByNumber(889) @@ -625,7 +625,7 @@ func TestGetMetadata(t *testing.T) { expectedL1Head := common.Hash{0x0a, 0x0b} expectedL2BlockNumber := uint64(123) expectedRootClaim := common.Hash{0x01, 0x02} - expectedStatus := types.GameStatusChallengerWon + expectedStatus := gameTypes.GameStatusChallengerWon block := rpcblock.ByNumber(889) stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head}) if version.IsSuperGame() { @@ -705,7 +705,7 @@ func TestFaultDisputeGame_GetCredit(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) addr := common.Address{0x01} expectedCredit := big.NewInt(4284) - expectedStatus := types.GameStatusChallengerWon + expectedStatus := gameTypes.GameStatusChallengerWon stubRpc.SetResponse(fdgAddr, methodCredit, rpcblock.Latest, []interface{}{addr}, []interface{}{expectedCredit}) stubRpc.SetResponse(fdgAddr, methodStatus, rpcblock.Latest, nil, []interface{}{expectedStatus}) diff --git a/op-challenger/game/fault/contracts/gamefactory.go b/op-challenger/game/fault/contracts/gamefactory.go index 6b36392de71..4727d0b9cc5 100644 --- a/op-challenger/game/fault/contracts/gamefactory.go +++ b/op-challenger/game/fault/contracts/gamefactory.go @@ -9,8 +9,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/gameargs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" - "github.com/ethereum-optimism/optimism/op-challenger/game/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/txmgr" @@ -39,9 +38,9 @@ var ( //go:embed abis/DisputeGameFactory-1.2.0.json var disputeGameFactoryAbi120 []byte -type gameArgsFunc func(ctx context.Context, caller *batching.MultiCaller, block rpcblock.Block, contract *batching.BoundContract, gameType faultTypes.GameType) ([]byte, error) +type gameArgsFunc func(ctx context.Context, caller *batching.MultiCaller, block rpcblock.Block, contract *batching.BoundContract, gameType gameTypes.GameType) ([]byte, error) -func getGameArgsLatest(ctx context.Context, caller *batching.MultiCaller, block rpcblock.Block, contract *batching.BoundContract, gameType faultTypes.GameType) ([]byte, error) { +func getGameArgsLatest(ctx context.Context, caller *batching.MultiCaller, block rpcblock.Block, contract *batching.BoundContract, gameType gameTypes.GameType) ([]byte, error) { result, err := caller.SingleCall(ctx, block, contract.Call(methodGameArgs, gameType)) if err != nil { return nil, fmt.Errorf("failed to get game args: %w", err) @@ -49,7 +48,7 @@ func getGameArgsLatest(ctx context.Context, caller *batching.MultiCaller, block return result.GetBytes(0), nil } -func getGameArgsNoOp(_ context.Context, _ *batching.MultiCaller, _ rpcblock.Block, _ *batching.BoundContract, _ faultTypes.GameType) ([]byte, error) { +func getGameArgsNoOp(_ context.Context, _ *batching.MultiCaller, _ rpcblock.Block, _ *batching.BoundContract, _ gameTypes.GameType) ([]byte, error) { return nil, nil } @@ -89,9 +88,9 @@ func newDisputeGameFactoryContract(m metrics.ContractMetricer, addr common.Addre } } -func (f *DisputeGameFactoryContract) GetGameFromParameters(ctx context.Context, traceType uint32, outputRoot common.Hash, l2BlockNum uint64) (common.Address, error) { +func (f *DisputeGameFactoryContract) GetGameFromParameters(ctx context.Context, gameType uint32, outputRoot common.Hash, l2BlockNum uint64) (common.Address, error) { defer f.metrics.StartContractRequest("GetGameFromParameters")() - result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodGames, traceType, outputRoot, common.BigToHash(big.NewInt(int64(l2BlockNum))).Bytes())) + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodGames, gameType, outputRoot, common.BigToHash(big.NewInt(int64(l2BlockNum))).Bytes())) if err != nil { return common.Address{}, fmt.Errorf("failed to fetch game from parameters: %w", err) } @@ -107,16 +106,16 @@ func (f *DisputeGameFactoryContract) GetGameCount(ctx context.Context, blockHash return result.GetBigInt(0).Uint64(), nil } -func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockHash common.Hash) (types.GameMetadata, error) { +func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, blockHash common.Hash) (gameTypes.GameMetadata, error) { defer f.metrics.StartContractRequest("GetGame")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.ByHash(blockHash), f.contract.Call(methodGameAtIndex, new(big.Int).SetUint64(idx))) if err != nil { - return types.GameMetadata{}, fmt.Errorf("failed to load game %v: %w", idx, err) + return gameTypes.GameMetadata{}, fmt.Errorf("failed to load game %v: %w", idx, err) } return f.decodeGame(idx, result), nil } -func (f *DisputeGameFactoryContract) getGameImpl(ctx context.Context, gameType faultTypes.GameType) (common.Address, error) { +func (f *DisputeGameFactoryContract) getGameImpl(ctx context.Context, gameType gameTypes.GameType) (common.Address, error) { defer f.metrics.StartContractRequest("GetGameImpl")() result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodGameImpls, gameType)) if err != nil { @@ -125,7 +124,7 @@ func (f *DisputeGameFactoryContract) getGameImpl(ctx context.Context, gameType f return result.GetAddress(0), nil } -func (f *DisputeGameFactoryContract) HasGameImpl(ctx context.Context, gameType faultTypes.GameType) (bool, error) { +func (f *DisputeGameFactoryContract) HasGameImpl(ctx context.Context, gameType gameTypes.GameType) (bool, error) { impl, err := f.getGameImpl(ctx, gameType) if err != nil { return false, err @@ -133,7 +132,7 @@ func (f *DisputeGameFactoryContract) HasGameImpl(ctx context.Context, gameType f return impl != (common.Address{}), nil } -func (f *DisputeGameFactoryContract) GetGameVm(ctx context.Context, gameType faultTypes.GameType) (*VMContract, error) { +func (f *DisputeGameFactoryContract) GetGameVm(ctx context.Context, gameType gameTypes.GameType) (*VMContract, error) { defer f.metrics.StartContractRequest("GetGameVm")() gameArgs, err := f.getGameArgs(ctx, f.multiCaller, rpcblock.Latest, f.contract, gameType) if err != nil { @@ -155,7 +154,7 @@ func (f *DisputeGameFactoryContract) GetGameVm(ctx context.Context, gameType fau return NewVMContract(args.Vm, f.multiCaller), nil } -func (f *DisputeGameFactoryContract) GetGamePrestate(ctx context.Context, gameType faultTypes.GameType) (common.Hash, error) { +func (f *DisputeGameFactoryContract) GetGamePrestate(ctx context.Context, gameType gameTypes.GameType) (common.Hash, error) { defer f.metrics.StartContractRequest("GetGamePrestate")() gameArgs, err := f.getGameArgs(ctx, f.multiCaller, rpcblock.Latest, f.contract, gameType) if err != nil { @@ -177,7 +176,7 @@ func (f *DisputeGameFactoryContract) GetGamePrestate(ctx context.Context, gameTy return args.AbsolutePrestate, nil } -func (f *DisputeGameFactoryContract) faultDisputeGameForType(ctx context.Context, gameType faultTypes.GameType) (FaultDisputeGameContract, error) { +func (f *DisputeGameFactoryContract) faultDisputeGameForType(ctx context.Context, gameType gameTypes.GameType) (FaultDisputeGameContract, error) { addr, err := f.getGameImpl(ctx, gameType) if err != nil { return nil, err @@ -185,7 +184,7 @@ func (f *DisputeGameFactoryContract) faultDisputeGameForType(ctx context.Context return NewFaultDisputeGameContract(ctx, f.metrics, addr, f.multiCaller) } -func (f *DisputeGameFactoryContract) GetGamesAtOrAfter(ctx context.Context, blockHash common.Hash, earliestTimestamp uint64) ([]types.GameMetadata, error) { +func (f *DisputeGameFactoryContract) GetGamesAtOrAfter(ctx context.Context, blockHash common.Hash, earliestTimestamp uint64) ([]gameTypes.GameMetadata, error) { defer f.metrics.StartContractRequest("GetGamesAtOrAfter")() count, err := f.GetGameCount(ctx, blockHash) if err != nil { @@ -194,7 +193,7 @@ func (f *DisputeGameFactoryContract) GetGamesAtOrAfter(ctx context.Context, bloc batchSize := uint64(f.multiCaller.BatchSize()) rangeEnd := count - var games []types.GameMetadata + var games []gameTypes.GameMetadata for { if rangeEnd == uint64(0) { // rangeEnd is exclusive so if its 0 we've reached the end. @@ -230,7 +229,7 @@ func (f *DisputeGameFactoryContract) GetGamesAtOrAfter(ctx context.Context, bloc } } -func (f *DisputeGameFactoryContract) GetAllGames(ctx context.Context, blockHash common.Hash) ([]types.GameMetadata, error) { +func (f *DisputeGameFactoryContract) GetAllGames(ctx context.Context, blockHash common.Hash) ([]gameTypes.GameMetadata, error) { defer f.metrics.StartContractRequest("GetAllGames")() count, err := f.GetGameCount(ctx, blockHash) if err != nil { @@ -247,20 +246,20 @@ func (f *DisputeGameFactoryContract) GetAllGames(ctx context.Context, blockHash return nil, fmt.Errorf("failed to fetch games: %w", err) } - var games []types.GameMetadata + var games []gameTypes.GameMetadata for i, result := range results { games = append(games, f.decodeGame(uint64(i), result)) } return games, nil } -func (f *DisputeGameFactoryContract) CreateTx(ctx context.Context, traceType uint32, outputRoot common.Hash, l2BlockNum uint64) (txmgr.TxCandidate, error) { - result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodInitBonds, traceType)) +func (f *DisputeGameFactoryContract) CreateTx(ctx context.Context, gameType uint32, outputRoot common.Hash, l2BlockNum uint64) (txmgr.TxCandidate, error) { + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, f.contract.Call(methodInitBonds, gameType)) if err != nil { return txmgr.TxCandidate{}, fmt.Errorf("failed to fetch init bond: %w", err) } initBond := result.GetBigInt(0) - call := f.contract.Call(methodCreateGame, traceType, outputRoot, common.BigToHash(big.NewInt(int64(l2BlockNum))).Bytes()) + call := f.contract.Call(methodCreateGame, gameType, outputRoot, common.BigToHash(big.NewInt(int64(l2BlockNum))).Bytes()) candidate, err := call.ToTxCandidate() if err != nil { return txmgr.TxCandidate{}, err @@ -290,11 +289,11 @@ func (f *DisputeGameFactoryContract) DecodeDisputeGameCreatedLog(rcpt *ethTypes. return common.Address{}, 0, common.Hash{}, fmt.Errorf("%w: %v", ErrEventNotFound, eventDisputeGameCreated) } -func (f *DisputeGameFactoryContract) decodeGame(idx uint64, result *batching.CallResult) types.GameMetadata { +func (f *DisputeGameFactoryContract) decodeGame(idx uint64, result *batching.CallResult) gameTypes.GameMetadata { gameType := result.GetUint32(0) timestamp := result.GetUint64(1) proxy := result.GetAddress(2) - return types.GameMetadata{ + return gameTypes.GameMetadata{ Index: idx, GameType: gameType, Timestamp: timestamp, diff --git a/op-challenger/game/fault/contracts/gamefactory_test.go b/op-challenger/game/fault/contracts/gamefactory_test.go index bda80d025bf..bdd468c49f4 100644 --- a/op-challenger/game/fault/contracts/gamefactory_test.go +++ b/op-challenger/game/fault/contracts/gamefactory_test.go @@ -9,8 +9,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/gameargs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" - "github.com/ethereum-optimism/optimism/op-challenger/game/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" @@ -104,25 +103,25 @@ func TestLoadGame(t *testing.T) { t.Run(version.String(), func(t *testing.T) { blockHash := common.Hash{0xbb, 0xce} stubRpc, factory := setupDisputeGameFactoryTest(t, version) - game0 := types.GameMetadata{ + game0 := gameTypes.GameMetadata{ Index: 0, GameType: 0, Timestamp: 1234, Proxy: common.Address{0xaa}, } - game1 := types.GameMetadata{ + game1 := gameTypes.GameMetadata{ Index: 1, GameType: 1, Timestamp: 5678, Proxy: common.Address{0xbb}, } - game2 := types.GameMetadata{ + game2 := gameTypes.GameMetadata{ Index: 2, GameType: 99, Timestamp: 9988, Proxy: common.Address{0xcc}, } - expectedGames := []types.GameMetadata{game0, game1, game2} + expectedGames := []gameTypes.GameMetadata{game0, game1, game2} for idx, expected := range expectedGames { expectGetGame(stubRpc, idx, blockHash, expected) actual, err := factory.GetGame(context.Background(), uint64(idx), blockHash) @@ -138,26 +137,26 @@ func TestGetAllGames(t *testing.T) { t.Run(version.String(), func(t *testing.T) { blockHash := common.Hash{0xbb, 0xce} stubRpc, factory := setupDisputeGameFactoryTest(t, version) - game0 := types.GameMetadata{ + game0 := gameTypes.GameMetadata{ Index: 0, GameType: 0, Timestamp: 1234, Proxy: common.Address{0xaa}, } - game1 := types.GameMetadata{ + game1 := gameTypes.GameMetadata{ Index: 1, GameType: 1, Timestamp: 5678, Proxy: common.Address{0xbb}, } - game2 := types.GameMetadata{ + game2 := gameTypes.GameMetadata{ Index: 2, GameType: 99, Timestamp: 9988, Proxy: common.Address{0xcc}, } - expectedGames := []types.GameMetadata{game0, game1, game2} + expectedGames := []gameTypes.GameMetadata{game0, game1, game2} stubRpc.SetResponse(factoryAddr, methodGameCount, rpcblock.ByHash(blockHash), nil, []interface{}{big.NewInt(int64(len(expectedGames)))}) for idx, expected := range expectedGames { expectGetGame(stubRpc, idx, blockHash, expected) @@ -190,9 +189,9 @@ func TestGetAllGamesAtOrAfter(t *testing.T) { t.Run(fmt.Sprintf("Count_%v_Start_%v", test.gameCount, test.earliestGameIdx), func(t *testing.T) { blockHash := common.Hash{0xbb, 0xce} stubRpc, factory := setupDisputeGameFactoryTest(t, version) - var allGames []types.GameMetadata + var allGames []gameTypes.GameMetadata for i := 0; i < test.gameCount; i++ { - allGames = append(allGames, types.GameMetadata{ + allGames = append(allGames, gameTypes.GameMetadata{ Index: uint64(i), GameType: uint32(i), Timestamp: uint64(i), @@ -209,7 +208,7 @@ func TestGetAllGamesAtOrAfter(t *testing.T) { actualGames, err := factory.GetGamesAtOrAfter(context.Background(), blockHash, earliestTimestamp) require.NoError(t, err) // Games come back in descending timestamp order - var expectedGames []types.GameMetadata + var expectedGames []gameTypes.GameMetadata if test.earliestGameIdx < len(allGames) { expectedGames = slices.Clone(allGames[test.earliestGameIdx:]) } @@ -229,17 +228,17 @@ func TestGetGameFromParameters(t *testing.T) { for _, version := range factoryVersions { t.Run(version.String(), func(t *testing.T) { stubRpc, factory := setupDisputeGameFactoryTest(t, version) - traceType := uint32(123) + gameType := uint32(123) outputRoot := common.Hash{0x01} l2BlockNum := common.BigToHash(big.NewInt(456)).Bytes() stubRpc.SetResponse( factoryAddr, methodGames, rpcblock.Latest, - []interface{}{traceType, outputRoot, l2BlockNum}, + []interface{}{gameType, outputRoot, l2BlockNum}, []interface{}{common.Address{0xaa}, uint64(1)}, ) - addr, err := factory.GetGameFromParameters(context.Background(), traceType, outputRoot, uint64(456)) + addr, err := factory.GetGameFromParameters(context.Background(), gameType, outputRoot, uint64(456)) require.NoError(t, err) require.Equal(t, common.Address{0xaa}, addr) }) @@ -250,7 +249,7 @@ func TestHasGameImpl(t *testing.T) { for _, version := range factoryVersions { t.Run(version.String()+"-set", func(t *testing.T) { stubRpc, factory := setupDisputeGameFactoryTest(t, version) - gameType := faultTypes.CannonGameType + gameType := gameTypes.CannonGameType gameImplAddr := common.Address{0xaa} stubRpc.SetResponse( factoryAddr, @@ -258,20 +257,20 @@ func TestHasGameImpl(t *testing.T) { rpcblock.Latest, []interface{}{gameType}, []interface{}{gameImplAddr}) - actual, err := factory.HasGameImpl(context.Background(), faultTypes.CannonGameType) + actual, err := factory.HasGameImpl(context.Background(), gameTypes.CannonGameType) require.NoError(t, err) require.True(t, actual) }) t.Run(version.String()+"-unset", func(t *testing.T) { stubRpc, factory := setupDisputeGameFactoryTest(t, version) - gameType := faultTypes.CannonGameType + gameType := gameTypes.CannonGameType stubRpc.SetResponse( factoryAddr, methodGameImpls, rpcblock.Latest, []interface{}{gameType}, []interface{}{common.Address{}}) - actual, err := factory.HasGameImpl(context.Background(), faultTypes.CannonGameType) + actual, err := factory.HasGameImpl(context.Background(), gameTypes.CannonGameType) require.NoError(t, err) require.False(t, actual) }) @@ -283,7 +282,7 @@ func TestGetGameVM(t *testing.T) { t.Run(fmt.Sprintf("GameArgs-%v", usesGameArgs), func(t *testing.T) { for _, version := range factoryVersions { t.Run(version.String(), func(t *testing.T) { - gameType := faultTypes.CannonGameType + gameType := gameTypes.CannonGameType rpc, factory := setupDisputeGameFactoryTest(t, version) if usesGameArgs { @@ -322,7 +321,7 @@ func TestGetGamePrestate(t *testing.T) { t.Run(fmt.Sprintf("GameArgs-%v", usesGameArgs), func(t *testing.T) { for _, version := range factoryVersions { t.Run(version.String(), func(t *testing.T) { - gameType := faultTypes.CannonGameType + gameType := gameTypes.CannonGameType prestate := common.Hash{92, 4, 6, 12, 4} rpc, factory := setupDisputeGameFactoryTest(t, version) @@ -428,7 +427,7 @@ func TestDecodeDisputeGameCreatedLog(t *testing.T) { } } -func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game types.GameMetadata) { +func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game gameTypes.GameMetadata) { stubRpc.SetResponse( factoryAddr, methodGameAtIndex, @@ -445,13 +444,13 @@ func TestCreateTx(t *testing.T) { for _, version := range factoryVersions { t.Run(version.String(), func(t *testing.T) { stubRpc, factory := setupDisputeGameFactoryTest(t, version) - traceType := uint32(123) + gameType := uint32(123) outputRoot := common.Hash{0x01} l2BlockNum := common.BigToHash(big.NewInt(456)).Bytes() bond := big.NewInt(49284294829) - stubRpc.SetResponse(factoryAddr, methodInitBonds, rpcblock.Latest, []interface{}{traceType}, []interface{}{bond}) - stubRpc.SetResponse(factoryAddr, methodCreateGame, rpcblock.Latest, []interface{}{traceType, outputRoot, l2BlockNum}, nil) - tx, err := factory.CreateTx(context.Background(), traceType, outputRoot, uint64(456)) + stubRpc.SetResponse(factoryAddr, methodInitBonds, rpcblock.Latest, []interface{}{gameType}, []interface{}{bond}) + stubRpc.SetResponse(factoryAddr, methodCreateGame, rpcblock.Latest, []interface{}{gameType, outputRoot, l2BlockNum}, nil) + tx, err := factory.CreateTx(context.Background(), gameType, outputRoot, uint64(456)) require.NoError(t, err) stubRpc.VerifyTxCandidate(tx) require.NotNil(t, tx.Value) @@ -469,7 +468,7 @@ func setupDisputeGameFactoryTest(t *testing.T, version factoryContractVersion) ( return stubRpc, factory } -func setupDisputeGame(rpc *batchingTest.AbiBasedRpc, gameAddr common.Address, gameType faultTypes.GameType) { +func setupDisputeGame(rpc *batchingTest.AbiBasedRpc, gameAddr common.Address, gameType gameTypes.GameType) { rpc.AddContract(gameAddr, snapshots.LoadFaultDisputeGameABI()) rpc.SetResponse(gameAddr, methodVersion, rpcblock.Latest, nil, []interface{}{versLatest}) rpc.SetResponse(gameAddr, methodGameType, rpcblock.Latest, nil, []interface{}{gameType}) diff --git a/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go b/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go index aba34618429..9c9a393f2fc 100644 --- a/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go +++ b/op-challenger/game/fault/contracts/optimisticzkdisputegame_test.go @@ -7,8 +7,7 @@ import ( "time" contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" - "github.com/ethereum-optimism/optimism/op-challenger/game/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" @@ -24,7 +23,7 @@ const ( var zkVersions = []contractVersion{ { version: versZKLatest, - gameType: faultTypes.OptimisticZKGameType, + gameType: gameTypes.OptimisticZKGameType, loadAbi: snapshots.LoadZKDisputeGameABI, }, } @@ -42,7 +41,7 @@ func TestZKSimpleGetters(t *testing.T) { { methodAlias: "status", method: methodStatus, - result: types.GameStatusChallengerWon, + result: gameTypes.GameStatusChallengerWon, call: func(game OptimisticZKDisputeGameContract) (any, error) { return game.GetStatus(context.Background()) }, @@ -58,7 +57,7 @@ func TestZKSimpleGetters(t *testing.T) { { methodAlias: "resolve", method: methodResolve, - result: types.GameStatusInProgress, + result: gameTypes.GameStatusInProgress, call: func(game OptimisticZKDisputeGameContract) (any, error) { return game.CallResolve(context.Background()) }, @@ -105,7 +104,7 @@ func TestZKGetMetadata(t *testing.T) { expectedL1Head := common.Hash{0x0a, 0x0b} expectedL2BlockNumber := uint64(123) expectedRootClaim := common.Hash{0x01, 0x02} - expectedStatus := types.GameStatusChallengerWon + expectedStatus := gameTypes.GameStatusChallengerWon block := rpcblock.ByNumber(889) stubRpc.SetResponse(fdgAddr, methodL1Head, block, nil, []interface{}{expectedL1Head}) stubRpc.SetResponse(fdgAddr, methodL2SequenceNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) diff --git a/op-challenger/game/fault/register.go b/op-challenger/game/fault/register.go index a5cd1280f35..4373a371159 100644 --- a/op-challenger/game/fault/register.go +++ b/op-challenger/game/fault/register.go @@ -5,16 +5,16 @@ import ( "fmt" "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-challenger/game/client" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/clock" - "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) @@ -22,8 +22,8 @@ import ( type CloseFunc func() type Registry interface { - RegisterGameType(gameType faultTypes.GameType, creator scheduler.PlayerCreator) - RegisterBondContract(gameType faultTypes.GameType, creator claims.BondContractCreator) + RegisterGameType(gameType gameTypes.GameType, creator scheduler.PlayerCreator) + RegisterBondContract(gameType gameTypes.GameType, creator claims.BondContractCreator) } type OracleRegistry interface { @@ -37,11 +37,6 @@ type PrestateSource interface { PrestatePath(ctx context.Context, prestateHash common.Hash) (string, error) } -type RollupClient interface { - outputs.OutputRollupClient - SyncStatusProvider -} - func RegisterGameTypes( ctx context.Context, systemClock clock.Clock, @@ -53,94 +48,92 @@ func RegisterGameTypes( oracles OracleRegistry, txSender TxSender, gameFactory *contracts.DisputeGameFactoryContract, - caller *batching.MultiCaller, - l1HeaderSource L1HeaderSource, + clients *client.Provider, selective bool, claimants []common.Address, -) (CloseFunc, error) { - clients := &clientProvider{ctx: ctx, logger: logger, cfg: cfg} +) error { var registerTasks []*RegisterTask - if cfg.TraceTypeEnabled(faultTypes.TraceTypeCannon) { + if cfg.GameTypeEnabled(gameTypes.CannonGameType) { l2HeaderSource, rollupClient, syncValidator, err := clients.SingleChainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewCannonRegisterTask(faultTypes.CannonGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), l2HeaderSource, rollupClient, syncValidator)) + registerTasks = append(registerTasks, NewCannonRegisterTask(gameTypes.CannonGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), l2HeaderSource, rollupClient, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeCannonKona) { + if cfg.GameTypeEnabled(gameTypes.CannonKonaGameType) { l2HeaderSource, rollupClient, syncValidator, err := clients.SingleChainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewCannonKonaRegisterTask(faultTypes.CannonKonaGameType, cfg, m, vm.NewKonaExecutor(), l2HeaderSource, rollupClient, syncValidator)) + registerTasks = append(registerTasks, NewCannonKonaRegisterTask(gameTypes.CannonKonaGameType, cfg, m, vm.NewKonaExecutor(), l2HeaderSource, rollupClient, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeSuperCannon) { + if cfg.GameTypeEnabled(gameTypes.SuperCannonGameType) { rootProvider, syncValidator, err := clients.SuperchainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewSuperCannonRegisterTask(faultTypes.SuperCannonGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), rootProvider, syncValidator)) + registerTasks = append(registerTasks, NewSuperCannonRegisterTask(gameTypes.SuperCannonGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), rootProvider, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeSuperCannonKona) { + if cfg.GameTypeEnabled(gameTypes.SuperCannonKonaGameType) { rootProvider, syncValidator, err := clients.SuperchainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewSuperCannonKonaRegisterTask(faultTypes.SuperCannonKonaGameType, cfg, m, vm.NewKonaSuperExecutor(), rootProvider, syncValidator)) + registerTasks = append(registerTasks, NewSuperCannonKonaRegisterTask(gameTypes.SuperCannonKonaGameType, cfg, m, vm.NewKonaSuperExecutor(), rootProvider, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypePermissioned) { + if cfg.GameTypeEnabled(gameTypes.PermissionedGameType) { l2HeaderSource, rollupClient, syncValidator, err := clients.SingleChainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewCannonRegisterTask(faultTypes.PermissionedGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), l2HeaderSource, rollupClient, syncValidator)) + registerTasks = append(registerTasks, NewCannonRegisterTask(gameTypes.PermissionedGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), l2HeaderSource, rollupClient, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeSuperPermissioned) { + if cfg.GameTypeEnabled(gameTypes.SuperPermissionedGameType) { rootProvider, syncValidator, err := clients.SuperchainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewSuperCannonRegisterTask(faultTypes.SuperPermissionedGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), rootProvider, syncValidator)) + registerTasks = append(registerTasks, NewSuperCannonRegisterTask(gameTypes.SuperPermissionedGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), rootProvider, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeAsterisc) { + if cfg.GameTypeEnabled(gameTypes.AsteriscGameType) { l2HeaderSource, rollupClient, syncValidator, err := clients.SingleChainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewAsteriscRegisterTask(faultTypes.AsteriscGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), l2HeaderSource, rollupClient, syncValidator)) + registerTasks = append(registerTasks, NewAsteriscRegisterTask(gameTypes.AsteriscGameType, cfg, m, vm.NewOpProgramServerExecutor(logger), l2HeaderSource, rollupClient, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeAsteriscKona) { + if cfg.GameTypeEnabled(gameTypes.AsteriscKonaGameType) { l2HeaderSource, rollupClient, syncValidator, err := clients.SingleChainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewAsteriscKonaRegisterTask(faultTypes.AsteriscKonaGameType, cfg, m, vm.NewKonaExecutor(), l2HeaderSource, rollupClient, syncValidator)) + registerTasks = append(registerTasks, NewAsteriscKonaRegisterTask(gameTypes.AsteriscKonaGameType, cfg, m, vm.NewKonaExecutor(), l2HeaderSource, rollupClient, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeSuperAsteriscKona) { + if cfg.GameTypeEnabled(gameTypes.SuperAsteriscKonaGameType) { rootProvider, syncValidator, err := clients.SuperchainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewSuperAsteriscKonaRegisterTask(faultTypes.SuperAsteriscKonaGameType, cfg, m, vm.NewKonaSuperExecutor(), rootProvider, syncValidator)) + registerTasks = append(registerTasks, NewSuperAsteriscKonaRegisterTask(gameTypes.SuperAsteriscKonaGameType, cfg, m, vm.NewKonaSuperExecutor(), rootProvider, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeFast) { + if cfg.GameTypeEnabled(gameTypes.FastGameType) { l2HeaderSource, rollupClient, syncValidator, err := clients.SingleChainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewAlphabetRegisterTask(faultTypes.FastGameType, l2HeaderSource, rollupClient, syncValidator)) + registerTasks = append(registerTasks, NewAlphabetRegisterTask(gameTypes.FastGameType, l2HeaderSource, rollupClient, syncValidator)) } - if cfg.TraceTypeEnabled(faultTypes.TraceTypeAlphabet) { + if cfg.GameTypeEnabled(gameTypes.AlphabetGameType) { l2HeaderSource, rollupClient, syncValidator, err := clients.SingleChainClients() if err != nil { - return nil, err + return err } - registerTasks = append(registerTasks, NewAlphabetRegisterTask(faultTypes.AlphabetGameType, l2HeaderSource, rollupClient, syncValidator)) + registerTasks = append(registerTasks, NewAlphabetRegisterTask(gameTypes.AlphabetGameType, l2HeaderSource, rollupClient, syncValidator)) } for _, task := range registerTasks { - if err := task.Register(ctx, registry, oracles, systemClock, l1Clock, logger, m, txSender, gameFactory, caller, l1HeaderSource, selective, claimants, cfg.ResponseDelay, cfg.ResponseDelayAfter); err != nil { - return clients.Close, fmt.Errorf("failed to register %v game type: %w", task.gameType, err) + if err := task.Register(ctx, registry, oracles, systemClock, l1Clock, logger, m, txSender, gameFactory, clients.MultiCaller(), clients.L1Client(), selective, claimants, cfg.ResponseDelay, cfg.ResponseDelayAfter); err != nil { + return fmt.Errorf("failed to register %v game type: %w", task.gameType, err) } } - return clients.Close, nil + return nil } diff --git a/op-challenger/game/fault/register_task.go b/op-challenger/game/fault/register_task.go index a5e26500656..dbd10bd2fa4 100644 --- a/op-challenger/game/fault/register_task.go +++ b/op-challenger/game/fault/register_task.go @@ -20,8 +20,9 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/game/generic" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" - "github.com/ethereum-optimism/optimism/op-challenger/game/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -32,10 +33,10 @@ import ( ) type RegisterTask struct { - gameType faultTypes.GameType + gameType gameTypes.GameType skipPrestateValidation bool - syncValidator SyncValidator + syncValidator generic.SyncValidator getTopPrestateProvider func(ctx context.Context, prestateBlock uint64) (faultTypes.PrestateProvider, error) getBottomPrestateProvider func(ctx context.Context, prestateHash common.Hash) (faultTypes.PrestateProvider, error) @@ -51,21 +52,21 @@ type RegisterTask struct { poststateBlock uint64) (*trace.Accessor, error) } -func NewSuperCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator *super.SyncValidator) *RegisterTask { +func NewSuperCannonRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator generic.SyncValidator) *RegisterTask { return newSuperCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, rootProvider, syncValidator, cfg.Cannon, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState) } -func NewSuperCannonKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator *super.SyncValidator) *RegisterTask { +func NewSuperCannonKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator generic.SyncValidator) *RegisterTask { return newSuperCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, rootProvider, syncValidator, cfg.CannonKona, cfg.CannonKonaAbsolutePreStateBaseURL, cfg.CannonKonaAbsolutePreState) } func newSuperCannonVMRegisterTaskWithConfig( - gameType faultTypes.GameType, + gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, - syncValidator SyncValidator, + syncValidator generic.SyncValidator, vmCfg vm.Config, preStateBaseURL *url.URL, preState string, @@ -74,7 +75,7 @@ func newSuperCannonVMRegisterTaskWithConfig( return &RegisterTask{ gameType: gameType, syncValidator: syncValidator, - skipPrestateValidation: gameType == faultTypes.SuperPermissionedGameType, + skipPrestateValidation: gameType == gameTypes.SuperPermissionedGameType, getTopPrestateProvider: func(ctx context.Context, prestateTimestamp uint64) (faultTypes.PrestateProvider, error) { return super.NewSuperRootPrestateProvider(rootProvider, prestateTimestamp), nil }, @@ -105,22 +106,22 @@ func newSuperCannonVMRegisterTaskWithConfig( } } -func NewCannonRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewCannonRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { return newCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, l2Client, rollupClient, syncValidator, cfg.Cannon, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState) } -func NewCannonKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewCannonKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { return newCannonVMRegisterTaskWithConfig(gameType, cfg, m, serverExecutor, l2Client, rollupClient, syncValidator, cfg.CannonKona, cfg.CannonKonaAbsolutePreStateBaseURL, cfg.CannonKonaAbsolutePreState) } func newCannonVMRegisterTaskWithConfig( - gameType faultTypes.GameType, + gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, - syncValidator SyncValidator, + syncValidator generic.SyncValidator, vmCfg vm.Config, preStateBaseURL *url.URL, preState string, @@ -132,7 +133,7 @@ func newCannonVMRegisterTaskWithConfig( // 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, + skipPrestateValidation: gameType == gameTypes.PermissionedGameType, getTopPrestateProvider: func(ctx context.Context, prestateBlock uint64) (faultTypes.PrestateProvider, error) { return outputs.NewPrestateProvider(rollupClient, prestateBlock), nil }, @@ -162,7 +163,7 @@ func newCannonVMRegisterTaskWithConfig( } } -func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewAsteriscRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { stateConverter := asterisc.NewStateConverter(cfg.Asterisc) return &RegisterTask{ gameType: gameType, @@ -196,7 +197,7 @@ func NewAsteriscRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m } } -func NewAsteriscKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { stateConverter := asterisc.NewStateConverter(cfg.Asterisc) return &RegisterTask{ gameType: gameType, @@ -230,12 +231,12 @@ func NewAsteriscKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Confi } } -func NewSuperAsteriscKonaRegisterTask(gameType faultTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator *super.SyncValidator) *RegisterTask { +func NewSuperAsteriscKonaRegisterTask(gameType gameTypes.GameType, cfg *config.Config, m caching.Metrics, serverExecutor vm.OracleServerExecutor, rootProvider super.RootProvider, syncValidator generic.SyncValidator) *RegisterTask { stateConverter := asterisc.NewStateConverter(cfg.AsteriscKona) return &RegisterTask{ gameType: gameType, syncValidator: syncValidator, - skipPrestateValidation: gameType == faultTypes.SuperPermissionedGameType, + skipPrestateValidation: gameType == gameTypes.SuperPermissionedGameType, getTopPrestateProvider: func(ctx context.Context, prestateTimestamp uint64) (faultTypes.PrestateProvider, error) { return super.NewSuperRootPrestateProvider(rootProvider, prestateTimestamp), nil }, @@ -266,7 +267,7 @@ func NewSuperAsteriscKonaRegisterTask(gameType faultTypes.GameType, cfg *config. } } -func NewAlphabetRegisterTask(gameType faultTypes.GameType, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator SyncValidator) *RegisterTask { +func NewAlphabetRegisterTask(gameType gameTypes.GameType, l2Client utils.L2HeaderSource, rollupClient outputs.OutputRollupClient, syncValidator generic.SyncValidator) *RegisterTask { return &RegisterTask{ gameType: gameType, syncValidator: syncValidator, @@ -292,7 +293,7 @@ func NewAlphabetRegisterTask(gameType faultTypes.GameType, l2Client utils.L2Head } func cachePrestates( - gameType faultTypes.GameType, + gameType gameTypes.GameType, stateConverter vm.StateConverter, m caching.Metrics, prestateBaseURL *url.URL, @@ -323,13 +324,13 @@ func (e *RegisterTask) Register( txSender TxSender, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller, - l1HeaderSource L1HeaderSource, + l1HeaderSource generic.L1HeaderSource, selective bool, claimants []common.Address, responseDelay time.Duration, responseDelayAfter uint64) error { - playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { + playerCreator := func(game gameTypes.GameMetadata, dir string) (scheduler.GamePlayer, error) { contract, err := contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) if err != nil { return nil, fmt.Errorf("failed to create fault dispute game contracts: %w", err) @@ -357,27 +358,32 @@ func (e *RegisterTask) Register( if err != nil { return nil, fmt.Errorf("failed to load split depth: %w", err) } - l1HeadID, err := loadL1Head(contract, ctx, l1HeaderSource) - if err != nil { - return nil, err - } prestateProvider, err := e.getTopPrestateProvider(ctx, prestateBlock) if err != nil { return nil, fmt.Errorf("failed to create top prestate provider: %w", err) } - creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, dir string) (faultTypes.TraceAccessor, error) { + creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, l1HeadID eth.BlockID, dir string) (faultTypes.TraceAccessor, error) { accessor, err := e.newTraceAccessor(logger, m, prestateProvider, vmPrestateProvider, dir, l1HeadID, splitDepth, prestateBlock, poststateBlock) if err != nil { return nil, err } return accessor, nil } - var validators []Validator + var validators []generic.PrestateValidator 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, e.syncValidator, validators, creator, l1HeaderSource, selective, claimants, responseDelay, responseDelayAfter) + return generic.NewGenericGamePlayer( + ctx, + logger, + game.Proxy, + contract, + e.syncValidator, + validators, + l1HeaderSource, + AgentCreator(systemClock, l1Clock, m, dir, txSender, contract, creator, selective, claimants, responseDelay, responseDelayAfter), + ) } err := registerOracle(ctx, logger, oracles, gameFactory, e.gameType) if err != nil { @@ -385,14 +391,14 @@ func (e *RegisterTask) Register( } registry.RegisterGameType(e.gameType, playerCreator) - contractCreator := func(game types.GameMetadata) (claims.BondContract, error) { + contractCreator := func(game gameTypes.GameMetadata) (claims.BondContract, error) { return contracts.NewFaultDisputeGameContract(ctx, m, game.Proxy, caller) } registry.RegisterBondContract(e.gameType, contractCreator) return nil } -func registerOracle(ctx context.Context, logger log.Logger, oracles OracleRegistry, gameFactory *contracts.DisputeGameFactoryContract, gameType faultTypes.GameType) error { +func registerOracle(ctx context.Context, logger log.Logger, oracles OracleRegistry, gameFactory *contracts.DisputeGameFactoryContract, gameType gameTypes.GameType) error { // Check that there is an implementation set for this game type and skip if not. hasImpl, err := gameFactory.HasGameImpl(ctx, gameType) if err != nil { @@ -413,15 +419,3 @@ func registerOracle(ctx context.Context, logger log.Logger, oracles OracleRegist oracles.RegisterOracle(oracle) return nil } - -func loadL1Head(contract contracts.FaultDisputeGameContract, ctx context.Context, l1HeaderSource L1HeaderSource) (eth.BlockID, error) { - l1Head, err := contract.GetL1Head(ctx) - if err != nil { - return eth.BlockID{}, fmt.Errorf("failed to load L1 head: %w", err) - } - l1Header, err := l1HeaderSource.HeaderByHash(ctx, l1Head) - if err != nil { - return eth.BlockID{}, fmt.Errorf("failed to load L1 header: %w", err) - } - return eth.HeaderBlockID(l1Header), nil -} diff --git a/op-challenger/game/fault/register_task_test.go b/op-challenger/game/fault/register_task_test.go index 4aa61860979..5eaeb15f458 100644 --- a/op-challenger/game/fault/register_task_test.go +++ b/op-challenger/game/fault/register_task_test.go @@ -7,8 +7,8 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/gameargs" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/registry" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/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/batching" @@ -35,7 +35,7 @@ func TestRegisterOracle_MissingGameImpl(t *testing.T) { logger, logs := testlog.CaptureLogger(t, log.LvlInfo) oracles := registry.NewOracleRegistry() - gameType := faultTypes.CannonGameType + gameType := gameTypes.CannonGameType rpc.SetResponse(gameFactoryAddr, "gameImpls", rpcblock.Latest, []interface{}{gameType}, []interface{}{common.Address{}}) @@ -76,7 +76,7 @@ func TestRegisterOracle_AddsOracle(t *testing.T) { } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - for _, gameType := range []faultTypes.GameType{faultTypes.CannonGameType, faultTypes.SuperCannonGameType, faultTypes.SuperAsteriscKonaGameType} { + for _, gameType := range []gameTypes.GameType{gameTypes.CannonGameType, gameTypes.SuperCannonGameType, gameTypes.SuperAsteriscKonaGameType} { t.Run(fmt.Sprintf("%v", gameType), func(t *testing.T) { gameFactoryAddr := common.Address{0xaa} gameImplAddr := common.Address{0xbb} @@ -84,9 +84,9 @@ func TestRegisterOracle_AddsOracle(t *testing.T) { oracleAddr := common.Address{0xdd} rpc := test.NewAbiBasedRpc(t, gameFactoryAddr, snapshots.LoadDisputeGameFactoryABI()) rpc.SetResponse(gameFactoryAddr, "version", rpcblock.Latest, nil, []interface{}{testCase.version}) - if gameType == faultTypes.CannonGameType { + if gameType == gameTypes.CannonGameType { rpc.AddContract(gameImplAddr, snapshots.LoadFaultDisputeGameABI()) - } else if gameType == faultTypes.SuperCannonGameType || gameType == faultTypes.SuperAsteriscKonaGameType { + } else if gameType == gameTypes.SuperCannonGameType || gameType == gameTypes.SuperAsteriscKonaGameType { rpc.AddContract(gameImplAddr, snapshots.LoadSuperFaultDisputeGameABI()) } else { t.Fatalf("game type %v not supported", gameType) diff --git a/op-challenger/game/fault/sync.go b/op-challenger/game/fault/sync.go deleted file mode 100644 index badbca1bc40..00000000000 --- a/op-challenger/game/fault/sync.go +++ /dev/null @@ -1,34 +0,0 @@ -package fault - -import ( - "context" - "fmt" - - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" - "github.com/ethereum-optimism/optimism/op-service/eth" -) - -type SyncStatusProvider interface { - SyncStatus(context.Context) (*eth.SyncStatus, error) -} - -type syncStatusValidator struct { - statusProvider SyncStatusProvider -} - -func newSyncStatusValidator(statusProvider SyncStatusProvider) *syncStatusValidator { - return &syncStatusValidator{ - statusProvider: statusProvider, - } -} - -func (s *syncStatusValidator) ValidateNodeSynced(ctx context.Context, gameL1Head eth.BlockID) error { - syncStatus, err := s.statusProvider.SyncStatus(ctx) - if err != nil { - return fmt.Errorf("failed to retrieve local node sync status: %w", err) - } - if syncStatus.CurrentL1.Number <= gameL1Head.Number { - return fmt.Errorf("%w require L1 block above %v but at %v", types.ErrNotInSync, gameL1Head.Number, syncStatus.CurrentL1.Number) - } - return nil -} diff --git a/op-challenger/game/fault/trace/super/sync.go b/op-challenger/game/fault/trace/super/sync.go deleted file mode 100644 index ec7348c739b..00000000000 --- a/op-challenger/game/fault/trace/super/sync.go +++ /dev/null @@ -1,34 +0,0 @@ -package super - -import ( - "context" - "fmt" - - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" - "github.com/ethereum-optimism/optimism/op-service/eth" -) - -type SyncValidator struct { - syncStatusProvider SyncStatusProvider -} - -type SyncStatusProvider interface { - SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) -} - -func NewSyncValidator(syncStatusProvider SyncStatusProvider) *SyncValidator { - return &SyncValidator{ - syncStatusProvider: syncStatusProvider, - } -} - -func (s SyncValidator) ValidateNodeSynced(ctx context.Context, gameL1Head eth.BlockID) error { - syncStatus, err := s.syncStatusProvider.SyncStatus(ctx) - if err != nil { - return fmt.Errorf("failed to retrieve sync status: %w", err) - } - if syncStatus.MinSyncedL1.Number <= gameL1Head.Number { - return fmt.Errorf("%w require L1 block above %v but at %v", types.ErrNotInSync, gameL1Head.Number, syncStatus.MinSyncedL1.Number) - } - return nil -} diff --git a/op-challenger/game/fault/trace/vm/executor.go b/op-challenger/game/fault/trace/vm/executor.go index 38fe3791c84..f6908b95bed 100644 --- a/op-challenger/game/fault/trace/vm/executor.go +++ b/op-challenger/game/fault/trace/vm/executor.go @@ -12,11 +12,11 @@ import ( "strings" "time" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-service/jsonutil" @@ -41,7 +41,7 @@ type Metricer = metrics.TypedVmMetricer type Config struct { // VM Configuration - VmType types.TraceType + VmType gameTypes.GameType VmBin string // Path to the vm executable to run when generating trace data SnapshotFreq uint // Frequency of snapshots to create when executing (in VM instructions) InfoFreq uint // Frequency of progress log messages (in VM instructions) diff --git a/op-challenger/game/fault/trace/vm/executor_test.go b/op-challenger/game/fault/trace/vm/executor_test.go index 55b7ab24037..d6bdf48fedd 100644 --- a/op-challenger/game/fault/trace/vm/executor_test.go +++ b/op-challenger/game/fault/trace/vm/executor_test.go @@ -27,7 +27,7 @@ func TestGenerateProof(t *testing.T) { tempDir := t.TempDir() dir := filepath.Join(tempDir, "gameDir") cfg := Config{ - VmType: "test", + VmType: 7248992, L1: "http://localhost:8888", L1Beacon: "http://localhost:9000", L2s: []string{"http://localhost:9999", "http://localhost:9999/two"}, diff --git a/op-challenger/game/fault/types/types.go b/op-challenger/game/fault/types/types.go index 51fe0cf9e13..04269b4b7c2 100644 --- a/op-challenger/game/fault/types/types.go +++ b/op-challenger/game/fault/types/types.go @@ -4,13 +4,10 @@ import ( "context" "encoding/binary" "errors" - "fmt" "math" "math/big" "time" - "slices" - preimage "github.com/ethereum-optimism/optimism/op-preimage" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" @@ -21,149 +18,8 @@ import ( var ( ErrGameDepthReached = errors.New("game depth reached") ErrL2BlockNumberValid = errors.New("l2 block number is valid") - ErrNotInSync = errors.New("local node too far behind") -) - -type GameType uint32 - -const ( - CannonGameType GameType = 0 - PermissionedGameType GameType = 1 - AsteriscGameType GameType = 2 - AsteriscKonaGameType GameType = 3 - SuperCannonGameType GameType = 4 - SuperPermissionedGameType GameType = 5 - OPSuccinctGameType GameType = 6 - SuperAsteriscKonaGameType GameType = 7 - CannonKonaGameType GameType = 8 - SuperCannonKonaGameType GameType = 9 - OptimisticZKGameType GameType = 10 - FastGameType GameType = 254 - AlphabetGameType GameType = 255 - KailuaGameType GameType = 1337 - UnknownGameType GameType = math.MaxUint32 -) - -func (t GameType) MarshalText() ([]byte, error) { - return []byte(t.String()), nil -} - -func (t GameType) String() string { - switch t { - case CannonGameType: - return "cannon" - case PermissionedGameType: - return "permissioned" - case AsteriscGameType: - return "asterisc" - case AsteriscKonaGameType: - return "asterisc-kona" - case SuperCannonGameType: - return "super-cannon" - case SuperPermissionedGameType: - return "super-permissioned" - case OPSuccinctGameType: - return "op-succinct" - case SuperAsteriscKonaGameType: - return "super-asterisc-kona" - case CannonKonaGameType: - return "cannon-kona" - case SuperCannonKonaGameType: - return "super-cannon-kona" - case OptimisticZKGameType: - return "optimistic-zk" - case FastGameType: - return "fast" - case AlphabetGameType: - return "alphabet" - case KailuaGameType: - return "kailua" - default: - return fmt.Sprintf("", t) - } -} - -type TraceType string - -const ( - TraceTypeAlphabet TraceType = "alphabet" - TraceTypeFast TraceType = "fast" - TraceTypeCannon TraceType = "cannon" - TraceTypeCannonKona TraceType = "cannon-kona" - TraceTypeAsterisc TraceType = "asterisc" - TraceTypeAsteriscKona TraceType = "asterisc-kona" - TraceTypePermissioned TraceType = "permissioned" - TraceTypeSuperCannon TraceType = "super-cannon" - TraceTypeSuperCannonKona TraceType = "super-cannon-kona" - TraceTypeSuperPermissioned TraceType = "super-permissioned" - TraceTypeSuperAsteriscKona TraceType = "super-asterisc-kona" ) -var TraceTypes = []TraceType{ - TraceTypeAlphabet, - TraceTypeCannon, - TraceTypeCannonKona, - TraceTypePermissioned, - TraceTypeAsterisc, - TraceTypeAsteriscKona, - TraceTypeFast, - TraceTypeSuperCannon, - TraceTypeSuperCannonKona, - TraceTypeSuperPermissioned, - TraceTypeSuperAsteriscKona, -} - -func (t TraceType) String() string { - return string(t) -} - -// Set implements the Set method required by the [cli.Generic] interface. -func (t *TraceType) Set(value string) error { - if !ValidTraceType(TraceType(value)) { - return fmt.Errorf("unknown trace type: %q", value) - } - *t = TraceType(value) - return nil -} - -func (t *TraceType) Clone() any { - cpy := *t - return &cpy -} - -func ValidTraceType(value TraceType) bool { - return slices.Contains(TraceTypes, value) -} - -func (t TraceType) GameType() GameType { - switch t { - case TraceTypeCannon: - return CannonGameType - case TraceTypeCannonKona: - return CannonKonaGameType - case TraceTypePermissioned: - return PermissionedGameType - case TraceTypeAsterisc: - return AsteriscGameType - case TraceTypeAsteriscKona: - return AsteriscKonaGameType - case TraceTypeFast: - return FastGameType - case TraceTypeAlphabet: - return AlphabetGameType - case TraceTypeSuperCannon: - return SuperCannonGameType - case TraceTypeSuperCannonKona: - return SuperCannonKonaGameType - case TraceTypeSuperPermissioned: - return SuperPermissionedGameType - case TraceTypeSuperAsteriscKona: - return SuperAsteriscKonaGameType - default: - return UnknownGameType - } -} - type ClockReader interface { Now() time.Time } diff --git a/op-challenger/game/fault/types/types_test.go b/op-challenger/game/fault/types/types_test.go index 01f63791e97..ca3e25b7e4b 100644 --- a/op-challenger/game/fault/types/types_test.go +++ b/op-challenger/game/fault/types/types_test.go @@ -60,21 +60,3 @@ func TestIsRootPosition(t *testing.T) { }) } } - -func TestKnownGameTypeForEveryTraceType(t *testing.T) { - for _, traceType := range TraceTypes { - traceType := traceType - t.Run(traceType.String(), func(t *testing.T) { - require.NotEqual(t, UnknownGameType, traceType.GameType()) - }) - } -} - -func TestKnownStringForUtilisedGameType(t *testing.T) { - for _, traceType := range TraceTypes { - traceType := traceType - t.Run(traceType.String(), func(t *testing.T) { - require.NotContains(t, traceType.GameType().String(), "invalid") - }) - } -} diff --git a/op-challenger/game/fault/player.go b/op-challenger/game/generic/player.go similarity index 51% rename from op-challenger/game/fault/player.go rename to op-challenger/game/generic/player.go index 7094696af2d..5545a51b709 100644 --- a/op-challenger/game/fault/player.go +++ b/op-challenger/game/generic/player.go @@ -1,36 +1,35 @@ -package fault +package generic import ( "context" "errors" "fmt" - "time" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger/game/client" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" - "github.com/ethereum-optimism/optimism/op-challenger/metrics" - "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" ) -type actor func(ctx context.Context) error +type PrestateValidator interface { + Validate(ctx context.Context) error +} + +type Actor interface { + Act(ctx context.Context) error + AdditionalStatus(ctx context.Context) ([]any, error) +} -type GameInfo interface { +type GenericGameLoader interface { + GetL1Head(context.Context) (common.Hash, error) GetStatus(context.Context) (gameTypes.GameStatus, error) - GetClaimCount(context.Context) (uint64, error) } type SyncValidator interface { // ValidateNodeSynced checks that the local node is sufficiently up to date to play the game. - // It returns types.ErrNotInSync if the node is too far behind. + // It returns client.ErrNotInSync if the node is too far behind. ValidateNodeSynced(ctx context.Context, gameL1Head eth.BlockID) error } @@ -38,58 +37,32 @@ type L1HeaderSource interface { HeaderByHash(context.Context, common.Hash) (*gethTypes.Header, error) } -type TxSender interface { - From() common.Address - SendAndWaitSimple(txPurpose string, txs ...txmgr.TxCandidate) error -} +type ActorCreator func(ctx context.Context, logger log.Logger, l1Head eth.BlockID) (Actor, error) type GamePlayer struct { - act actor - loader GameInfo + actor Actor + loader GenericGameLoader logger log.Logger syncValidator SyncValidator - prestateValidators []Validator + prestateValidators []PrestateValidator status gameTypes.GameStatus gameL1Head eth.BlockID } -type GameContract interface { - preimages.PreimageGameContract - responder.GameContract - claims.BondContract - GameInfo - ClaimLoader - GetStatus(ctx context.Context) (gameTypes.GameStatus, error) - GetMaxGameDepth(ctx context.Context) (types.Depth, error) - GetMaxClockDuration(ctx context.Context) (time.Duration, error) - GetOracle(ctx context.Context) (contracts.PreimageOracleContract, error) - GetL1Head(ctx context.Context) (common.Hash, error) -} - -var actNoop = func(ctx context.Context) error { - return nil -} +type actNoop struct{} -type resourceCreator func(ctx context.Context, logger log.Logger, gameDepth types.Depth, dir string) (types.TraceAccessor, error) +func (a *actNoop) Act(_ context.Context) error { return nil } +func (a *actNoop) AdditionalStatus(_ context.Context) ([]any, error) { return nil, nil } -func NewGamePlayer( +func NewGenericGamePlayer( ctx context.Context, - systemClock clock.Clock, - l1Clock types.ClockReader, logger log.Logger, - m metrics.Metricer, - dir string, addr common.Address, - txSender TxSender, - loader GameContract, + loader GenericGameLoader, syncValidator SyncValidator, - validators []Validator, - creator resourceCreator, + validators []PrestateValidator, l1HeaderSource L1HeaderSource, - selective bool, - claimants []common.Address, - responseDelay time.Duration, - responseDelayAfter uint64, + createActor ActorCreator, ) (*GamePlayer, error) { logger = logger.New("game", addr) @@ -106,30 +79,9 @@ func NewGamePlayer( prestateValidators: validators, status: status, // Act function does nothing because the game is already complete - act: actNoop, + actor: &actNoop{}, }, nil } - - maxClockDuration, err := loader.GetMaxClockDuration(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch the game duration: %w", err) - } - - gameDepth, err := loader.GetMaxGameDepth(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch the game depth: %w", err) - } - - accessor, err := creator(ctx, logger, gameDepth, dir) - if err != nil { - return nil, fmt.Errorf("failed to create trace accessor: %w", err) - } - - oracle, err := loader.GetOracle(ctx) - if err != nil { - return nil, fmt.Errorf("failed to load oracle: %w", err) - } - l1HeadHash, err := loader.GetL1Head(ctx) if err != nil { return nil, fmt.Errorf("failed to load game L1 head: %w", err) @@ -140,21 +92,13 @@ func NewGamePlayer( } l1Head := eth.HeaderBlockID(l1Header) - minLargePreimageSize, err := oracle.MinLargePreimageSize(ctx) - if err != nil { - return nil, fmt.Errorf("failed to load min large preimage size: %w", err) - } - direct := preimages.NewDirectPreimageUploader(logger, txSender, loader) - large := preimages.NewLargePreimageUploader(logger, l1Clock, txSender, oracle) - uploader := preimages.NewSplitPreimageUploader(direct, large, minLargePreimageSize) - responder, err := responder.NewFaultResponder(logger, txSender, loader, uploader, oracle) + actor, err := createActor(ctx, logger, l1Head) if err != nil { - return nil, fmt.Errorf("failed to create the responder: %w", err) + return nil, fmt.Errorf("failed to create actor: %w", err) } - agent := NewAgent(m, systemClock, l1Clock, loader, gameDepth, maxClockDuration, accessor, responder, logger, selective, claimants, responseDelay, responseDelayAfter) return &GamePlayer{ - act: agent.Act, + actor: actor, loader: loader, logger: logger, status: status, @@ -183,7 +127,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) gameTypes.GameStatus { g.logger.Trace("Skipping completed game") return g.status } - if err := g.syncValidator.ValidateNodeSynced(ctx, g.gameL1Head); errors.Is(err, types.ErrNotInSync) { + if err := g.syncValidator.ValidateNodeSynced(ctx, g.gameL1Head); errors.Is(err, client.ErrNotInSync) { g.logger.Warn("Local node not sufficiently up to date", "err", err) return g.status } else if err != nil { @@ -191,7 +135,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) gameTypes.GameStatus { return g.status } g.logger.Trace("Checking if actions are required") - if err := g.act(ctx); err != nil { + if err := g.actor.Act(ctx); err != nil { g.logger.Error("Error when acting on game", "err", err) } status, err := g.loader.GetStatus(ctx) @@ -203,19 +147,20 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) gameTypes.GameStatus { g.status = status if status != gameTypes.GameStatusInProgress { // Release the agent as we will no longer need to act on this game. - g.act = actNoop + g.actor = &actNoop{} } return status } func (g *GamePlayer) logGameStatus(ctx context.Context, status gameTypes.GameStatus) { if status == gameTypes.GameStatusInProgress { - claimCount, err := g.loader.GetClaimCount(ctx) + additionalStatus, err := g.actor.AdditionalStatus(ctx) if err != nil { - g.logger.Error("Failed to get claim count for in progress game", "err", err) + g.logger.Error("Failed to get additional status info for in progress game", "err", err) return } - g.logger.Info("Game info", "claims", claimCount, "status", status) + additionalStatus = append(additionalStatus, "status", g.status) + g.logger.Info("Game info", additionalStatus...) return } g.logger.Info("Game resolved", "status", status) diff --git a/op-challenger/game/fault/player_test.go b/op-challenger/game/generic/player_test.go similarity index 83% rename from op-challenger/game/fault/player_test.go rename to op-challenger/game/generic/player_test.go index 7c05525a882..ba3348e2c25 100644 --- a/op-challenger/game/fault/player_test.go +++ b/op-challenger/game/generic/player_test.go @@ -1,4 +1,4 @@ -package fault +package generic import ( "context" @@ -33,7 +33,7 @@ func TestProgressGame_LogErrorFromAct(t *testing.T) { msgFilter = testlog.NewMessageFilter("Game info") msg := handler.FindLog(levelFilter, msgFilter) require.NotNil(t, msg) - require.Equal(t, uint64(1), msg.AttrValue("claims")) + require.Equal(t, "statusValue", msg.AttrValue("extra")) } func TestProgressGame_LogGameStatus(t *testing.T) { @@ -93,7 +93,7 @@ func TestDoNotActOnCompleteGame(t *testing.T) { // Should have replaced the act function with a noop so callCount doesn't update even when called directly // This allows the agent resources to be GC'd - require.NoError(t, game.act(context.Background())) + require.NoError(t, game.actor.Act(context.Background())) require.Equal(t, 1, gameState.callCount) }) } @@ -113,27 +113,27 @@ func TestValidateLocalNodeSync(t *testing.T) { func TestValidatePrestate(t *testing.T) { tests := []struct { name string - validators []Validator + validators []PrestateValidator errors bool }{ { name: "SingleValidator", - validators: []Validator{&mockValidator{}}, + validators: []PrestateValidator{&mockValidator{}}, errors: false, }, { name: "MultipleValidators", - validators: []Validator{&mockValidator{}, &mockValidator{}}, + validators: []PrestateValidator{&mockValidator{}, &mockValidator{}}, errors: false, }, { name: "SingleValidator_Errors", - validators: []Validator{&mockValidator{true}}, + validators: []PrestateValidator{&mockValidator{true}}, errors: true, }, { name: "MultipleValidators_Errors", - validators: []Validator{&mockValidator{}, &mockValidator{true}}, + validators: []PrestateValidator{&mockValidator{}, &mockValidator{true}}, errors: true, }, } @@ -153,7 +153,7 @@ func TestValidatePrestate(t *testing.T) { } } -var _ Validator = (*mockValidator)(nil) +var _ PrestateValidator = (*mockValidator)(nil) type mockValidator struct { err bool @@ -171,7 +171,7 @@ func setupProgressGameTest(t *testing.T) (*testlog.CapturingHandler, *GamePlayer gameState := &stubGameState{claimCount: 1} syncValidator := &stubSyncValidator{} game := &GamePlayer{ - act: gameState.Act, + actor: gameState, loader: gameState, logger: logger, syncValidator: syncValidator, @@ -199,19 +199,27 @@ type stubGameState struct { Err error } -func (s *stubGameState) Act(ctx context.Context) error { +func (s *stubGameState) AdditionalStatus(_ context.Context) ([]any, error) { + return []any{"extra", "statusValue"}, nil +} + +func (s *stubGameState) Act(_ context.Context) error { s.callCount++ return s.actErr } -func (s *stubGameState) GetStatus(ctx context.Context) (types.GameStatus, error) { +func (s *stubGameState) GetL1Head(_ context.Context) (common.Hash, error) { + return common.Hash{0x1a}, nil +} + +func (s *stubGameState) GetStatus(_ context.Context) (types.GameStatus, error) { return s.status, nil } -func (s *stubGameState) GetClaimCount(ctx context.Context) (uint64, error) { +func (s *stubGameState) GetClaimCount(_ context.Context) (uint64, error) { return s.claimCount, nil } -func (s *stubGameState) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) { +func (s *stubGameState) GetAbsolutePrestateHash(_ context.Context) (common.Hash, error) { return common.Hash{}, s.Err } diff --git a/op-challenger/game/registry/registry.go b/op-challenger/game/registry/registry.go index 3e016eb0d83..bf7dc255cfd 100644 --- a/op-challenger/game/registry/registry.go +++ b/op-challenger/game/registry/registry.go @@ -5,35 +5,34 @@ import ( "fmt" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" - "github.com/ethereum-optimism/optimism/op-challenger/game/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" ) var ErrUnsupportedGameType = errors.New("unsupported game type") type GameTypeRegistry struct { - types map[faultTypes.GameType]scheduler.PlayerCreator - bondCreators map[faultTypes.GameType]claims.BondContractCreator + types map[gameTypes.GameType]scheduler.PlayerCreator + bondCreators map[gameTypes.GameType]claims.BondContractCreator } func NewGameTypeRegistry() *GameTypeRegistry { return &GameTypeRegistry{ - types: make(map[faultTypes.GameType]scheduler.PlayerCreator), - bondCreators: make(map[faultTypes.GameType]claims.BondContractCreator), + types: make(map[gameTypes.GameType]scheduler.PlayerCreator), + bondCreators: make(map[gameTypes.GameType]claims.BondContractCreator), } } // RegisterGameType registers a scheduler.PlayerCreator to use for a specific game type. // Panics if the same game type is registered multiple times, since this indicates a significant programmer error. -func (r *GameTypeRegistry) RegisterGameType(gameType faultTypes.GameType, creator scheduler.PlayerCreator) { +func (r *GameTypeRegistry) RegisterGameType(gameType gameTypes.GameType, creator scheduler.PlayerCreator) { if _, ok := r.types[gameType]; ok { panic(fmt.Errorf("duplicate creator registered for game type: %v", gameType)) } r.types[gameType] = creator } -func (r *GameTypeRegistry) RegisterBondContract(gameType faultTypes.GameType, creator claims.BondContractCreator) { +func (r *GameTypeRegistry) RegisterBondContract(gameType gameTypes.GameType, creator claims.BondContractCreator) { if _, ok := r.bondCreators[gameType]; ok { panic(fmt.Errorf("duplicate bond contract registered for game type: %v", gameType)) } @@ -41,16 +40,16 @@ func (r *GameTypeRegistry) RegisterBondContract(gameType faultTypes.GameType, cr } // CreatePlayer creates a new game player for the given game, using the specified directory for persisting data. -func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { - creator, ok := r.types[faultTypes.GameType(game.GameType)] +func (r *GameTypeRegistry) CreatePlayer(game gameTypes.GameMetadata, dir string) (scheduler.GamePlayer, error) { + creator, ok := r.types[gameTypes.GameType(game.GameType)] if !ok { return nil, fmt.Errorf("%w: %v", ErrUnsupportedGameType, game.GameType) } return creator(game, dir) } -func (r *GameTypeRegistry) CreateBondContract(game types.GameMetadata) (claims.BondContract, error) { - creator, ok := r.bondCreators[faultTypes.GameType(game.GameType)] +func (r *GameTypeRegistry) CreateBondContract(game gameTypes.GameMetadata) (claims.BondContract, error) { + creator, ok := r.bondCreators[gameTypes.GameType(game.GameType)] if !ok { return nil, fmt.Errorf("%w: %v", ErrUnsupportedGameType, game.GameType) } diff --git a/op-challenger/game/service.go b/op-challenger/game/service.go index 28f279d455b..eb7be17c098 100644 --- a/op-challenger/game/service.go +++ b/op-challenger/game/service.go @@ -7,6 +7,7 @@ import ( "io" "sync/atomic" + challengerClient "github.com/ethereum-optimism/optimism/op-challenger/game/client" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak" "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/fetcher" "github.com/ethereum-optimism/optimism/op-challenger/sender" @@ -38,7 +39,7 @@ type Service struct { monitor *gameMonitor sched *scheduler.Scheduler - faultGamesCloser fault.CloseFunc + clientProvider *challengerClient.Provider preimages *keccak.LargePreimageScheduler @@ -211,9 +212,8 @@ func (s *Service) initBondClaims() error { func (s *Service) registerGameTypes(ctx context.Context, cfg *config.Config) error { gameTypeRegistry := registry.NewGameTypeRegistry() oracles := registry.NewOracleRegistry() - caller := batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize) - closer, err := fault.RegisterGameTypes(ctx, s.systemClock, s.l1Clock, s.logger, s.metrics, cfg, gameTypeRegistry, oracles, s.txSender, s.factoryContract, caller, s.l1Client, cfg.SelectiveClaimResolution, s.claimants) - s.faultGamesCloser = closer + s.clientProvider = challengerClient.NewProvider(ctx, s.logger, cfg, s.l1Client) + err := fault.RegisterGameTypes(ctx, s.systemClock, s.l1Clock, s.logger, s.metrics, cfg, gameTypeRegistry, oracles, s.txSender, s.factoryContract, s.clientProvider, cfg.SelectiveClaimResolution, s.claimants) if err != nil { return err } @@ -272,8 +272,8 @@ func (s *Service) Stop(ctx context.Context) error { result = errors.Join(result, fmt.Errorf("failed to close claimer: %w", err)) } } - if s.faultGamesCloser != nil { - s.faultGamesCloser() + if s.clientProvider != nil { + s.clientProvider.Close() } if s.pprofService != nil { if err := s.pprofService.Stop(ctx); err != nil { diff --git a/op-challenger/game/types/game_type.go b/op-challenger/game/types/game_type.go new file mode 100644 index 00000000000..aec84d80aa0 --- /dev/null +++ b/op-challenger/game/types/game_type.go @@ -0,0 +1,108 @@ +package types + +import ( + "errors" + "fmt" + "math" +) + +var ErrUnknownGameType = errors.New("unknown game type") + +type GameType uint32 + +const ( + CannonGameType GameType = 0 + PermissionedGameType GameType = 1 + AsteriscGameType GameType = 2 + AsteriscKonaGameType GameType = 3 + SuperCannonGameType GameType = 4 + SuperPermissionedGameType GameType = 5 + OPSuccinctGameType GameType = 6 // Not supported by op-challenger + SuperAsteriscKonaGameType GameType = 7 + CannonKonaGameType GameType = 8 + SuperCannonKonaGameType GameType = 9 + OptimisticZKGameType GameType = 10 // Not (yet) supported by op-challenger + FastGameType GameType = 254 + AlphabetGameType GameType = 255 + KailuaGameType GameType = 1337 // Not supported by op-challenger + UnknownGameType GameType = math.MaxUint32 // Not supported by op-challenger +) + +// SupportedGameTypes is the list of game types that are supported by op-challenger. +// Game type codes may be reserved that are not supported by op-challenger. +var SupportedGameTypes = []GameType{ + AlphabetGameType, + CannonGameType, + CannonKonaGameType, + PermissionedGameType, + AsteriscGameType, + AsteriscKonaGameType, + FastGameType, + SuperCannonGameType, + SuperCannonKonaGameType, + SuperPermissionedGameType, + SuperAsteriscKonaGameType, +} + +// Set implements the Set method required by the [cli.Generic] interface. +func (g *GameType) Set(value string) error { + gameType, err := SupportedGameTypeFromString(value) + if err != nil { + return err + } + *g = gameType + return nil +} + +func SupportedGameTypeFromString(s string) (GameType, error) { + for _, candidate := range SupportedGameTypes { + if candidate.String() == s { + return candidate, nil + } + } + return UnknownGameType, fmt.Errorf("%w: %q", ErrUnknownGameType, s) +} + +func (t *GameType) Clone() any { + cpy := *t + return &cpy +} + +func (g GameType) MarshalText() ([]byte, error) { + return []byte(g.String()), nil +} + +func (g GameType) String() string { + switch g { + case CannonGameType: + return "cannon" + case PermissionedGameType: + return "permissioned" + case AsteriscGameType: + return "asterisc" + case AsteriscKonaGameType: + return "asterisc-kona" + case SuperCannonGameType: + return "super-cannon" + case SuperPermissionedGameType: + return "super-permissioned" + case OPSuccinctGameType: + return "op-succinct" + case SuperAsteriscKonaGameType: + return "super-asterisc-kona" + case CannonKonaGameType: + return "cannon-kona" + case SuperCannonKonaGameType: + return "super-cannon-kona" + case OptimisticZKGameType: + return "optimistic-zk" + case FastGameType: + return "fast" + case AlphabetGameType: + return "alphabet" + case KailuaGameType: + return "kailua" + default: + return fmt.Sprintf("", g) + } +} diff --git a/op-challenger/game/types/game_type_test.go b/op-challenger/game/types/game_type_test.go new file mode 100644 index 00000000000..a3631d71fb2 --- /dev/null +++ b/op-challenger/game/types/game_type_test.go @@ -0,0 +1,43 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSetAllSupportedGameTypes(t *testing.T) { + for _, gameType := range SupportedGameTypes { + t.Run(gameType.String(), func(t *testing.T) { + result := new(GameType) + err := result.Set(gameType.String()) + require.NoError(t, err, "failed to set game type") + + require.Equal(t, gameType, *result) + }) + } +} + +func TestGameTypeFromStringForAllSupportedGameTypes(t *testing.T) { + for _, gameType := range SupportedGameTypes { + t.Run(gameType.String(), func(t *testing.T) { + result, err := SupportedGameTypeFromString(gameType.String()) + require.NoError(t, err, "failed to get game type from string") + + require.Equal(t, gameType, result) + }) + } +} + +func TestKnownStringForAllSupportedGameTypes(t *testing.T) { + for _, gameType := range SupportedGameTypes { + t.Run(gameType.String(), func(t *testing.T) { + require.NotContains(t, gameType.String(), "invalid") + }) + } + + t.Run("UnknownGameTypeStringContainsInvalid", func(t *testing.T) { + // Check that the test above would detect if we hit the unknown case + require.Contains(t, GameType(4829482).String(), "invalid") + }) +} diff --git a/op-challenger/runner/factory.go b/op-challenger/runner/factory.go index b5ace96147d..68e71d3427c 100644 --- a/op-challenger/runner/factory.go +++ b/op-challenger/runner/factory.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum/go-ethereum/log" ) @@ -24,12 +25,12 @@ func createTraceProvider( m vm.Metricer, cfg *config.Config, prestateSource prestateFetcher, - traceType types.TraceType, + gameType gameTypes.GameType, localInputs utils.LocalGameInputs, dir string, ) (types.TraceProvider, error) { - switch traceType { - case types.TraceTypeCannon, types.TraceTypeSuperCannon: + switch gameType { + case gameTypes.CannonGameType, gameTypes.SuperCannonGameType: serverExecutor := vm.NewOpProgramServerExecutor(logger) stateConverter := cannon.NewStateConverter(cfg.Cannon) prestate, err := prestateSource.getPrestate(ctx, logger, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState, dir, stateConverter) @@ -38,7 +39,7 @@ func createTraceProvider( } prestateProvider := vm.NewPrestateProvider(prestate, stateConverter) return cannon.NewTraceProvider(logger, m, cfg.Cannon, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil - case types.TraceTypeCannonKona, types.TraceTypeSuperCannonKona: + case gameTypes.CannonKonaGameType, gameTypes.SuperCannonKonaGameType: serverExecutor := vm.NewKonaExecutor() stateConverter := cannon.NewStateConverter(cfg.CannonKona) prestate, err := prestateSource.getPrestate(ctx, logger, cfg.CannonKonaAbsolutePreStateBaseURL, cfg.CannonKonaAbsolutePreState, dir, stateConverter) @@ -47,7 +48,7 @@ func createTraceProvider( } prestateProvider := vm.NewPrestateProvider(prestate, stateConverter) return cannon.NewTraceProvider(logger, m, cfg.CannonKona, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil - case types.TraceTypeAsterisc: + case gameTypes.AsteriscGameType: serverExecutor := vm.NewOpProgramServerExecutor(logger) stateConverter := asterisc.NewStateConverter(cfg.Asterisc) prestate, err := prestateSource.getPrestate(ctx, logger, cfg.AsteriscAbsolutePreStateBaseURL, cfg.AsteriscAbsolutePreState, dir, stateConverter) @@ -56,7 +57,7 @@ func createTraceProvider( } prestateProvider := vm.NewPrestateProvider(prestate, stateConverter) return asterisc.NewTraceProvider(logger, m, cfg.Asterisc, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil - case types.TraceTypeAsteriscKona: + case gameTypes.AsteriscKonaGameType: serverExecutor := vm.NewKonaExecutor() stateConverter := asterisc.NewStateConverter(cfg.AsteriscKona) prestate, err := prestateSource.getPrestate(ctx, logger, cfg.AsteriscKonaAbsolutePreStateBaseURL, cfg.AsteriscKonaAbsolutePreState, dir, stateConverter) @@ -65,7 +66,7 @@ func createTraceProvider( } prestateProvider := vm.NewPrestateProvider(prestate, stateConverter) return asterisc.NewTraceProvider(logger, m, cfg.AsteriscKona, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil - case types.TraceTypeSuperAsteriscKona: + case gameTypes.SuperAsteriscKonaGameType: serverExecutor := vm.NewKonaSuperExecutor() stateConverter := asterisc.NewStateConverter(cfg.AsteriscKona) prestate, err := prestateSource.getPrestate(ctx, logger, cfg.AsteriscKonaAbsolutePreStateBaseURL, cfg.AsteriscKonaAbsolutePreState, dir, stateConverter) @@ -75,5 +76,5 @@ func createTraceProvider( prestateProvider := vm.NewPrestateProvider(prestate, stateConverter) return asterisc.NewTraceProvider(logger, m, cfg.AsteriscKona, serverExecutor, prestateProvider, prestate, localInputs, dir, 42), nil } - return nil, errors.New("invalid trace type") + return nil, errors.New("invalid game type") } diff --git a/op-challenger/runner/game_inputs.go b/op-challenger/runner/game_inputs.go index a17126469d4..2814ec5e48e 100644 --- a/op-challenger/runner/game_inputs.go +++ b/op-challenger/runner/game_inputs.go @@ -10,21 +10,22 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) -func createGameInputs(ctx context.Context, log log.Logger, rollupClient *sources.RollupClient, supervisorClient *sources.SupervisorClient, typeName string, traceType types.TraceType) (utils.LocalGameInputs, error) { - switch traceType { - case types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned, types.TraceTypeSuperAsteriscKona, types.TraceTypeSuperCannonKona: +func createGameInputs(ctx context.Context, log log.Logger, rollupClient *sources.RollupClient, supervisorClient *sources.SupervisorClient, typeName string, gameType gameTypes.GameType) (utils.LocalGameInputs, error) { + switch gameType { + case gameTypes.SuperCannonGameType, gameTypes.SuperPermissionedGameType, gameTypes.SuperAsteriscKonaGameType, gameTypes.SuperCannonKonaGameType: if supervisorClient == nil { - return utils.LocalGameInputs{}, fmt.Errorf("trace type %s requires supervisor rpc to be set", traceType) + return utils.LocalGameInputs{}, fmt.Errorf("game type %s requires supervisor rpc to be set", gameType) } return createGameInputsInterop(ctx, log, supervisorClient, typeName) default: if rollupClient == nil { - return utils.LocalGameInputs{}, fmt.Errorf("trace type %s requires rollup rpc to be set", traceType) + return utils.LocalGameInputs{}, fmt.Errorf("game type %s requires rollup rpc to be set", gameType) } return createGameInputsSingle(ctx, log, rollupClient, typeName) } diff --git a/op-challenger/runner/prestates.go b/op-challenger/runner/prestates.go index ffd8203645e..ecd5746cbc9 100644 --- a/op-challenger/runner/prestates.go +++ b/op-challenger/runner/prestates.go @@ -14,7 +14,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/prestates" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -23,7 +23,7 @@ import ( type OnChainPrestateFetcher struct { m metrics.ContractMetricer gameFactoryAddress common.Address - gameType types.GameType + gameType gameTypes.GameType caller *batching.MultiCaller } diff --git a/op-challenger/runner/runner.go b/op-challenger/runner/runner.go index 53ef8bcfc20..796a9da1897 100644 --- a/op-challenger/runner/runner.go +++ b/op-challenger/runner/runner.go @@ -15,6 +15,7 @@ import ( "sync/atomic" "time" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -49,7 +50,7 @@ type Metricer interface { } type RunConfig struct { - TraceType types.TraceType + GameType gameTypes.GameType Name string Prestate common.Hash PrestateFilename string @@ -138,44 +139,44 @@ func (r *Runner) loop(ctx context.Context, runConfig RunConfig, rollupClient *so } func (r *Runner) runAndRecordOnce(ctx context.Context, rlog log.Logger, runConfig RunConfig, rollupClient *sources.RollupClient, supervisorClient *sources.SupervisorClient, caller *batching.MultiCaller) { - recordError := func(err error, traceType string, m Metricer, log log.Logger) { + recordError := func(err error, configName string, m Metricer, log log.Logger) { if errors.Is(err, ErrUnexpectedStatusCode) { log.Error("Incorrect status code", "type", runConfig.Name, "err", err) - m.RecordInvalid(traceType) + m.RecordInvalid(configName) } else if errors.Is(err, trace.ErrVMPanic) { log.Error("VM panicked", "type", runConfig.Name) - m.RecordPanic(traceType) + m.RecordPanic(configName) } else if err != nil { log.Error("Failed to run", "type", runConfig.Name, "err", err) - m.RecordFailure(traceType) + m.RecordFailure(configName) } else { log.Info("Successfully verified output root", "type", runConfig.Name) - m.RecordSuccess(traceType) + m.RecordSuccess(configName) } } var prestateSource prestateFetcher if strings.HasPrefix(runConfig.PrestateFilename, "file:") { path := runConfig.PrestateFilename[len("file:"):] - rlog.Info("Using local file prestate", "type", runConfig.TraceType, "path", path) + rlog.Info("Using local file prestate", "type", runConfig.GameType, "path", path) prestateSource = &LocalPrestateFetcher{path: path} } else if runConfig.PrestateFilename != "" { - rlog.Info("Using named prestate", "type", runConfig.TraceType, "filename", runConfig.PrestateFilename) + rlog.Info("Using named prestate", "type", runConfig.GameType, "filename", runConfig.PrestateFilename) prestateSource = &NamedPrestateFetcher{filename: runConfig.PrestateFilename} } else if runConfig.Prestate == (common.Hash{}) { - rlog.Info("Using on chain prestate", "type", runConfig.TraceType) + rlog.Info("Using on chain prestate", "type", runConfig.GameType) prestateSource = &OnChainPrestateFetcher{ m: r.m, gameFactoryAddress: r.cfg.GameFactoryAddress, - gameType: runConfig.TraceType.GameType(), + gameType: runConfig.GameType, caller: caller, } } else { - rlog.Info("Using specific prestate", "type", runConfig.TraceType, "hash", runConfig.Prestate) + rlog.Info("Using specific prestate", "type", runConfig.GameType, "hash", runConfig.Prestate) prestateSource = &HashPrestateFetcher{prestateHash: runConfig.Prestate} } - localInputs, err := createGameInputs(ctx, rlog, rollupClient, supervisorClient, runConfig.Name, runConfig.TraceType) + localInputs, err := createGameInputs(ctx, rlog, rollupClient, supervisorClient, runConfig.Name, runConfig.GameType) if err != nil { recordError(err, runConfig.Name, r.m, rlog) return @@ -189,12 +190,12 @@ func (r *Runner) runAndRecordOnce(ctx context.Context, rlog log.Logger, runConfi recordError(err, runConfig.Name, r.m, rlog) return } - err = r.runOnce(ctx, inputsLogger.With("type", runConfig.Name), runConfig.Name, runConfig.TraceType, prestateSource, localInputs, dir) + err = r.runOnce(ctx, inputsLogger.With("type", runConfig.Name), runConfig.Name, runConfig.GameType, prestateSource, localInputs, dir) recordError(err, runConfig.Name, r.m, rlog) } -func (r *Runner) runOnce(ctx context.Context, logger log.Logger, name string, traceType types.TraceType, prestateSource prestateFetcher, localInputs utils.LocalGameInputs, dir string) error { - provider, err := createTraceProvider(ctx, logger, metrics.NewTypedVmMetrics(r.m, name), r.cfg, prestateSource, traceType, localInputs, dir) +func (r *Runner) runOnce(ctx context.Context, logger log.Logger, name string, gameType gameTypes.GameType, prestateSource prestateFetcher, localInputs utils.LocalGameInputs, dir string) error { + provider, err := createTraceProvider(ctx, logger, metrics.NewTypedVmMetrics(r.m, name), r.cfg, prestateSource, gameType, localInputs, dir) if err != nil { return fmt.Errorf("failed to create trace provider: %w", err) } diff --git a/op-challenger/tools/create_game.go b/op-challenger/tools/create_game.go index 4453a195e1d..74ef1a846a4 100644 --- a/op-challenger/tools/create_game.go +++ b/op-challenger/tools/create_game.go @@ -22,8 +22,8 @@ func NewGameCreator(contract *contracts.DisputeGameFactoryContract, txMgr txmgr. } } -func (g *GameCreator) CreateGame(ctx context.Context, outputRoot common.Hash, traceType uint64, l2BlockNum uint64) (common.Address, error) { - txCandidate, err := g.contract.CreateTx(ctx, uint32(traceType), outputRoot, l2BlockNum) +func (g *GameCreator) CreateGame(ctx context.Context, outputRoot common.Hash, gameType uint64, l2BlockNum uint64) (common.Address, error) { + txCandidate, err := g.contract.CreateTx(ctx, uint32(gameType), outputRoot, l2BlockNum) if err != nil { return common.Address{}, fmt.Errorf("failed to create tx: %w", err) } diff --git a/op-deployer/pkg/deployer/pipeline/dispute_games.go b/op-deployer/pkg/deployer/pipeline/dispute_games.go index 2a0c2ecd4aa..8a6850e5328 100644 --- a/op-deployer/pkg/deployer/pipeline/dispute_games.go +++ b/op-deployer/pkg/deployer/pipeline/dispute_games.go @@ -5,7 +5,7 @@ import ( "math/big" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/gameargs" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -101,7 +101,7 @@ func deployDisputeGame( Proposer: thisIntent.Roles.Proposer, Challenger: thisIntent.Roles.Challenger, } - if game.DisputeGameType == uint32(types.PermissionedGameType) { + if game.DisputeGameType == uint32(gameTypes.PermissionedGameType) { gameArgs = args.PackPermissioned() } else { gameArgs = args.PackPermissionless() diff --git a/op-devstack/dsl/fb_builder.go b/op-devstack/dsl/fb_builder.go deleted file mode 100644 index 0820d9c46db..00000000000 --- a/op-devstack/dsl/fb_builder.go +++ /dev/null @@ -1,55 +0,0 @@ -package dsl - -import ( - "time" - - "github.com/ethereum-optimism/optimism/op-devstack/stack" - "github.com/ethereum/go-ethereum/log" -) - -type FlashblocksBuilderSet []*FlashblocksBuilderNode - -func (f FlashblocksBuilderSet) Leader() *FlashblocksBuilderNode { - for _, node := range f { - if node.Conductor().IsLeader() { - return node - } - } - return nil -} - -func NewFlashblocksBuilderSet(inner []stack.FlashblocksBuilderNode) FlashblocksBuilderSet { - flashblocksBuilders := make([]*FlashblocksBuilderNode, len(inner)) - for i, c := range inner { - flashblocksBuilders[i] = NewFlashblocksBuilderNode(c) - } - return flashblocksBuilders -} - -type FlashblocksBuilderNode struct { - commonImpl - inner stack.FlashblocksBuilderNode -} - -func NewFlashblocksBuilderNode(inner stack.FlashblocksBuilderNode) *FlashblocksBuilderNode { - return &FlashblocksBuilderNode{ - commonImpl: commonFromT(inner.T()), - inner: inner, - } -} - -func (c *FlashblocksBuilderNode) String() string { - return c.inner.ID().String() -} - -func (c *FlashblocksBuilderNode) Escape() stack.FlashblocksBuilderNode { - return c.inner -} - -func (c *FlashblocksBuilderNode) Conductor() *Conductor { - return NewConductor(c.inner.Conductor()) -} - -func (c *FlashblocksBuilderNode) ListenFor(logger log.Logger, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { - return websocketListenFor(logger, c.inner.FlashblocksWsUrl(), c.inner.FlashblocksWsHeaders(), duration, output, done) -} diff --git a/op-devstack/dsl/fb_ws_client.go b/op-devstack/dsl/fb_ws_client.go new file mode 100644 index 00000000000..fad80c7aa59 --- /dev/null +++ b/op-devstack/dsl/fb_ws_client.go @@ -0,0 +1,146 @@ +package dsl + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum/go-ethereum/log" + "github.com/gorilla/websocket" +) + +type FlashblocksWSClientSet []*FlashblocksWSClient + +func NewFlashblocksWSClientSet(inner []stack.FlashblocksWSClient) FlashblocksWSClientSet { + flashblocksWSClients := make([]*FlashblocksWSClient, len(inner)) + for i, c := range inner { + flashblocksWSClients[i] = NewFlashblocksWSClient(c) + } + return flashblocksWSClients +} + +type FlashblocksWSClient struct { + commonImpl + inner stack.FlashblocksWSClient +} + +func NewFlashblocksWSClient(inner stack.FlashblocksWSClient) *FlashblocksWSClient { + return &FlashblocksWSClient{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (c *FlashblocksWSClient) String() string { + return c.inner.ID().String() +} + +func (c *FlashblocksWSClient) Escape() stack.FlashblocksWSClient { + return c.inner +} + +func (c *FlashblocksWSClient) ListenFor(ctx context.Context, logger log.Logger, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { + wsURL := c.Escape().WsUrl() + headers := c.Escape().WsHeaders() + return websocketListenFor(ctx, logger, wsURL, headers, duration, output, done) +} + +func websocketListenFor(ctx context.Context, logger log.Logger, wsURL string, headers http.Header, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { + defer close(done) + + listenCtx, cancel := context.WithTimeout(ctx, duration) + defer cancel() + + logger.Debug("Testing WebSocket connection to", "url", wsURL, "headers", headers) + + // Log the headers for debug purposes + if headers != nil { + for key, values := range headers { + logger.Debug("Header", "key", key, "values", values) + } + } else { + logger.Debug("No headers provided") + } + + dialer := &websocket.Dialer{ + HandshakeTimeout: 6 * time.Second, + } + + // Always close the response body to prevent resource leaks + logger.Debug("Attempting WebSocket connection", "url", wsURL) + conn, resp, err := dialer.DialContext(listenCtx, wsURL, headers) + if err != nil { + if listenCtx.Err() != nil { + logger.Info("Context completed before WebSocket connection established", "reason", listenCtx.Err()) + return nil + } + logger.Error("WebSocket connection failed", "url", wsURL, "error", err) + if resp != nil { + logger.Error("HTTP response details", "status", resp.Status, "headers", resp.Header) + resp.Body.Close() + } + return fmt.Errorf("failed to connect to Flashblocks WebSocket endpoint %s: %w", wsURL, err) + } + + if resp != nil { + defer resp.Body.Close() + } + defer conn.Close() + + logger.Info("WebSocket connection established successfully", "url", wsURL, "reading_stream_for", duration) + go func() { + <-listenCtx.Done() + _ = conn.Close() + }() + + messageCount := 0 + for { + select { + case <-listenCtx.Done(): + logListenStop(logger, listenCtx.Err(), messageCount) + return nil + default: + } + + _, message, err := conn.ReadMessage() + if err != nil { + if listenCtx.Err() != nil { + logListenStop(logger, listenCtx.Err(), messageCount) + return nil + } + + if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) { + logger.Info("WebSocket connection closed by peer", "total_messages", messageCount) + return nil + } + + logger.Error("Error reading WebSocket message", "error", err, "message_count", messageCount) + return fmt.Errorf("error reading WebSocket message: %w", err) + } + + messageCount++ + logger.Debug("Received WebSocket message", "message_count", messageCount, "message_length", len(message)) + + select { + case output <- message: + logger.Debug("Message sent to output channel", "message_count", messageCount) + case <-listenCtx.Done(): + logListenStop(logger, listenCtx.Err(), messageCount) + return nil + } + } +} + +func logListenStop(logger log.Logger, reason error, messageCount int) { + switch { + case errors.Is(reason, context.DeadlineExceeded): + logger.Info("WebSocket read duration reached", "total_messages", messageCount) + case errors.Is(reason, context.Canceled): + logger.Info("WebSocket listener canceled", "total_messages", messageCount) + default: + logger.Info("WebSocket listener stopping", "total_messages", messageCount) + } +} diff --git a/op-devstack/dsl/fb_ws_proxy.go b/op-devstack/dsl/fb_ws_proxy.go deleted file mode 100644 index 72fa8c5e3a6..00000000000 --- a/op-devstack/dsl/fb_ws_proxy.go +++ /dev/null @@ -1,116 +0,0 @@ -package dsl - -import ( - "fmt" - "net/http" - "strings" - "time" - - "github.com/ethereum-optimism/optimism/op-devstack/stack" - "github.com/ethereum/go-ethereum/log" - "github.com/gorilla/websocket" -) - -type FlashblocksWebsocketProxySet []*FlashblocksWebsocketProxy - -func NewFlashblocksWebsocketProxySet(inner []stack.FlashblocksWebsocketProxy) FlashblocksWebsocketProxySet { - flashblocksWebsocketProxies := make([]*FlashblocksWebsocketProxy, len(inner)) - for i, c := range inner { - flashblocksWebsocketProxies[i] = NewFlashblocksWebsocketProxy(c) - } - return flashblocksWebsocketProxies -} - -type FlashblocksWebsocketProxy struct { - commonImpl - inner stack.FlashblocksWebsocketProxy -} - -func NewFlashblocksWebsocketProxy(inner stack.FlashblocksWebsocketProxy) *FlashblocksWebsocketProxy { - return &FlashblocksWebsocketProxy{ - commonImpl: commonFromT(inner.T()), - inner: inner, - } -} - -func (c *FlashblocksWebsocketProxy) String() string { - return c.inner.ID().String() -} - -func (c *FlashblocksWebsocketProxy) Escape() stack.FlashblocksWebsocketProxy { - return c.inner -} - -func (c *FlashblocksWebsocketProxy) ListenFor(logger log.Logger, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { - wsURL := c.Escape().WsUrl() - headers := c.Escape().WsHeaders() - return websocketListenFor(logger, wsURL, headers, duration, output, done) -} - -func websocketListenFor(logger log.Logger, wsURL string, headers http.Header, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { - defer close(done) - logger.Debug("Testing WebSocket connection to", "url", wsURL, "headers", headers) - - // Log the headers for debug purposes - if headers != nil { - for key, values := range headers { - logger.Debug("Header", "key", key, "values", values) - } - } else { - logger.Debug("No headers provided") - } - - dialer := &websocket.Dialer{ - HandshakeTimeout: 6 * time.Second, - } - - // Always close the response body to prevent resource leaks - logger.Debug("Attempting WebSocket connection", "url", wsURL) - conn, resp, err := dialer.Dial(wsURL, headers) - if err != nil { - logger.Error("WebSocket connection failed", "url", wsURL, "error", err) - if resp != nil { - logger.Error("HTTP response details", "status", resp.Status, "headers", resp.Header) - resp.Body.Close() - } - return fmt.Errorf("failed to connect to Flashblocks WebSocket endpoint %s: %w", wsURL, err) - } - - if resp != nil { - defer resp.Body.Close() - } - defer conn.Close() - - logger.Info("WebSocket connection established successfully", "url", wsURL, "reading stream for", duration) - - timeout := time.After(duration) - messageCount := 0 - for { - select { - case <-timeout: - logger.Info("WebSocket read timeout reached", "total_messages", messageCount) - return nil - default: - err = conn.SetReadDeadline(time.Now().Add(duration)) - if err != nil { - return fmt.Errorf("failed to set read deadline: %w", err) - } - _, message, err := conn.ReadMessage() - if err != nil && !strings.Contains(err.Error(), "timeout") { - logger.Error("Error reading WebSocket message", "error", err, "message_count", messageCount) - return fmt.Errorf("error reading WebSocket message: %w", err) - } - if err == nil { - messageCount++ - logger.Debug("Received WebSocket message", "message_count", messageCount, "message_length", len(message)) - select { - case output <- message: - logger.Debug("Message sent to output channel", "message_count", messageCount) - case <-timeout: // to avoid indefinite hang - logger.Info("Timeout while sending message to output channel", "total_messages", messageCount) - return nil - } - } - } - } -} diff --git a/op-devstack/dsl/l2_cl.go b/op-devstack/dsl/l2_cl.go index 1ca3556e0e2..862394fc522 100644 --- a/op-devstack/dsl/l2_cl.go +++ b/op-devstack/dsl/l2_cl.go @@ -191,7 +191,8 @@ func (cl *L2CLNode) ReachedRefFn(lvl types.SafetyLevel, target eth.BlockID, atte if err != nil { return err } - ethclient := cl.inner.ELs()[0].EthClient() + + ethclient := cl.inner.ELClient() result, err := ethclient.BlockRefByNumber(cl.ctx, target.Number) if err != nil { return err @@ -230,14 +231,26 @@ func (cl *L2CLNode) Advanced(lvl types.SafetyLevel, delta uint64, attempts int) cl.require.NoError(cl.AdvancedFn(lvl, delta, attempts)()) } +func (cl *L2CLNode) AdvancedUnsafe(delta uint64, attempts int) { + cl.Advanced(types.LocalUnsafe, delta, attempts) +} + func (cl *L2CLNode) NotAdvanced(lvl types.SafetyLevel, attempts int) { cl.require.NoError(cl.NotAdvancedFn(lvl, attempts)()) } +func (cl *L2CLNode) NotAdvancedUnsafe(attempts int) { + cl.NotAdvanced(types.LocalUnsafe, attempts) +} + func (cl *L2CLNode) Reached(lvl types.SafetyLevel, target uint64, attempts int) { cl.require.NoError(cl.ReachedFn(lvl, target, attempts)()) } +func (cl *L2CLNode) ReachedUnsafe(target uint64, attempts int) { + cl.Reached(types.LocalUnsafe, target, attempts) +} + func (cl *L2CLNode) ReachedRef(lvl types.SafetyLevel, target eth.BlockID, attempts int) { cl.require.NoError(cl.ReachedRefFn(lvl, target, attempts)()) } @@ -281,6 +294,10 @@ func (cl *L2CLNode) Matched(refNode SyncStatusProvider, lvl types.SafetyLevel, a cl.require.NoError(cl.MatchedFn(refNode, lvl, attempts)()) } +func (cl *L2CLNode) MatchedUnsafe(refNode SyncStatusProvider, attempts int) { + cl.Matched(refNode, types.LocalUnsafe, attempts) +} + func (cl *L2CLNode) PeerInfo() *apis.PeerInfo { peerInfo, err := retry.Do(cl.ctx, 3, retry.Exponential(), func() (*apis.PeerInfo, error) { return cl.inner.P2PAPI().Self(cl.ctx) @@ -413,3 +430,7 @@ func (cl *L2CLNode) AppendUnsafePayloadUntilTip(verEL, seqEL *L2ELNode, maxAttem func (cl *L2CLNode) UnsafeHead() *BlockRefResult { return &BlockRefResult{T: cl.t, BlockRef: cl.HeadBlockRef(types.LocalUnsafe)} } + +func (cl *L2CLNode) SafeHead() *BlockRefResult { + return &BlockRefResult{T: cl.t, BlockRef: cl.HeadBlockRef(types.CrossSafe)} +} diff --git a/op-devstack/dsl/l2_el.go b/op-devstack/dsl/l2_el.go index 14c0548bef1..19b76925dd2 100644 --- a/op-devstack/dsl/l2_el.go +++ b/op-devstack/dsl/l2_el.go @@ -170,6 +170,10 @@ func (el *L2ELNode) NotAdvanced(label eth.BlockLabel, attempts int) { el.require.NoError(el.NotAdvancedFn(label, attempts)()) } +func (el *L2ELNode) NotAdvancedUnsafe(attempts int) { + el.NotAdvanced(eth.Unsafe, attempts) +} + func (el *L2ELNode) ReorgTriggered(target eth.L2BlockRef, attempts int) { el.require.NoError(el.ReorgTriggeredFn(target, attempts)()) } @@ -349,6 +353,10 @@ func (el *L2ELNode) Matched(refNode SyncStatusProvider, lvl types.SafetyLevel, a el.require.NoError(el.MatchedFn(refNode, lvl, attempts)()) } +func (el *L2ELNode) MatchedUnsafe(refNode SyncStatusProvider, attempts int) { + el.Matched(refNode, types.LocalUnsafe, attempts) +} + func (el *L2ELNode) UnsafeHead() *BlockRefResult { return &BlockRefResult{T: el.t, BlockRef: el.BlockRefByLabel(eth.Unsafe)} } @@ -366,3 +374,7 @@ func (r *BlockRefResult) NumEqualTo(num uint64) *BlockRefResult { r.T.Require().Equal(num, r.BlockRef.Number) return r } + +func (r *BlockRefResult) IsGenesis() *BlockRefResult { + return r.NumEqualTo(0) +} diff --git a/op-devstack/dsl/l2_op_rbuilder.go b/op-devstack/dsl/l2_op_rbuilder.go new file mode 100644 index 00000000000..78f395c51e1 --- /dev/null +++ b/op-devstack/dsl/l2_op_rbuilder.go @@ -0,0 +1,60 @@ +package dsl + +import ( + "context" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum/go-ethereum/log" +) + +type OPRBuilderNodeSet []*OPRBuilderNode + +func NewOPRBuilderNodeSet(inner []stack.OPRBuilderNode, control stack.ControlPlane) OPRBuilderNodeSet { + oprbuilders := make([]*OPRBuilderNode, len(inner)) + for i, c := range inner { + oprbuilders[i] = NewOPRBuilderNode(c, control) + } + return oprbuilders +} + +type OPRBuilderNode struct { + commonImpl + inner stack.OPRBuilderNode + wsClient *FlashblocksWSClient + control stack.ControlPlane +} + +func NewOPRBuilderNode(inner stack.OPRBuilderNode, control stack.ControlPlane) *OPRBuilderNode { + return &OPRBuilderNode{ + commonImpl: commonFromT(inner.T()), + inner: inner, + wsClient: NewFlashblocksWSClient(inner.FlashblocksClient()), + control: control, + } +} + +func (c *OPRBuilderNode) String() string { + return c.inner.ID().String() +} + +func (c *OPRBuilderNode) Escape() stack.OPRBuilderNode { + return c.inner +} + +func (c *OPRBuilderNode) ListenFor(ctx context.Context, logger log.Logger, duration time.Duration, output chan<- []byte, done chan<- struct{}) error { + return c.wsClient.ListenFor(ctx, logger, duration, output, done) +} + +func (el *OPRBuilderNode) Stop() { + el.log.Info("Stopping", "id", el.inner.ID()) + el.control.OPRBuilderNodeState(el.inner.ID(), stack.Stop) +} + +func (el *OPRBuilderNode) Start() { + el.control.OPRBuilderNodeState(el.inner.ID(), stack.Start) +} + +func (el *OPRBuilderNode) FlashblocksClient() *FlashblocksWSClient { + return NewFlashblocksWSClient(el.inner.FlashblocksClient()) +} diff --git a/op-devstack/dsl/proofs/dispute_game_factory.go b/op-devstack/dsl/proofs/dispute_game_factory.go index 75135df20e0..d8247b4c1d5 100644 --- a/op-devstack/dsl/proofs/dispute_game_factory.go +++ b/op-devstack/dsl/proofs/dispute_game_factory.go @@ -13,6 +13,7 @@ import ( "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/vm" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/eth" safetyTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" @@ -150,13 +151,13 @@ func (f *DisputeGameFactory) GameAtIndex(idx int64) *FaultDisputeGame { return NewFaultDisputeGame(f.t, f.require, gameInfo.Proxy, f.getGameHelper, f.honestTraceForGame, game) } -func (f *DisputeGameFactory) GameImpl(gameType challengerTypes.GameType) *FaultDisputeGame { +func (f *DisputeGameFactory) GameImpl(gameType gameTypes.GameType) *FaultDisputeGame { implAddr := contract.Read(f.dgf.GameImpls(uint32(gameType))) game := bindings.NewFaultDisputeGame(bindings.WithClient(f.ethClient), bindings.WithTo(implAddr), bindings.WithTest(f.t)) return NewFaultDisputeGame(f.t, f.require, implAddr, f.getGameHelper, f.honestTraceForGame, game) } -func (f *DisputeGameFactory) GameArgs(gameType challengerTypes.GameType) []byte { +func (f *DisputeGameFactory) GameArgs(gameType gameTypes.GameType) []byte { return contract.Read(f.dgf.GameArgs(uint32(gameType))) } @@ -175,10 +176,10 @@ func (f *DisputeGameFactory) WaitForGame() *FaultDisputeGame { func (f *DisputeGameFactory) StartSuperCannonGame(eoa *dsl.EOA, opts ...GameOpt) *SuperFaultDisputeGame { f.require.NotNil(f.supervisor, "supervisor is required to start super games") - return f.startSuperCannonGameOfType(eoa, challengerTypes.SuperCannonGameType, opts...) + return f.startSuperCannonGameOfType(eoa, gameTypes.SuperCannonGameType, opts...) } -func (f *DisputeGameFactory) startSuperCannonGameOfType(eoa *dsl.EOA, gameType challengerTypes.GameType, opts ...GameOpt) *SuperFaultDisputeGame { +func (f *DisputeGameFactory) startSuperCannonGameOfType(eoa *dsl.EOA, gameType gameTypes.GameType, opts ...GameOpt) *SuperFaultDisputeGame { cfg := NewGameCfg(opts...) timestamp := cfg.l2SequenceNumber if !cfg.l2SequenceNumberSet { @@ -207,11 +208,11 @@ func (f *DisputeGameFactory) createSuperGameExtraData(timestamp uint64, cfg *Gam } func (f *DisputeGameFactory) StartCannonGame(eoa *dsl.EOA, opts ...GameOpt) *FaultDisputeGame { - return f.startOutputRootGameOfType(eoa, challengerTypes.CannonGameType, f.honestTraceForGame, opts...) + return f.startOutputRootGameOfType(eoa, gameTypes.CannonGameType, f.honestTraceForGame, opts...) } func (f *DisputeGameFactory) StartCannonKonaGame(eoa *dsl.EOA, opts ...GameOpt) *FaultDisputeGame { - return f.startOutputRootGameOfType(eoa, challengerTypes.CannonKonaGameType, f.honestTraceForGame, opts...) + return f.startOutputRootGameOfType(eoa, gameTypes.CannonKonaGameType, f.honestTraceForGame, opts...) } func (f *DisputeGameFactory) honestTraceForGame(game *FaultDisputeGame) challengerTypes.TraceAccessor { @@ -220,7 +221,7 @@ func (f *DisputeGameFactory) honestTraceForGame(game *FaultDisputeGame) challeng } f.require.NotNil(f.challengerCfg, "Challenger config is required to create honest trace") switch game.GameType() { - case challengerTypes.CannonGameType: + case gameTypes.CannonGameType: return f.honestOutputCannonTrace( game, f.challengerCfg.CannonAbsolutePreStateBaseURL, @@ -228,7 +229,7 @@ func (f *DisputeGameFactory) honestTraceForGame(game *FaultDisputeGame) challeng f.challengerCfg.Cannon, vm.NewOpProgramServerExecutor(f.log), ) - case challengerTypes.CannonKonaGameType: + case gameTypes.CannonKonaGameType: return f.honestOutputCannonTrace( game, f.challengerCfg.CannonKonaAbsolutePreStateBaseURL, @@ -288,7 +289,7 @@ func (f *DisputeGameFactory) honestOutputCannonTrace( func (f *DisputeGameFactory) startOutputRootGameOfType( eoa *dsl.EOA, - gameType challengerTypes.GameType, + gameType gameTypes.GameType, honestTraceProvider func(game *FaultDisputeGame) challengerTypes.TraceAccessor, opts ...GameOpt) *FaultDisputeGame { cfg := NewGameCfg(opts...) @@ -318,7 +319,7 @@ func (f *DisputeGameFactory) createOutputGameExtraData(blockNum uint64, cfg *Gam return extraData } -func (f *DisputeGameFactory) createNewGame(eoa *dsl.EOA, gameType challengerTypes.GameType, claim common.Hash, extraData []byte) (*bindings.FaultDisputeGame, common.Address) { +func (f *DisputeGameFactory) createNewGame(eoa *dsl.EOA, gameType gameTypes.GameType, claim common.Hash, extraData []byte) (*bindings.FaultDisputeGame, common.Address) { f.log.Info("Creating dispute game", "gameType", gameType, "claim", claim.Hex(), "extradata", common.Bytes2Hex(extraData)) // Pull some metadata we need to construct a new game @@ -337,7 +338,7 @@ func (f *DisputeGameFactory) createNewGame(eoa *dsl.EOA, gameType challengerType return bindings.NewFaultDisputeGame(bindings.WithClient(f.ethClient), bindings.WithTo(gameAddr), bindings.WithTest(f.t)), gameAddr } -func (f *DisputeGameFactory) initBond(gameType challengerTypes.GameType) eth.ETH { +func (f *DisputeGameFactory) initBond(gameType gameTypes.GameType) eth.ETH { return eth.WeiBig(contract.Read(f.dgf.InitBonds(uint32(gameType)))) } diff --git a/op-devstack/dsl/proofs/fault_dispute_game.go b/op-devstack/dsl/proofs/fault_dispute_game.go index d95a2211f48..01ecd7d2c3a 100644 --- a/op-devstack/dsl/proofs/fault_dispute_game.go +++ b/op-devstack/dsl/proofs/fault_dispute_game.go @@ -53,8 +53,8 @@ func NewFaultDisputeGame( return fdg } -func (g *FaultDisputeGame) GameType() challengerTypes.GameType { - return challengerTypes.GameType(contract.Read(g.game.GameType())) +func (g *FaultDisputeGame) GameType() gameTypes.GameType { + return gameTypes.GameType(contract.Read(g.game.GameType())) } func (g *FaultDisputeGame) MaxDepth() challengerTypes.Depth { diff --git a/op-devstack/dsl/proofs/game_helper.go b/op-devstack/dsl/proofs/game_helper.go index 76e60dc2408..095337e0ea4 100644 --- a/op-devstack/dsl/proofs/game_helper.go +++ b/op-devstack/dsl/proofs/game_helper.go @@ -9,6 +9,7 @@ import ( "path/filepath" challengerTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -138,7 +139,7 @@ func (gs *GameHelper) AuthEOA(eoa *dsl.EOA) *GameHelper { func (gs *GameHelper) CreateGameWithClaims( eoa *dsl.EOA, factory *DisputeGameFactory, - gameType challengerTypes.GameType, + gameType gameTypes.GameType, rootClaim common.Hash, extraData []byte, moves []GameHelperMove, diff --git a/op-devstack/dsl/rollup_boost.go b/op-devstack/dsl/rollup_boost.go new file mode 100644 index 00000000000..1638944c1bb --- /dev/null +++ b/op-devstack/dsl/rollup_boost.go @@ -0,0 +1,35 @@ +package dsl + +import "github.com/ethereum-optimism/optimism/op-devstack/stack" + +type RollupBoostNodesSet []*RollupBoostNode + +func NewRollupBoostNodesSet(inner []stack.RollupBoostNode, control stack.ControlPlane) RollupBoostNodesSet { + rollupBoostNodes := make([]*RollupBoostNode, len(inner)) + for i, c := range inner { + rollupBoostNodes[i] = NewRollupBoostNode(c, control) + } + return rollupBoostNodes +} + +// RollupBoostNode wraps a stack.RollupBoostNode interface for DSL operations +type RollupBoostNode struct { + inner stack.RollupBoostNode + control stack.ControlPlane +} + +func (r *RollupBoostNode) Escape() stack.RollupBoostNode { + return r.inner +} + +// NewRollupBoostNode creates a new RollupBoostNode DSL wrapper +func NewRollupBoostNode(inner stack.RollupBoostNode, control stack.ControlPlane) *RollupBoostNode { + return &RollupBoostNode{ + inner, + control, + } +} + +func (r *RollupBoostNode) FlashblocksClient() *FlashblocksWSClient { + return NewFlashblocksWSClient(r.inner.FlashblocksClient()) +} diff --git a/op-devstack/presets/cl_config.go b/op-devstack/presets/cl_config.go index 3760cb3ab9a..41109fc23e3 100644 --- a/op-devstack/presets/cl_config.go +++ b/op-devstack/presets/cl_config.go @@ -56,3 +56,12 @@ func WithNoDiscovery() stack.CommonOption { cfg.NoDiscovery = true }))) } + +func WithUnsafeOnly() stack.CommonOption { + return stack.MakeCommon( + sysgo.WithGlobalL2CLOption(sysgo.L2CLOptionFn( + func(_ devtest.P, id stack.L2CLNodeID, cfg *sysgo.L2CLConfig) { + cfg.SequencerUnsafeOnly = true + cfg.VerifierUnsafeOnly = true + }))) +} diff --git a/op-devstack/presets/flashblocks.go b/op-devstack/presets/flashblocks.go index 43b9deb504d..60298b917f6 100644 --- a/op-devstack/presets/flashblocks.go +++ b/op-devstack/presets/flashblocks.go @@ -1,71 +1,85 @@ package presets import ( - "github.com/ethereum-optimism/optimism/op-devstack/compat" + "time" + + challengerConfig "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/dsl/proofs" "github.com/ethereum-optimism/optimism/op-devstack/shim" "github.com/ethereum-optimism/optimism/op-devstack/stack" "github.com/ethereum-optimism/optimism/op-devstack/stack/match" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" ) -type SimpleFlashblocks struct { +type SingleChainWithFlashblocks struct { *Minimal - ConductorSets map[stack.L2NetworkID]dsl.ConductorSet - FlashblocksBuilderSets map[stack.L2NetworkID]dsl.FlashblocksBuilderSet - FlashblocksWebsocketProxies map[stack.L2NetworkID]dsl.FlashblocksWebsocketProxySet - - Faucets map[stack.L2NetworkID]*dsl.Faucet - Funders map[stack.L2NetworkID]*dsl.Funder - L2ELNodes map[stack.L2NetworkID]*dsl.L2ELNode + L2OPRBuilder *dsl.OPRBuilderNode + L2RollupBoost *dsl.RollupBoostNode + TestSequencer *dsl.TestSequencer } -func WithSimpleFlashblocks() stack.CommonOption { - return stack.Combine( - stack.MakeCommon(sysgo.DefaultMinimalSystem(&sysgo.DefaultMinimalSystemIDs{})), - // TODO(#16450): add sysgo support for flashblocks - WithCompatibleTypes(compat.Persistent, compat.Kurtosis), - ) +func (m *SingleChainWithFlashblocks) L2Networks() []*dsl.L2Network { + return []*dsl.L2Network{ + m.L2Chain, + } } -func NewSimpleFlashblocks(t devtest.T) *SimpleFlashblocks { - system := shim.NewSystem(t) - orch := Orchestrator() - orch.Hydrate(system) - chains := system.L2Networks() - - minimalPreset := NewMinimal(t) +func (m *SingleChainWithFlashblocks) StandardBridge() *dsl.StandardBridge { + return dsl.NewStandardBridge(m.T, m.L2Chain, nil, m.L1EL) +} - conductorSets := make(map[stack.L2NetworkID]dsl.ConductorSet) - flashblocksBuilderSets := make(map[stack.L2NetworkID]dsl.FlashblocksBuilderSet) - faucets := make(map[stack.L2NetworkID]*dsl.Faucet) - funders := make(map[stack.L2NetworkID]*dsl.Funder) - l2ELNodes := make(map[stack.L2NetworkID]*dsl.L2ELNode) - flashblocksWebsocketProxies := make(map[stack.L2NetworkID]dsl.FlashblocksWebsocketProxySet) +func (m *SingleChainWithFlashblocks) DisputeGameFactory() *proofs.DisputeGameFactory { + return proofs.NewDisputeGameFactory(m.T, m.L1Network, m.L1EL.EthClient(), m.L2Chain.DisputeGameFactoryProxyAddr(), m.L2CL, m.L2EL, nil, m.challengerConfig) +} - for _, chain := range chains { - chainMatcher := match.L2ChainById(chain.ID()) - l2 := system.L2Network(match.Assume(t, chainMatcher)) - firstELNode := dsl.NewL2ELNode(l2.L2ELNode(match.FirstL2EL), orch.ControlPlane()) - firstFaucet := dsl.NewFaucet(l2.Faucet(match.Assume(t, match.FirstFaucet))) +func (m *SingleChainWithFlashblocks) AdvanceTime(amount time.Duration) { + ttSys, ok := m.system.(stack.TimeTravelSystem) + m.T.Require().True(ok, "attempting to advance time on incompatible system") + ttSys.AdvanceTime(amount) +} - conductorSets[chain.ID()] = dsl.NewConductorSet(l2.Conductors()) - flashblocksBuilderSets[chain.ID()] = dsl.NewFlashblocksBuilderSet(l2.FlashblocksBuilders()) - flashblocksWebsocketProxies[chain.ID()] = dsl.NewFlashblocksWebsocketProxySet(l2.FlashblocksWebsocketProxies()) +func WithSingleChainSystemWithFlashblocks() stack.CommonOption { + return stack.MakeCommon(sysgo.DefaultSingleChainSystemWithFlashblocks(&sysgo.SingleChainSystemWithFlashblocksIDs{})) +} - faucets[chain.ID()] = firstFaucet - funders[chain.ID()] = dsl.NewFunder(minimalPreset.Wallet, firstFaucet, firstELNode) - l2ELNodes[chain.ID()] = firstELNode +func NewSingleChainWithFlashblocks(t devtest.T) *SingleChainWithFlashblocks { + system := shim.NewSystem(t) + orch := Orchestrator() + orch.Hydrate(system) + l1Net := system.L1Network(match.FirstL1Network) + l2 := system.L2Network(match.Assume(t, match.L2ChainA)) + sequencerCL := l2.L2CLNode(match.Assume(t, match.WithSequencerActive(t.Ctx()))) + sequencerEL := l2.L2ELNode(match.Assume(t, match.EngineFor(sequencerCL))) + var challengerCfg *challengerConfig.Config + if len(l2.L2Challengers()) > 0 { + challengerCfg = l2.L2Challengers()[0].Config() } - return &SimpleFlashblocks{ - Minimal: minimalPreset, - ConductorSets: conductorSets, - FlashblocksBuilderSets: flashblocksBuilderSets, - FlashblocksWebsocketProxies: flashblocksWebsocketProxies, - Faucets: faucets, - Funders: funders, - L2ELNodes: l2ELNodes, + + out := &SingleChainWithFlashblocks{ + L2OPRBuilder: dsl.NewOPRBuilderNode(l2.OPRBuilderNode(match.Assume(t, match.FirstOPRBuilderNode)), orch.ControlPlane()), + L2RollupBoost: dsl.NewRollupBoostNode(l2.RollupBoostNode(match.Assume(t, match.FirstRollupBoostNode)), orch.ControlPlane()), + Minimal: &Minimal{ + Log: t.Logger(), + T: t, + ControlPlane: orch.ControlPlane(), + system: system, + L1Network: dsl.NewL1Network(system.L1Network(match.FirstL1Network)), + L1EL: dsl.NewL1ELNode(l1Net.L1ELNode(match.Assume(t, match.FirstL1EL))), + L2Chain: dsl.NewL2Network(l2, orch.ControlPlane()), + L2Batcher: dsl.NewL2Batcher(l2.L2Batcher(match.Assume(t, match.FirstL2Batcher))), + L2EL: dsl.NewL2ELNode(sequencerEL, orch.ControlPlane()), + L2CL: dsl.NewL2CLNode(sequencerCL, orch.ControlPlane()), + Wallet: dsl.NewRandomHDWallet(t, 30), // Random for test isolation + FaucetL2: dsl.NewFaucet(l2.Faucet(match.Assume(t, match.FirstFaucet))), + challengerConfig: challengerCfg, + }, + TestSequencer: dsl.NewTestSequencer(system.TestSequencer(match.Assume(t, match.FirstTestSequencer))), } + out.FaucetL1 = dsl.NewFaucet(out.L1Network.Escape().Faucet(match.Assume(t, match.FirstFaucet))) + out.FunderL1 = dsl.NewFunder(out.Wallet, out.FaucetL1, out.L1EL) + out.FunderL2 = dsl.NewFunder(out.Wallet, out.FaucetL2, out.L2EL) + return out } diff --git a/op-devstack/presets/minimal_external_el.go b/op-devstack/presets/minimal_external_el.go index 7b0bfd709c2..797d384fc89 100644 --- a/op-devstack/presets/minimal_external_el.go +++ b/op-devstack/presets/minimal_external_el.go @@ -5,7 +5,9 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/shim" "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/stack/match" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" ) @@ -34,3 +36,27 @@ func (m *MinimalExternalEL) L2Networks() []*dsl.L2Network { func WithExternalELWithSuperchainRegistry(networkPreset stack.ExtNetworkConfig) stack.CommonOption { return stack.MakeCommon(sysgo.ExternalELSystemWithEndpointAndSuperchainRegistry(&sysgo.DefaultMinimalExternalELSystemIDs{}, networkPreset)) } + +func NewMinimalExternalEL(t devtest.T) *MinimalExternalEL { + orch := Orchestrator() + system := shim.NewSystem(t) + orch.Hydrate(system) + + l2 := system.L2Network(match.L2ChainA) + verifierCL := l2.L2CLNode(match.FirstL2CL) + syncTester := l2.SyncTester(match.FirstSyncTester) + + sys := &MinimalExternalEL{ + Log: t.Logger(), + T: t, + ControlPlane: orch.ControlPlane(), + L1Network: dsl.NewL1Network(system.L1Network(match.FirstL1Network)), + L1EL: dsl.NewL1ELNode(system.L1Network(match.FirstL1Network).L1ELNode(match.FirstL1EL)), + L2Chain: dsl.NewL2Network(l2, orch.ControlPlane()), + L2CL: dsl.NewL2CLNode(verifierCL, orch.ControlPlane()), + L2ELReadOnly: dsl.NewL2ELNode(l2.L2ELNode(match.FirstL2EL), orch.ControlPlane()), + L2EL: dsl.NewL2ELNode(l2.L2ELNode(match.SecondL2EL), orch.ControlPlane()), + SyncTester: dsl.NewSyncTester(syncTester), + } + return sys +} diff --git a/op-devstack/presets/proof.go b/op-devstack/presets/proof.go index ae258ef0410..c40c855e022 100644 --- a/op-devstack/presets/proof.go +++ b/op-devstack/presets/proof.go @@ -1,7 +1,7 @@ package presets import ( - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-devstack/dsl/contract" "github.com/ethereum-optimism/optimism/op-devstack/stack" "github.com/ethereum-optimism/optimism/op-devstack/stack/match" @@ -11,7 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" ) -func WithRespectedGameType(gameType faultTypes.GameType) stack.CommonOption { +func WithRespectedGameType(gameType gameTypes.GameType) stack.CommonOption { opts := WithProposerGameType(gameType) opts = stack.Combine(opts, stack.MakeCommon(sysgo.WithRespectedGameType(gameType)), // Set if sysgo is in use @@ -20,13 +20,13 @@ func WithRespectedGameType(gameType faultTypes.GameType) stack.CommonOption { return opts } -func WithAddedGameType(gameType faultTypes.GameType) stack.CommonOption { +func WithAddedGameType(gameType gameTypes.GameType) stack.CommonOption { opts := stack.Combine( stack.MakeCommon(sysgo.WithGameTypeAdded(gameType)), // Add if sysgo is in use RequireGameTypePresent(gameType), // Verify present for other chains ) - if gameType == faultTypes.CannonKonaGameType { + if gameType == gameTypes.CannonKonaGameType { opts = stack.Combine( opts, WithCannonKonaFeatureEnabled(), @@ -36,7 +36,7 @@ func WithAddedGameType(gameType faultTypes.GameType) stack.CommonOption { return opts } -func RequireGameTypePresent(gameType faultTypes.GameType) stack.CommonOption { +func RequireGameTypePresent(gameType gameTypes.GameType) stack.CommonOption { return stack.FnOption[stack.Orchestrator]{ PostHydrateFn: func(sys stack.System) { elNode := sys.L1Network(match.FirstL1Network).L1ELNode(match.FirstL1EL) @@ -53,7 +53,7 @@ func RequireGameTypePresent(gameType faultTypes.GameType) stack.CommonOption { } } -func RequireRespectedGameType(gameType faultTypes.GameType) stack.CommonOption { +func RequireRespectedGameType(gameType gameTypes.GameType) stack.CommonOption { return stack.FnOption[stack.Orchestrator]{ PostHydrateFn: func(sys stack.System) { @@ -73,7 +73,7 @@ func RequireRespectedGameType(gameType faultTypes.GameType) stack.CommonOption { } } -func WithProposerGameType(gameType faultTypes.GameType) stack.CommonOption { +func WithProposerGameType(gameType gameTypes.GameType) stack.CommonOption { return stack.Combine( stack.MakeCommon( sysgo.WithProposerOption(func(id stack.L2ProposerID, cfg *ps.CLIConfig) { diff --git a/op-devstack/presets/singlechain_twoverifiers.go b/op-devstack/presets/singlechain_twoverifiers.go new file mode 100644 index 00000000000..76af5ef8bb7 --- /dev/null +++ b/op-devstack/presets/singlechain_twoverifiers.go @@ -0,0 +1,46 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/shim" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/stack/match" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +type SingleChainTwoVerifiers struct { + SingleChainMultiNode + + L2ELC *dsl.L2ELNode + L2CLC *dsl.L2CLNode +} + +func WithSingleChainTwoVerifiers() stack.CommonOption { + return stack.MakeCommon(sysgo.DefaultSingleChainTwoVerifiersSystem(&sysgo.DefaultSingleChainTwoVerifiersSystemIDs{})) +} + +func NewSingleChainTwoVerifiersWithoutCheck(t devtest.T) *SingleChainTwoVerifiers { + system := shim.NewSystem(t) + orch := Orchestrator() + orch.Hydrate(system) + singleChainMultiNode := NewSingleChainMultiNodeWithoutCheck(t) + l2 := system.L2Network(match.Assume(t, match.L2ChainA)) + verifierCL := l2.L2CLNode(match.Assume(t, + match.And( + match.Not(match.WithSequencerActive(t.Ctx())), + match.Not[stack.L2CLNodeID, stack.L2CLNode](singleChainMultiNode.L2CL.ID()), + match.Not[stack.L2CLNodeID, stack.L2CLNode](singleChainMultiNode.L2CLB.ID()), + ))) + verifierEL := l2.L2ELNode(match.Assume(t, + match.And( + match.Not[stack.L2ELNodeID, stack.L2ELNode](singleChainMultiNode.L2EL.ID()), + match.Not[stack.L2ELNodeID, stack.L2ELNode](singleChainMultiNode.L2ELB.ID()), + ))) + preset := &SingleChainTwoVerifiers{ + SingleChainMultiNode: *singleChainMultiNode, + L2ELC: dsl.NewL2ELNode(verifierEL, orch.ControlPlane()), + L2CLC: dsl.NewL2CLNode(verifierCL, orch.ControlPlane()), + } + return preset +} diff --git a/op-devstack/shared/challenger/challenger.go b/op-devstack/shared/challenger/challenger.go index 806f31ac881..2b0fa928834 100644 --- a/op-devstack/shared/challenger/challenger.go +++ b/op-devstack/shared/challenger/challenger.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" @@ -153,44 +153,44 @@ func WithCannonKonaConfig(rollupCfgs []*rollup.Config, l1Genesis *core.Genesis, } } -func WithCannonTraceType() Option { +func WithCannonGameType() Option { return func(c *config.Config) error { - c.TraceTypes = append(c.TraceTypes, types.TraceTypeCannon) + c.GameTypes = append(c.GameTypes, gameTypes.CannonGameType) return nil } } -func WithCannonKonaTraceType() Option { +func WithCannonKonaGameType() Option { return func(c *config.Config) error { - c.TraceTypes = append(c.TraceTypes, types.TraceTypeCannonKona) + c.GameTypes = append(c.GameTypes, gameTypes.CannonKonaGameType) return nil } } -func WithPermissionedTraceType() Option { +func WithPermissionedGameType() Option { return func(c *config.Config) error { - c.TraceTypes = append(c.TraceTypes, types.TraceTypePermissioned) + c.GameTypes = append(c.GameTypes, gameTypes.PermissionedGameType) return nil } } -func WithSuperCannonTraceType() Option { +func WithSuperCannonGameType() Option { return func(c *config.Config) error { - c.TraceTypes = append(c.TraceTypes, types.TraceTypeSuperCannon) + c.GameTypes = append(c.GameTypes, gameTypes.SuperCannonGameType) return nil } } -func WithSuperPermissionedTraceType() Option { +func WithSuperPermissionedGameType() Option { return func(c *config.Config) error { - c.TraceTypes = append(c.TraceTypes, types.TraceTypeSuperPermissioned) + c.GameTypes = append(c.GameTypes, gameTypes.SuperPermissionedGameType) return nil } } func WithFastGames() Option { return func(c *config.Config) error { - c.TraceTypes = append(c.TraceTypes, types.TraceTypeFast) + c.GameTypes = append(c.GameTypes, gameTypes.FastGameType) return nil } } diff --git a/op-devstack/shim/fb_builder.go b/op-devstack/shim/fb_builder.go deleted file mode 100644 index 960747612e3..00000000000 --- a/op-devstack/shim/fb_builder.go +++ /dev/null @@ -1,68 +0,0 @@ -package shim - -import ( - "net/http" - - "github.com/stretchr/testify/require" - - "github.com/ethereum-optimism/optimism/op-devstack/stack" - "github.com/ethereum-optimism/optimism/op-service/apis" - "github.com/ethereum-optimism/optimism/op-service/sources" -) - -type FlashblocksBuilderNodeConfig struct { - ELNodeConfig - ID stack.FlashblocksBuilderID - Conductor stack.Conductor - FlashblocksWsUrl string - FlashblocksWsHeaders http.Header -} - -type flashblocksBuilderNode struct { - rpcELNode - l2Client *sources.L2Client - - id stack.FlashblocksBuilderID - conductor stack.Conductor - - flashblocksWsUrl string - flashblocksWsHeaders http.Header -} - -var _ stack.FlashblocksBuilderNode = (*flashblocksBuilderNode)(nil) - -func NewFlashblocksBuilderNode(cfg FlashblocksBuilderNodeConfig) stack.FlashblocksBuilderNode { - require.Equal(cfg.T, cfg.ID.ChainID(), cfg.ELNodeConfig.ChainID, "chainID must be configured to match node chainID") - cfg.T = cfg.T.WithCtx(stack.ContextWithID(cfg.T.Ctx(), cfg.ID)) - l2Client, err := sources.NewL2Client(cfg.ELNodeConfig.Client, cfg.T.Logger(), nil, sources.L2ClientSimpleConfig(nil, false, 10, 10)) - require.NoError(cfg.T, err) - - return &flashblocksBuilderNode{ - rpcELNode: newRpcELNode(cfg.ELNodeConfig), - l2Client: l2Client, - id: cfg.ID, - conductor: cfg.Conductor, - flashblocksWsUrl: cfg.FlashblocksWsUrl, - flashblocksWsHeaders: cfg.FlashblocksWsHeaders, - } -} - -func (r *flashblocksBuilderNode) ID() stack.FlashblocksBuilderID { - return r.id -} - -func (r *flashblocksBuilderNode) Conductor() stack.Conductor { - return r.conductor -} - -func (r *flashblocksBuilderNode) L2EthClient() apis.L2EthClient { - return r.l2Client -} - -func (r *flashblocksBuilderNode) FlashblocksWsUrl() string { - return r.flashblocksWsUrl -} - -func (r *flashblocksBuilderNode) FlashblocksWsHeaders() http.Header { - return r.flashblocksWsHeaders -} diff --git a/op-devstack/shim/fb_ws_client.go b/op-devstack/shim/fb_ws_client.go new file mode 100644 index 00000000000..b9275497a6e --- /dev/null +++ b/op-devstack/shim/fb_ws_client.go @@ -0,0 +1,50 @@ +package shim + +import ( + "net/http" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type FlashblocksWSClientConfig struct { + CommonConfig + ID stack.FlashblocksWSClientID + WsUrl string + WsHeaders http.Header +} + +type flashblocksWSClient struct { + commonImpl + id stack.FlashblocksWSClientID + wsUrl string + wsHeaders http.Header +} + +var _ stack.FlashblocksWSClient = (*flashblocksWSClient)(nil) + +func NewFlashblocksWSClient(cfg FlashblocksWSClientConfig) stack.FlashblocksWSClient { + cfg.T = cfg.T.WithCtx(stack.ContextWithID(cfg.T.Ctx(), cfg.ID)) + return &flashblocksWSClient{ + commonImpl: newCommon(cfg.CommonConfig), + id: cfg.ID, + wsUrl: cfg.WsUrl, + wsHeaders: cfg.WsHeaders, + } +} + +func (r *flashblocksWSClient) ID() stack.FlashblocksWSClientID { + return r.id +} + +func (r *flashblocksWSClient) ChainID() eth.ChainID { + return r.id.ChainID() +} + +func (r *flashblocksWSClient) WsUrl() string { + return r.wsUrl +} + +func (r *flashblocksWSClient) WsHeaders() http.Header { + return r.wsHeaders +} diff --git a/op-devstack/shim/fb_ws_proxy.go b/op-devstack/shim/fb_ws_proxy.go deleted file mode 100644 index b01d10441ef..00000000000 --- a/op-devstack/shim/fb_ws_proxy.go +++ /dev/null @@ -1,50 +0,0 @@ -package shim - -import ( - "net/http" - - "github.com/ethereum-optimism/optimism/op-devstack/stack" - "github.com/ethereum-optimism/optimism/op-service/eth" -) - -type FlashblocksWebsocketProxyConfig struct { - CommonConfig - ID stack.FlashblocksWebsocketProxyID - WsUrl string - WsHeaders http.Header -} - -type flashblocksWebsocketProxy struct { - commonImpl - id stack.FlashblocksWebsocketProxyID - wsUrl string - wsHeaders http.Header -} - -var _ stack.FlashblocksWebsocketProxy = (*flashblocksWebsocketProxy)(nil) - -func NewFlashblocksWebsocketProxy(cfg FlashblocksWebsocketProxyConfig) stack.FlashblocksWebsocketProxy { - cfg.T = cfg.T.WithCtx(stack.ContextWithID(cfg.T.Ctx(), cfg.ID)) - return &flashblocksWebsocketProxy{ - commonImpl: newCommon(cfg.CommonConfig), - id: cfg.ID, - wsUrl: cfg.WsUrl, - wsHeaders: cfg.WsHeaders, - } -} - -func (r *flashblocksWebsocketProxy) ID() stack.FlashblocksWebsocketProxyID { - return r.id -} - -func (r *flashblocksWebsocketProxy) ChainID() eth.ChainID { - return r.id.ChainID() -} - -func (r *flashblocksWebsocketProxy) WsUrl() string { - return r.wsUrl -} - -func (r *flashblocksWebsocketProxy) WsHeaders() http.Header { - return r.wsHeaders -} diff --git a/op-devstack/shim/l2_cl.go b/op-devstack/shim/l2_cl.go index bcf02f78355..1421c54f229 100644 --- a/op-devstack/shim/l2_cl.go +++ b/op-devstack/shim/l2_cl.go @@ -22,11 +22,13 @@ type L2CLNodeConfig struct { type rpcL2CLNode struct { commonImpl - id stack.L2CLNodeID - client client.RPC - rollupClient apis.RollupClient - p2pClient apis.P2PClient - els locks.RWMap[stack.L2ELNodeID, stack.L2ELNode] + id stack.L2CLNodeID + client client.RPC + rollupClient apis.RollupClient + p2pClient apis.P2PClient + els locks.RWMap[stack.L2ELNodeID, stack.L2ELNode] + rollupBoostNodes locks.RWMap[stack.RollupBoostNodeID, stack.RollupBoostNode] + oprbuilderNodes locks.RWMap[stack.OPRBuilderNodeID, stack.OPRBuilderNode] userRPC string @@ -74,10 +76,38 @@ func (r *rpcL2CLNode) LinkEL(el stack.L2ELNode) { r.els.Set(el.ID(), el) } +func (r *rpcL2CLNode) LinkRollupBoostNode(rollupBoostNode stack.RollupBoostNode) { + r.rollupBoostNodes.Set(rollupBoostNode.ID(), rollupBoostNode) +} + +func (r *rpcL2CLNode) LinkOPRBuilderNode(oprb stack.OPRBuilderNode) { + r.oprbuilderNodes.Set(oprb.ID(), oprb) +} + func (r *rpcL2CLNode) ELs() []stack.L2ELNode { return stack.SortL2ELNodes(r.els.Values()) } +func (r *rpcL2CLNode) ELClient() apis.EthClient { + var ethclient apis.EthClient + if len(r.els.Values()) > 0 { + ethclient = r.els.Values()[0].EthClient() + } else if len(r.rollupBoostNodes.Values()) > 0 { + ethclient = r.rollupBoostNodes.Values()[0].EthClient() + } else if len(r.oprbuilderNodes.Values()) > 0 { + ethclient = r.oprbuilderNodes.Values()[0].EthClient() + } + return ethclient +} + +func (r *rpcL2CLNode) RollupBoostNodes() []stack.RollupBoostNode { + return stack.SortRollupBoostNodes(r.rollupBoostNodes.Values()) +} + +func (r *rpcL2CLNode) OPRBuilderNodes() []stack.OPRBuilderNode { + return stack.SortOPRBuilderNodes(r.oprbuilderNodes.Values()) +} + func (r *rpcL2CLNode) UserRPC() string { return r.userRPC } diff --git a/op-devstack/shim/l2_network.go b/op-devstack/shim/l2_network.go index 58ce9462161..914c35aac63 100644 --- a/op-devstack/shim/l2_network.go +++ b/op-devstack/shim/l2_network.go @@ -40,9 +40,9 @@ type presetL2Network struct { els locks.RWMap[stack.L2ELNodeID, stack.L2ELNode] cls locks.RWMap[stack.L2CLNodeID, stack.L2CLNode] - conductors locks.RWMap[stack.ConductorID, stack.Conductor] - fbBuilders locks.RWMap[stack.FlashblocksBuilderID, stack.FlashblocksBuilderNode] - fbWsProxies locks.RWMap[stack.FlashblocksWebsocketProxyID, stack.FlashblocksWebsocketProxy] + conductors locks.RWMap[stack.ConductorID, stack.Conductor] + rollupBoostNodes locks.RWMap[stack.RollupBoostNodeID, stack.RollupBoostNode] + oprBuilderNodes locks.RWMap[stack.OPRBuilderNodeID, stack.OPRBuilderNode] } var _ stack.L2Network = (*presetL2Network)(nil) @@ -122,17 +122,6 @@ func (p *presetL2Network) AddConductor(v stack.Conductor) { p.require().True(p.conductors.SetIfMissing(id, v), "conductor %s must not already exist", id) } -func (p *presetL2Network) FlashblocksBuilder(m stack.FlashblocksBuilderMatcher) stack.FlashblocksBuilderNode { - v, ok := findMatch(m, p.fbBuilders.Get, p.FlashblocksBuilders) - p.require().True(ok, "must find flashblocks builder %s", m) - return v -} - -func (p *presetL2Network) AddFlashblocksBuilder(v stack.FlashblocksBuilderNode) { - id := v.ID() - p.require().True(p.fbBuilders.SetIfMissing(id, v), "flashblocks builder %s must not already exist", id) -} - func (p *presetL2Network) L2Proposer(m stack.L2ProposerMatcher) stack.L2Proposer { v, ok := findMatch(m, p.proposers.Get, p.L2Proposers) p.require().True(ok, "must find L2 proposer %s", m) @@ -145,12 +134,6 @@ func (p *presetL2Network) AddL2Proposer(v stack.L2Proposer) { p.require().True(p.proposers.SetIfMissing(id, v), "l2 proposer %s must not already exist", id) } -func (p *presetL2Network) AddFlashblocksWebsocketProxy(v stack.FlashblocksWebsocketProxy) { - id := v.ID() - p.require().Equal(p.chainID, id.ChainID(), "flashblocks websocket proxy %s must be on chain %s", id, p.chainID) - p.require().True(p.fbWsProxies.SetIfMissing(id, v), "flashblocks websocket proxy %s must not already exist", id) -} - func (p *presetL2Network) L2Challenger(m stack.L2ChallengerMatcher) stack.L2Challenger { v, ok := findMatch(m, p.challengers.Get, p.L2Challengers) p.require().True(ok, "must find L2 challenger %s", m) @@ -203,14 +186,6 @@ func (p *presetL2Network) L2Proposers() []stack.L2Proposer { return stack.SortL2Proposers(p.proposers.Values()) } -func (p *presetL2Network) FlashblocksWebsocketProxies() []stack.FlashblocksWebsocketProxy { - return stack.SortFlashblocksWebsocketProxies(p.fbWsProxies.Values()) -} - -func (p *presetL2Network) FlashblocksWebsocketProxyIDs() []stack.FlashblocksWebsocketProxyID { - return stack.SortFlashblocksWebsocketProxyIDs(p.fbWsProxies.Keys()) -} - func (p *presetL2Network) L2ChallengerIDs() []stack.L2ChallengerID { return stack.SortL2ChallengerIDs(p.challengers.Keys()) } @@ -223,10 +198,6 @@ func (p *presetL2Network) Conductors() []stack.Conductor { return stack.SortConductors(p.conductors.Values()) } -func (p *presetL2Network) FlashblocksBuilders() []stack.FlashblocksBuilderNode { - return stack.SortFlashblocksBuilders(p.fbBuilders.Values()) -} - func (p *presetL2Network) L2CLNodeIDs() []stack.L2CLNodeID { return stack.SortL2CLNodeIDs(p.cls.Keys()) } @@ -242,3 +213,33 @@ func (p *presetL2Network) L2ELNodeIDs() []stack.L2ELNodeID { func (p *presetL2Network) L2ELNodes() []stack.L2ELNode { return stack.SortL2ELNodes(p.els.Values()) } + +func (p *presetL2Network) RollupBoostNodes() []stack.RollupBoostNode { + return stack.SortRollupBoostNodes(p.rollupBoostNodes.Values()) +} + +func (p *presetL2Network) OPRBuilderNodes() []stack.OPRBuilderNode { + return stack.SortOPRBuilderNodes(p.oprBuilderNodes.Values()) +} + +func (p *presetL2Network) AddRollupBoostNode(v stack.RollupBoostNode) { + id := v.ID() + p.require().True(p.rollupBoostNodes.SetIfMissing(id, v), "rollup boost node %s must not already exist", id) +} + +func (p *presetL2Network) AddOPRBuilderNode(v stack.OPRBuilderNode) { + id := v.ID() + p.require().True(p.oprBuilderNodes.SetIfMissing(id, v), "OPR builder node %s must not already exist", id) +} + +func (p *presetL2Network) OPRBuilderNode(m stack.OPRBuilderNodeMatcher) stack.OPRBuilderNode { + v, ok := findMatch(m, p.oprBuilderNodes.Get, p.OPRBuilderNodes) + p.require().True(ok, "must find OPR builder node %s", m) + return v +} + +func (p *presetL2Network) RollupBoostNode(m stack.RollupBoostNodeMatcher) stack.RollupBoostNode { + v, ok := findMatch(m, p.rollupBoostNodes.Get, p.RollupBoostNodes) + p.require().True(ok, "must find rollup boost node %s", m) + return v +} diff --git a/op-devstack/shim/op_rbuilder.go b/op-devstack/shim/op_rbuilder.go new file mode 100644 index 00000000000..8e931142136 --- /dev/null +++ b/op-devstack/shim/op_rbuilder.go @@ -0,0 +1,57 @@ +package shim + +import ( + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/sources" +) + +type OPRBuilderNodeConfig struct { + ELNodeConfig + RollupCfg *rollup.Config + ID stack.OPRBuilderNodeID + FlashblocksWsClient stack.FlashblocksWSClient +} + +type OPRBuilderNode struct { + rpcELNode + id stack.OPRBuilderNodeID + engineClient *sources.EngineClient + flashblocksWsClient stack.FlashblocksWSClient +} + +var _ stack.OPRBuilderNode = (*OPRBuilderNode)(nil) + +func NewOPRBuilderNode(cfg OPRBuilderNodeConfig) *OPRBuilderNode { + require.Equal(cfg.T, cfg.ID.ChainID(), cfg.ELNodeConfig.ChainID, "chainID must be configured to match node chainID") + cfg.T = cfg.T.WithCtx(stack.ContextWithID(cfg.T.Ctx(), cfg.ID)) + l2EngineClient, err := sources.NewEngineClient(cfg.ELNodeConfig.Client, cfg.T.Logger(), nil, sources.EngineClientDefaultConfig(cfg.RollupCfg)) + + require.NoError(cfg.T, err) + + return &OPRBuilderNode{ + rpcELNode: newRpcELNode(cfg.ELNodeConfig), + engineClient: l2EngineClient, + id: cfg.ID, + flashblocksWsClient: cfg.FlashblocksWsClient, + } +} + +func (r *OPRBuilderNode) ID() stack.OPRBuilderNodeID { + return r.id +} + +func (r *OPRBuilderNode) L2EthClient() apis.L2EthClient { + return r.engineClient.L2Client +} + +func (r *OPRBuilderNode) FlashblocksClient() stack.FlashblocksWSClient { + return r.flashblocksWsClient +} + +func (r *OPRBuilderNode) L2EngineClient() apis.EngineClient { + return r.engineClient.EngineAPIClient +} diff --git a/op-devstack/shim/rollup_boost.go b/op-devstack/shim/rollup_boost.go new file mode 100644 index 00000000000..6b2024f4f13 --- /dev/null +++ b/op-devstack/shim/rollup_boost.go @@ -0,0 +1,62 @@ +package shim + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/stretchr/testify/require" +) + +type RollupBoostNodeConfig struct { + ELNodeConfig + RollupCfg *rollup.Config + ID stack.RollupBoostNodeID + FlashblocksWsClient stack.FlashblocksWSClient +} + +type RollupBoostNode struct { + rpcELNode + engineClient *sources.EngineClient + + id stack.RollupBoostNodeID + + flashblocksWsClient stack.FlashblocksWSClient +} + +var _ stack.RollupBoostNode = (*RollupBoostNode)(nil) + +func NewRollupBoostNode(cfg RollupBoostNodeConfig) *RollupBoostNode { + require.Equal(cfg.T, cfg.ID.ChainID(), cfg.ELNodeConfig.ChainID, "chainID must be configured to match node chainID") + cfg.T = cfg.T.WithCtx(stack.ContextWithID(cfg.T.Ctx(), cfg.ID)) + l2EngineClient, err := sources.NewEngineClient(cfg.ELNodeConfig.Client, cfg.T.Logger(), nil, sources.EngineClientDefaultConfig(cfg.RollupCfg)) + + require.NoError(cfg.T, err) + + return &RollupBoostNode{ + rpcELNode: newRpcELNode(cfg.ELNodeConfig), + engineClient: l2EngineClient, + id: cfg.ID, + flashblocksWsClient: cfg.FlashblocksWsClient, + } +} + +func (r *RollupBoostNode) ID() stack.RollupBoostNodeID { + return r.id +} + +func (r *RollupBoostNode) L2EthClient() apis.L2EthClient { + return r.engineClient.L2Client +} + +func (r *RollupBoostNode) FlashblocksClient() stack.FlashblocksWSClient { + return r.flashblocksWsClient +} + +func (r *RollupBoostNode) L2EngineClient() apis.EngineClient { + return r.engineClient.EngineAPIClient +} + +func (r *RollupBoostNode) ELNode() stack.ELNode { + return &r.rpcELNode +} diff --git a/op-devstack/stack/fb_builder.go b/op-devstack/stack/fb_builder.go deleted file mode 100644 index 1a67f5b1517..00000000000 --- a/op-devstack/stack/fb_builder.go +++ /dev/null @@ -1,67 +0,0 @@ -package stack - -import ( - "log/slog" - "net/http" - - "github.com/ethereum-optimism/optimism/op-service/apis" - "github.com/ethereum-optimism/optimism/op-service/eth" -) - -type FlashblocksBuilderNode interface { - ELNode - ID() FlashblocksBuilderID - Conductor() Conductor - L2EthClient() apis.L2EthClient - FlashblocksWsUrl() string - FlashblocksWsHeaders() http.Header -} - -type FlashblocksBuilderID idWithChain - -const FlashblocksBuilderKind Kind = "FlashblocksBuilder" - -func NewFlashblocksBuilderID(key string, chainID eth.ChainID) FlashblocksBuilderID { - return FlashblocksBuilderID{ - key: key, - chainID: chainID, - } -} - -func (id FlashblocksBuilderID) String() string { - return idWithChain(id).string(FlashblocksBuilderKind) -} - -func (id FlashblocksBuilderID) ChainID() eth.ChainID { - return idWithChain(id).chainID -} - -func (id FlashblocksBuilderID) MarshalText() ([]byte, error) { - return idWithChain(id).marshalText(FlashblocksBuilderKind) -} - -func (id FlashblocksBuilderID) LogValue() slog.Value { - return slog.StringValue(id.String()) -} - -func (id *FlashblocksBuilderID) UnmarshalText(data []byte) error { - return (*idWithChain)(id).unmarshalText(FlashblocksBuilderKind, data) -} - -func SortFlashblocksBuilderIDs(ids []FlashblocksBuilderID) []FlashblocksBuilderID { - return copyAndSort(ids, func(a, b FlashblocksBuilderID) bool { - return lessIDWithChain(idWithChain(a), idWithChain(b)) - }) -} - -func SortFlashblocksBuilders(elems []FlashblocksBuilderNode) []FlashblocksBuilderNode { - return copyAndSort(elems, func(a, b FlashblocksBuilderNode) bool { - return lessIDWithChain(idWithChain(a.ID()), idWithChain(b.ID())) - }) -} - -var _ FlashblocksBuilderMatcher = FlashblocksBuilderID{} - -func (id FlashblocksBuilderID) Match(elems []FlashblocksBuilderNode) []FlashblocksBuilderNode { - return findByID(id, elems) -} diff --git a/op-devstack/stack/fb_ws_client.go b/op-devstack/stack/fb_ws_client.go new file mode 100644 index 00000000000..9da423dcbf3 --- /dev/null +++ b/op-devstack/stack/fb_ws_client.go @@ -0,0 +1,63 @@ +package stack + +import ( + "log/slog" + "net/http" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type FlashblocksWSClient interface { + Common + ChainID() eth.ChainID + ID() FlashblocksWSClientID + WsUrl() string + WsHeaders() http.Header +} + +type FlashblocksWSClientID idWithChain + +const FlashblocksWSClientKind Kind = "FlashblocksWSClient" + +func NewFlashblocksWSClientID(key string, chainID eth.ChainID) FlashblocksWSClientID { + return FlashblocksWSClientID{ + key: key, + chainID: chainID, + } +} + +func (id FlashblocksWSClientID) String() string { + return idWithChain(id).string(FlashblocksWSClientKind) +} + +func (id FlashblocksWSClientID) ChainID() eth.ChainID { + return idWithChain(id).chainID +} + +func (id FlashblocksWSClientID) MarshalText() ([]byte, error) { + return idWithChain(id).marshalText(FlashblocksWSClientKind) +} + +func (id FlashblocksWSClientID) LogValue() slog.Value { + return slog.StringValue(id.String()) +} + +func (id *FlashblocksWSClientID) UnmarshalText(data []byte) error { + return (*idWithChain)(id).unmarshalText(FlashblocksWSClientKind, data) +} + +func SortFlashblocksWSClientIDs(ids []FlashblocksWSClientID) []FlashblocksWSClientID { + return copyAndSort(ids, func(a, b FlashblocksWSClientID) bool { + return lessIDWithChain(idWithChain(a), idWithChain(b)) + }) +} + +func SortFlashblocksWSClients(elems []FlashblocksWSClient) []FlashblocksWSClient { + return copyAndSort(elems, func(a, b FlashblocksWSClient) bool { + return lessIDWithChain(idWithChain(a.ID()), idWithChain(b.ID())) + }) +} + +func (id FlashblocksWSClientID) Match(elems []FlashblocksWSClient) []FlashblocksWSClient { + return findByID(id, elems) +} diff --git a/op-devstack/stack/fb_ws_proxy.go b/op-devstack/stack/fb_ws_proxy.go deleted file mode 100644 index 4b0f1167bfa..00000000000 --- a/op-devstack/stack/fb_ws_proxy.go +++ /dev/null @@ -1,65 +0,0 @@ -package stack - -import ( - "log/slog" - "net/http" - - "github.com/ethereum-optimism/optimism/op-service/eth" -) - -type FlashblocksWebsocketProxy interface { - Common - ChainID() eth.ChainID - ID() FlashblocksWebsocketProxyID - WsUrl() string - WsHeaders() http.Header -} - -type FlashblocksWebsocketProxyID idWithChain - -const FlashblocksWebsocketProxyKind Kind = "FlashblocksWebsocketProxy" - -func NewFlashblocksWebsocketProxyID(key string, chainID eth.ChainID) FlashblocksWebsocketProxyID { - return FlashblocksWebsocketProxyID{ - key: key, - chainID: chainID, - } -} - -func (id FlashblocksWebsocketProxyID) String() string { - return idWithChain(id).string(FlashblocksWebsocketProxyKind) -} - -func (id FlashblocksWebsocketProxyID) ChainID() eth.ChainID { - return idWithChain(id).chainID -} - -func (id FlashblocksWebsocketProxyID) MarshalText() ([]byte, error) { - return idWithChain(id).marshalText(FlashblocksWebsocketProxyKind) -} - -func (id FlashblocksWebsocketProxyID) LogValue() slog.Value { - return slog.StringValue(id.String()) -} - -func (id *FlashblocksWebsocketProxyID) UnmarshalText(data []byte) error { - return (*idWithChain)(id).unmarshalText(FlashblocksWebsocketProxyKind, data) -} - -func SortFlashblocksWebsocketProxyIDs(ids []FlashblocksWebsocketProxyID) []FlashblocksWebsocketProxyID { - return copyAndSort(ids, func(a, b FlashblocksWebsocketProxyID) bool { - return lessIDWithChain(idWithChain(a), idWithChain(b)) - }) -} - -func SortFlashblocksWebsocketProxies(elems []FlashblocksWebsocketProxy) []FlashblocksWebsocketProxy { - return copyAndSort(elems, func(a, b FlashblocksWebsocketProxy) bool { - return lessIDWithChain(idWithChain(a.ID()), idWithChain(b.ID())) - }) -} - -var _ FlashblocksBuilderMatcher = FlashblocksBuilderID{} - -func (id FlashblocksWebsocketProxyID) Match(elems []FlashblocksWebsocketProxy) []FlashblocksWebsocketProxy { - return findByID(id, elems) -} diff --git a/op-devstack/stack/l2_cl.go b/op-devstack/stack/l2_cl.go index e5e5b042b34..8005cb3d28c 100644 --- a/op-devstack/stack/l2_cl.go +++ b/op-devstack/stack/l2_cl.go @@ -82,9 +82,15 @@ type L2CLNode interface { // ELs returns the engine(s) that this L2CLNode is connected to. // This may be empty, if the L2CL is not connected to any. ELs() []L2ELNode + RollupBoostNodes() []RollupBoostNode + OPRBuilderNodes() []OPRBuilderNode + + ELClient() apis.EthClient } type LinkableL2CLNode interface { // Links the nodes. Does not make any backend changes, just registers the EL as connected to this CL. LinkEL(el L2ELNode) + LinkRollupBoostNode(rollupBoostNode RollupBoostNode) + LinkOPRBuilderNode(oprb OPRBuilderNode) } diff --git a/op-devstack/stack/l2_network.go b/op-devstack/stack/l2_network.go index 296f0412c99..d49f26750b6 100644 --- a/op-devstack/stack/l2_network.go +++ b/op-devstack/stack/l2_network.go @@ -91,6 +91,8 @@ type L2Network interface { L2CLNode(m L2CLMatcher) L2CLNode L2ELNode(m L2ELMatcher) L2ELNode Conductor(m ConductorMatcher) Conductor + RollupBoostNode(m RollupBoostNodeMatcher) RollupBoostNode + OPRBuilderNode(m OPRBuilderNodeMatcher) OPRBuilderNode L2BatcherIDs() []L2BatcherID L2ProposerIDs() []L2ProposerID @@ -104,10 +106,8 @@ type L2Network interface { L2CLNodes() []L2CLNode L2ELNodes() []L2ELNode Conductors() []Conductor - FlashblocksBuilders() []FlashblocksBuilderNode - AddFlashblocksBuilder(v FlashblocksBuilderNode) - FlashblocksWebsocketProxies() []FlashblocksWebsocketProxy - AddFlashblocksWebsocketProxy(v FlashblocksWebsocketProxy) + RollupBoostNodes() []RollupBoostNode + OPRBuilderNodes() []OPRBuilderNode } // ExtensibleL2Network is an optional extension interface for L2Network, @@ -121,4 +121,6 @@ type ExtensibleL2Network interface { AddL2CLNode(v L2CLNode) AddL2ELNode(v L2ELNode) AddConductor(v Conductor) + AddRollupBoostNode(v RollupBoostNode) + AddOPRBuilderNode(v OPRBuilderNode) } diff --git a/op-devstack/stack/match/engine.go b/op-devstack/stack/match/engine.go index c5f0f36a7c9..a9bd93413e6 100644 --- a/op-devstack/stack/match/engine.go +++ b/op-devstack/stack/match/engine.go @@ -1,6 +1,8 @@ package match -import "github.com/ethereum-optimism/optimism/op-devstack/stack" +import ( + "github.com/ethereum-optimism/optimism/op-devstack/stack" +) func WithEngine(engine stack.L2ELNodeID) stack.Matcher[stack.L2CLNodeID, stack.L2CLNode] { return MatchElemFn[stack.L2CLNodeID, stack.L2CLNode](func(elem stack.L2CLNode) bool { @@ -9,6 +11,18 @@ func WithEngine(engine stack.L2ELNodeID) stack.Matcher[stack.L2CLNodeID, stack.L return true } } + rbID := stack.RollupBoostNodeID(engine) + for _, rb := range elem.RollupBoostNodes() { + if rb.ID().ChainID() == rbID.ChainID() { + return true + } + } + oprbID := stack.OPRBuilderNodeID(engine) + for _, oprb := range elem.OPRBuilderNodes() { + if oprb.ID() == oprbID { + return true + } + } return false }) } @@ -20,6 +34,18 @@ func EngineFor(cl stack.L2CLNode) stack.Matcher[stack.L2ELNodeID, stack.L2ELNode return true } } + rbID := stack.RollupBoostNodeID(elem.ID()) + for _, rb := range cl.RollupBoostNodes() { + if rb.ID().ChainID() == rbID.ChainID() { + return true + } + } + oprbID := stack.OPRBuilderNodeID(elem.ID()) + for _, oprb := range cl.OPRBuilderNodes() { + if oprb.ID() == oprbID { + return true + } + } return false }) } diff --git a/op-devstack/stack/match/first.go b/op-devstack/stack/match/first.go index 572b49e335c..6d1a53ab6f9 100644 --- a/op-devstack/stack/match/first.go +++ b/op-devstack/stack/match/first.go @@ -21,3 +21,6 @@ var FirstCluster = First[stack.ClusterID, stack.Cluster]() var FirstFaucet = First[stack.FaucetID, stack.Faucet]() var FirstSyncTester = First[stack.SyncTesterID, stack.SyncTester]() + +var FirstOPRBuilderNode = First[stack.OPRBuilderNodeID, stack.OPRBuilderNode]() +var FirstRollupBoostNode = First[stack.RollupBoostNodeID, stack.RollupBoostNode]() diff --git a/op-devstack/stack/match/labels.go b/op-devstack/stack/match/labels.go index 9224f6c8550..be32276b337 100644 --- a/op-devstack/stack/match/labels.go +++ b/op-devstack/stack/match/labels.go @@ -20,13 +20,13 @@ const ( type Vendor string const ( - Geth Vendor = "geth" - OpReth Vendor = "op-reth" - OpGeth Vendor = "op-geth" - Proxyd Vendor = "proxyd" - FlashblocksWebsocketProxy Vendor = "flashblocks-websocket-proxy" - OpNode Vendor = "op-node" - KonaNode Vendor = "kona-node" + Geth Vendor = "geth" + OpReth Vendor = "op-reth" + OpGeth Vendor = "op-geth" + Proxyd Vendor = "proxyd" + FlashblocksWSClient Vendor = "flashblocks-websocket-proxy" + OpNode Vendor = "op-node" + KonaNode Vendor = "kona-node" ) func (v Vendor) Match(elems []stack.L2ELNode) []stack.L2ELNode { diff --git a/op-devstack/stack/matcher.go b/op-devstack/stack/matcher.go index afe97cfeff9..8053c5e80b6 100644 --- a/op-devstack/stack/matcher.go +++ b/op-devstack/stack/matcher.go @@ -55,10 +55,12 @@ type TestSequencerMatcher = Matcher[TestSequencerID, TestSequencer] type ConductorMatcher = Matcher[ConductorID, Conductor] -type FlashblocksBuilderMatcher = Matcher[FlashblocksBuilderID, FlashblocksBuilderNode] - type L2ELMatcher = Matcher[L2ELNodeID, L2ELNode] type FaucetMatcher = Matcher[FaucetID, Faucet] type SyncTesterMatcher = Matcher[SyncTesterID, SyncTester] + +type RollupBoostNodeMatcher = Matcher[RollupBoostNodeID, RollupBoostNode] + +type OPRBuilderNodeMatcher = Matcher[OPRBuilderNodeID, OPRBuilderNode] diff --git a/op-devstack/stack/op_rbuilder.go b/op-devstack/stack/op_rbuilder.go new file mode 100644 index 00000000000..7c655e32237 --- /dev/null +++ b/op-devstack/stack/op_rbuilder.go @@ -0,0 +1,78 @@ +package stack + +import ( + "log/slog" + + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// OPRBuilderNodeID identifies a L2ELNode by name and chainID, is type-safe, and can be value-copied and used as map key. +type OPRBuilderNodeID idWithChain + +var _ IDWithChain = (*OPRBuilderNodeID)(nil) + +const OPRBuilderNodeKind Kind = "OPRBuilderNode" + +func NewOPRBuilderNodeID(key string, chainID eth.ChainID) OPRBuilderNodeID { + return OPRBuilderNodeID{ + key: key, + chainID: chainID, + } +} + +func (id OPRBuilderNodeID) String() string { + return idWithChain(id).string(OPRBuilderNodeKind) +} + +func (id OPRBuilderNodeID) ChainID() eth.ChainID { + return id.chainID +} + +func (id OPRBuilderNodeID) Kind() Kind { + return OPRBuilderNodeKind +} + +func (id OPRBuilderNodeID) Key() string { + return id.key +} + +func (id OPRBuilderNodeID) LogValue() slog.Value { + return slog.StringValue(id.String()) +} + +func (id OPRBuilderNodeID) MarshalText() ([]byte, error) { + return idWithChain(id).marshalText(OPRBuilderNodeKind) +} + +func (id *OPRBuilderNodeID) UnmarshalText(data []byte) error { + return (*idWithChain)(id).unmarshalText(OPRBuilderNodeKind, data) +} + +func SortOPRBuilderIDs(ids []OPRBuilderNodeID) []OPRBuilderNodeID { + return copyAndSort(ids, func(a, b OPRBuilderNodeID) bool { + return lessIDWithChain(idWithChain(a), idWithChain(b)) + }) +} + +func SortOPRBuilderNodes(elems []OPRBuilderNode) []OPRBuilderNode { + return copyAndSort(elems, func(a, b OPRBuilderNode) bool { + return lessIDWithChain(idWithChain(a.ID()), idWithChain(b.ID())) + }) +} + +var _ OPRBuilderNodeMatcher = OPRBuilderNodeID{} + +func (id OPRBuilderNodeID) Match(elems []OPRBuilderNode) []OPRBuilderNode { + return findByID(id, elems) +} + +// L2ELNode is a L2 ethereum execution-layer node +type OPRBuilderNode interface { + ID() OPRBuilderNodeID + L2EthClient() apis.L2EthClient + L2EngineClient() apis.EngineClient + FlashblocksClient() FlashblocksWSClient + + ELNode +} diff --git a/op-devstack/stack/orchestrator.go b/op-devstack/stack/orchestrator.go index d9dab5e48ff..a1e636fe26b 100644 --- a/op-devstack/stack/orchestrator.go +++ b/op-devstack/stack/orchestrator.go @@ -24,6 +24,8 @@ type ControlPlane interface { L2CLNodeState(id L2CLNodeID, action ControlAction) L2ELNodeState(id L2ELNodeID, action ControlAction) FakePoSState(id L1CLNodeID, action ControlAction) + RollupBoostNodeState(id RollupBoostNodeID, action ControlAction) + OPRBuilderNodeState(id OPRBuilderNodeID, action ControlAction) } // Orchestrator is the base interface for all system orchestrators. diff --git a/op-devstack/stack/rollup_boost.go b/op-devstack/stack/rollup_boost.go new file mode 100644 index 00000000000..92fdd6e760f --- /dev/null +++ b/op-devstack/stack/rollup_boost.go @@ -0,0 +1,78 @@ +package stack + +import ( + "log/slog" + + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// RollupBoostNodeID identifies a RollupBoost node by name and chainID, is type-safe, and can be value-copied and used as map key. +type RollupBoostNodeID L2ELNodeID + +var _ IDWithChain = (*RollupBoostNodeID)(nil) + +const RollupBoostNodeKind Kind = "RollupBoostNode" + +func NewRollupBoostNodeID(key string, chainID eth.ChainID) RollupBoostNodeID { + return RollupBoostNodeID{ + key: key, + chainID: chainID, + } +} + +func (id RollupBoostNodeID) String() string { + return idWithChain(id).string(RollupBoostNodeKind) +} + +func (id RollupBoostNodeID) ChainID() eth.ChainID { + return id.chainID +} + +func (id RollupBoostNodeID) Kind() Kind { + return RollupBoostNodeKind +} + +func (id RollupBoostNodeID) Key() string { + return id.key +} + +func (id RollupBoostNodeID) LogValue() slog.Value { + return slog.StringValue(id.String()) +} + +func (id RollupBoostNodeID) MarshalText() ([]byte, error) { + return idWithChain(id).marshalText(RollupBoostNodeKind) +} + +func (id *RollupBoostNodeID) UnmarshalText(data []byte) error { + return (*idWithChain)(id).unmarshalText(RollupBoostNodeKind, data) +} + +func SortRollupBoostIDs(ids []RollupBoostNodeID) []RollupBoostNodeID { + return copyAndSort(ids, func(a, b RollupBoostNodeID) bool { + return lessIDWithChain(idWithChain(a), idWithChain(b)) + }) +} + +func SortRollupBoostNodes(elems []RollupBoostNode) []RollupBoostNode { + return copyAndSort(elems, func(a, b RollupBoostNode) bool { + return lessIDWithChain(idWithChain(a.ID()), idWithChain(b.ID())) + }) +} + +var _ RollupBoostNodeMatcher = RollupBoostNodeID{} + +func (id RollupBoostNodeID) Match(elems []RollupBoostNode) []RollupBoostNode { + return findByID(id, elems) +} + +// RollupBoostNode is a shim service between an L2 consensus-layer node and an L2 ethereum execution-layer node +type RollupBoostNode interface { + ID() RollupBoostNodeID + L2EthClient() apis.L2EthClient + L2EngineClient() apis.EngineClient + FlashblocksClient() FlashblocksWSClient + + ELNode +} diff --git a/op-devstack/sysext/control_plane.go b/op-devstack/sysext/control_plane.go index ecdcdd63d01..65eb37d9d52 100644 --- a/op-devstack/sysext/control_plane.go +++ b/op-devstack/sysext/control_plane.go @@ -42,4 +42,12 @@ func (c *ControlPlane) FakePoSState(id stack.L1CLNodeID, mode stack.ControlActio panic("not implemented: plug in kurtosis wrapper, or gate for the test that uses this method to not run in kurtosis") } +func (c *ControlPlane) RollupBoostNodeState(id stack.RollupBoostNodeID, mode stack.ControlAction) { + c.setLifecycleState(id.Key(), mode) +} + +func (c *ControlPlane) OPRBuilderNodeState(id stack.OPRBuilderNodeID, mode stack.ControlAction) { + c.setLifecycleState(id.Key(), mode) +} + var _ stack.ControlPlane = (*ControlPlane)(nil) diff --git a/op-devstack/sysext/helpers.go b/op-devstack/sysext/helpers.go index cd36fb192d2..c6160a43802 100644 --- a/op-devstack/sysext/helpers.go +++ b/op-devstack/sysext/helpers.go @@ -14,10 +14,11 @@ import ( ) const ( - ELServiceName = "el" - CLServiceName = "cl" - RBuilderServiceName = "rbuilder" - ConductorServiceName = "conductor" + ELServiceName = "el" + CLServiceName = "cl" + OPRBuilderServiceName = "op-rbuilder" + RollupBoostServiceName = "rollup-boost" + ConductorServiceName = "conductor" HTTPProtocol = "http" RPCProtocol = "rpc" diff --git a/op-devstack/sysext/l2.go b/op-devstack/sysext/l2.go index 5ea7c198420..05a12fb0bd7 100644 --- a/op-devstack/sysext/l2.go +++ b/op-devstack/sysext/l2.go @@ -65,13 +65,13 @@ func (o *Orchestrator) hydrateL2(net *descriptors.L2Chain, system stack.Extensib for _, node := range net.Nodes { o.hydrateL2ELCL(&node, l2, opts) o.hydrateConductors(&node, l2) - o.hydrateFlashblocksBuilderIfPresent(&node, l2, opts) + o.hydrateRollupBoostNodeMaybe(&node, l2, opts) + o.hydrateOPRBuilderMaybe(&node, l2, opts) } o.hydrateBatcherMaybe(net, l2) o.hydrateProposerMaybe(net, l2) o.hydrateChallengerMaybe(net, l2) o.hydrateL2ProxydMaybe(net, l2) - o.hydrateFlashblocksWebsocketProxyMaybe(net, l2) if faucet, ok := net.Services["faucet"]; ok { for _, instance := range faucet { @@ -193,35 +193,36 @@ func (o *Orchestrator) hydrateConductors(node *descriptors.Node, l2Net stack.Ext l2Net.AddConductor(conductor) } -func (o *Orchestrator) hydrateFlashblocksBuilderIfPresent(node *descriptors.Node, l2Net stack.ExtensibleL2Network, opts []client.RPCOption) { +func (o *Orchestrator) hydrateRollupBoostNodeMaybe(node *descriptors.Node, l2Net stack.ExtensibleL2Network, opts []client.RPCOption) { require := l2Net.T().Require() l2ID := l2Net.ID() - rbuilderService, ok := node.Services[RBuilderServiceName] + rollupBoostService, ok := node.Services[RollupBoostServiceName] if !ok { - l2Net.Logger().Debug("L2 net node is missing the flashblocksBuilder service", "node", node.Name, "l2", l2ID) + l2Net.Logger().Debug("L2 net node does not have a rollup-boost service", "node", node.Name, "l2", l2ID) return } - associatedConductorService, ok := node.Services[ConductorServiceName] - require.True(ok, "L2 rbuilder service must have an associated conductor service", l2ID) + flashblocksWsUrl, flashblocksWsHeaders, err := o.findProtocolService(rollupBoostService, WebsocketFlashblocksProtocol) + require.NoError(err, "failed to find websocket service for rollup-boost") - flashblocksWsUrl, flashblocksWsHeaders, err := o.findProtocolService(rbuilderService, WebsocketFlashblocksProtocol) - require.NoError(err, "failed to find websocket service for rbuilder") - - flashblocksBuilder := shim.NewFlashblocksBuilderNode(shim.FlashblocksBuilderNodeConfig{ - ID: stack.NewFlashblocksBuilderID(rbuilderService.Name, l2ID.ChainID()), + rollupBoost := shim.NewRollupBoostNode(shim.RollupBoostNodeConfig{ + ID: stack.NewRollupBoostNodeID(rollupBoostService.Name, l2ID.ChainID()), ELNodeConfig: shim.ELNodeConfig{ CommonConfig: shim.NewCommonConfig(l2Net.T()), - Client: o.rpcClient(l2Net.T(), rbuilderService, RPCProtocol, "/", opts...), + Client: o.rpcClient(l2Net.T(), rollupBoostService, RPCProtocol, "/", opts...), ChainID: l2ID.ChainID(), }, - Conductor: l2Net.Conductor(stack.ConductorID(associatedConductorService.Name)), - FlashblocksWsUrl: flashblocksWsUrl, - FlashblocksWsHeaders: flashblocksWsHeaders, + RollupCfg: l2Net.RollupConfig(), + FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ + CommonConfig: shim.NewCommonConfig(l2Net.T()), + ID: stack.NewFlashblocksWSClientID(rollupBoostService.Name, l2ID.ChainID()), + WsUrl: flashblocksWsUrl, + WsHeaders: flashblocksWsHeaders, + }), }) - l2Net.AddFlashblocksBuilder(flashblocksBuilder) + l2Net.AddRollupBoostNode(rollupBoost) } func (o *Orchestrator) hydrateL2ProxydMaybe(net *descriptors.L2Chain, l2Net stack.ExtensibleL2Network) { @@ -250,29 +251,35 @@ func (o *Orchestrator) hydrateL2ProxydMaybe(net *descriptors.L2Chain, l2Net stac } } -func (o *Orchestrator) hydrateFlashblocksWebsocketProxyMaybe(net *descriptors.L2Chain, l2Net stack.ExtensibleL2Network) { +func (o *Orchestrator) hydrateOPRBuilderMaybe(node *descriptors.Node, l2Net stack.ExtensibleL2Network, opts []client.RPCOption) { require := l2Net.T().Require() - l2ID := getL2ID(net) - require.Equal(l2ID, l2Net.ID(), "must match L2 chain descriptor and target L2 net") + l2ID := l2Net.ID() - fbWsProxyService, ok := net.Services["flashblocks-websocket-proxy"] + rbuilderService, ok := node.Services[OPRBuilderServiceName] if !ok { + l2Net.Logger().Debug("L2 net node does not have a oprbuilder service", "node", node.Name, "l2", l2ID) return } - for _, instance := range fbWsProxyService { - wsUrl, wsHeaders, err := o.findProtocolService(instance, WebsocketFlashblocksProtocol) - require.NoError(err, "failed to get the websocket url for the flashblocks websocket proxy", "service", instance.Name) + flashblocksWsUrl, flashblocksWsHeaders, err := o.findProtocolService(rbuilderService, WebsocketFlashblocksProtocol) + require.NoError(err, "failed to find websocket service for rbuilder") - fbWsProxyShim := shim.NewFlashblocksWebsocketProxy(shim.FlashblocksWebsocketProxyConfig{ + flashblocksBuilder := shim.NewOPRBuilderNode(shim.OPRBuilderNodeConfig{ + ID: stack.NewOPRBuilderNodeID(rbuilderService.Name, l2ID.ChainID()), + ELNodeConfig: shim.ELNodeConfig{ CommonConfig: shim.NewCommonConfig(l2Net.T()), - ID: stack.NewFlashblocksWebsocketProxyID(instance.Name, l2ID.ChainID()), - WsUrl: wsUrl, - WsHeaders: wsHeaders, - }) - fbWsProxyShim.SetLabel(match.LabelVendor, string(match.FlashblocksWebsocketProxy)) - l2Net.AddFlashblocksWebsocketProxy(fbWsProxyShim) - } + Client: o.rpcClient(l2Net.T(), rbuilderService, RPCProtocol, "/", opts...), + ChainID: l2ID.ChainID(), + }, + FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ + CommonConfig: shim.NewCommonConfig(l2Net.T()), + ID: stack.NewFlashblocksWSClientID(rbuilderService.Name, l2ID.ChainID()), + WsUrl: flashblocksWsUrl, + WsHeaders: flashblocksWsHeaders, + }), + }) + + l2Net.AddOPRBuilderNode(flashblocksBuilder) } func (o *Orchestrator) hydrateBatcherMaybe(net *descriptors.L2Chain, l2Net stack.ExtensibleL2Network) { diff --git a/op-devstack/sysgo/add_game_type.go b/op-devstack/sysgo/add_game_type.go index 8dfd5e7ff67..63d674d1d5d 100644 --- a/op-devstack/sysgo/add_game_type.go +++ b/op-devstack/sysgo/add_game_type.go @@ -8,7 +8,7 @@ import ( "runtime" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/manage" "github.com/ethereum-optimism/optimism/op-devstack/devtest" @@ -28,8 +28,8 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -func WithGameTypeAdded(gameType types.GameType) stack.Option[*Orchestrator] { - if gameType == types.PermissionedGameType { +func WithGameTypeAdded(gameType gameTypes.GameType) stack.Option[*Orchestrator] { + if gameType == gameTypes.PermissionedGameType { // Permissioned games are added as part of the initial deployment // so no action required. return stack.Combine[*Orchestrator]() @@ -45,7 +45,7 @@ func WithGameTypeAdded(gameType types.GameType) stack.Option[*Orchestrator] { return opts } -func WithRespectedGameType(gameType types.GameType) stack.Option[*Orchestrator] { +func WithRespectedGameType(gameType gameTypes.GameType) stack.Option[*Orchestrator] { return stack.FnOption[*Orchestrator]{ FinallyFn: func(o *Orchestrator) { for _, l2ChainID := range o.l2Nets.Keys() { @@ -60,7 +60,7 @@ func WithCannonGameTypeAdded(l1ELID stack.L1ELNodeID, l2ChainID eth.ChainID) sta FinallyFn: func(o *Orchestrator) { // TODO(#17867): Rebuild the op-program prestate using the newly minted L2 chain configs before using it. absolutePrestate := getAbsolutePrestate(o.P(), "op-program/bin/prestate-proof-mt64.json") - addGameType(o, absolutePrestate, types.CannonGameType, l1ELID, l2ChainID) + addGameType(o, absolutePrestate, gameTypes.CannonGameType, l1ELID, l2ChainID) }, } } @@ -73,7 +73,7 @@ func WithCannonKonaGameTypeAdded() stack.Option[*Orchestrator] { FinallyFn: func(o *Orchestrator) { absolutePrestate := getCannonKonaAbsolutePrestate(o.P()) for _, l2ChainID := range o.l2Nets.Keys() { - addGameType(o, absolutePrestate, types.CannonKonaGameType, o.l1ELs.Keys()[0], l2ChainID) + addGameType(o, absolutePrestate, gameTypes.CannonKonaGameType, o.l1ELs.Keys()[0], l2ChainID) } }, } @@ -87,7 +87,7 @@ func WithChallengerCannonKonaEnabled() stack.Option[*Orchestrator] { } } -func setRespectedGameType(o *Orchestrator, gameType types.GameType, l1ELID stack.L1ELNodeID, l2ChainID eth.ChainID) { +func setRespectedGameType(o *Orchestrator, gameType gameTypes.GameType, l1ELID stack.L1ELNodeID, l2ChainID eth.ChainID) { t := o.P() require := t.Require() require.NotNil(o.wb, "must have a world builder") @@ -139,7 +139,7 @@ func setRespectedGameType(o *Orchestrator, gameType types.GameType, l1ELID stack require.Equal(rcpt.Status, gethTypes.ReceiptStatusSuccessful, "set respected game type tx did not execute correctly") } -func addGameType(o *Orchestrator, absolutePrestate common.Hash, gameType types.GameType, l1ELID stack.L1ELNodeID, l2ChainID eth.ChainID) { +func addGameType(o *Orchestrator, absolutePrestate common.Hash, gameType gameTypes.GameType, l1ELID stack.L1ELNodeID, l2ChainID eth.ChainID) { t := o.P() require := t.Require() require.NotNil(o.wb, "must have a world builder") @@ -210,11 +210,11 @@ func addGameType(o *Orchestrator, absolutePrestate common.Hash, gameType types.G transferOwnershipForDelegateCallProxy(t, l1ChainID.ToBig(), l1PAOKey, client, delegateCallProxy, dgf, l1PAO) } -func PrestateForGameType(t devtest.CommonT, gameType types.GameType) common.Hash { +func PrestateForGameType(t devtest.CommonT, gameType gameTypes.GameType) common.Hash { switch gameType { - case types.CannonGameType: + case gameTypes.CannonGameType: return getAbsolutePrestate(t, "op-program/bin/prestate-proof-mt64.json") - case types.CannonKonaGameType: + case gameTypes.CannonKonaGameType: return getCannonKonaAbsolutePrestate(t) default: t.Require().Fail("no prestate available for game type", gameType) diff --git a/op-devstack/sysgo/control_plane.go b/op-devstack/sysgo/control_plane.go index 09aa358c02f..a2817b6a11a 100644 --- a/op-devstack/sysgo/control_plane.go +++ b/op-devstack/sysgo/control_plane.go @@ -42,4 +42,16 @@ func (c *ControlPlane) FakePoSState(id stack.L1CLNodeID, mode stack.ControlActio control(s.fakepos, mode) } +func (c *ControlPlane) OPRBuilderNodeState(id stack.OPRBuilderNodeID, mode stack.ControlAction) { + s, ok := c.o.oprbuilderNodes.Get(id) + c.o.P().Require().True(ok, "need oprbuilder node to change state") + control(s, mode) +} + +func (c *ControlPlane) RollupBoostNodeState(id stack.RollupBoostNodeID, mode stack.ControlAction) { + s, ok := c.o.rollupBoosts.Get(id) + c.o.P().Require().True(ok, "need rollup boost node to change state") + control(s, mode) +} + var _ stack.ControlPlane = (*ControlPlane)(nil) diff --git a/op-devstack/sysgo/el_node_identity.go b/op-devstack/sysgo/el_node_identity.go new file mode 100644 index 00000000000..bd21dfa48d8 --- /dev/null +++ b/op-devstack/sysgo/el_node_identity.go @@ -0,0 +1,47 @@ +package sysgo + +import ( + "crypto/ecdsa" + "encoding/hex" + "net" + "strconv" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +type ELNodeIdentity struct { + Key *ecdsa.PrivateKey + Port int + Enode string +} + +func NewELNodeIdentity(addr string, port int) *ELNodeIdentity { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + if port <= 0 { + portStr, err := getAvailableLocalPort() + if err != nil { + panic(err) + } + port, err = strconv.Atoi(portStr) + if err != nil { + panic(err) + } + } + ip := net.ParseIP(addr) + if ip == nil { + panic("invalid ip for ELNodeIdentity: " + addr) + } + return &ELNodeIdentity{ + Key: key, + Port: port, + Enode: enode.NewV4(&key.PublicKey, ip, port, port).String(), + } +} + +func (id *ELNodeIdentity) KeyHex() string { + return hex.EncodeToString(crypto.FromECDSA(id.Key)) +} diff --git a/op-devstack/sysgo/l1_nodes_subprocess.go b/op-devstack/sysgo/l1_nodes_subprocess.go index ee88a2d9e73..4ebc01ac373 100644 --- a/op-devstack/sysgo/l1_nodes_subprocess.go +++ b/op-devstack/sysgo/l1_nodes_subprocess.go @@ -109,12 +109,12 @@ func (n *ExternalL1Geth) Start() { } } } - stdOutLogs := logpipe.LogProcessor(func(line []byte) { + stdOutLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseGoStructuredLogs(line) logOut(e) onLogEntry(e) }) - stdErrLogs := logpipe.LogProcessor(func(line []byte) { + stdErrLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseGoStructuredLogs(line) logErr(e) onLogEntry(e) diff --git a/op-devstack/sysgo/l2_challenger.go b/op-devstack/sysgo/l2_challenger.go index 7849ffc98d0..5f0999b7eaa 100644 --- a/op-devstack/sysgo/l2_challenger.go +++ b/op-devstack/sysgo/l2_challenger.go @@ -139,13 +139,13 @@ func WithL2ChallengerPostDeploy(orch *Orchestrator, challengerID stack.L2Challen shared.WithPrivKey(challengerSecret), shared.WithDepset(cluster.DepSet()), shared.WithCannonConfig(rollupCfgs, l1Genesis, l2Geneses, prestateVariant), - shared.WithSuperCannonTraceType(), - shared.WithSuperPermissionedTraceType(), + shared.WithSuperCannonGameType(), + shared.WithSuperPermissionedGameType(), } if orch.l2ChallengerOpts.useCannonKonaConfig { options = append(options, shared.WithCannonKonaConfig(rollupCfgs, l1Genesis, l2Geneses), - shared.WithCannonKonaTraceType(), + shared.WithCannonKonaGameType(), ) } cfg, err = shared.NewInteropChallengerConfig(dir, l1EL.UserRPC(), l1CL.beaconHTTPAddr, supervisorNode.UserRPC(), l2ELRPCs, options...) @@ -170,14 +170,14 @@ func WithL2ChallengerPostDeploy(orch *Orchestrator, challengerID stack.L2Challen shared.WithFactoryAddress(disputeGameFactoryAddr), shared.WithPrivKey(challengerSecret), shared.WithCannonConfig(rollupCfgs, l1Genesis, l2Geneses, prestateVariant), - shared.WithCannonTraceType(), - shared.WithPermissionedTraceType(), + shared.WithCannonGameType(), + shared.WithPermissionedGameType(), shared.WithFastGames(), } if orch.l2ChallengerOpts.useCannonKonaConfig { options = append(options, shared.WithCannonKonaConfig(rollupCfgs, l1Genesis, l2Geneses), - shared.WithCannonKonaTraceType(), + shared.WithCannonKonaGameType(), ) } cfg, err = shared.NewPreInteropChallengerConfig(dir, l1EL.UserRPC(), l1CL.beaconHTTPAddr, l2CL.UserRPC(), l2EL.UserRPC(), options...) diff --git a/op-devstack/sysgo/l2_cl.go b/op-devstack/sysgo/l2_cl.go index 3e1b4a4e658..f64c245953b 100644 --- a/op-devstack/sysgo/l2_cl.go +++ b/op-devstack/sysgo/l2_cl.go @@ -36,6 +36,10 @@ type L2CLConfig struct { // NoDiscovery is the flag to enable/disable discovery NoDiscovery bool + + // UnsafeOnly is the flag to disable derivation + SequencerUnsafeOnly bool + VerifierUnsafeOnly bool } func L2CLSequencer() L2CLOption { @@ -50,16 +54,24 @@ func L2CLIndexing() L2CLOption { }) } +func L2CLVerifierDisableUnsafeOnly() L2CLOption { + return L2CLOptionFn(func(p devtest.P, id stack.L2CLNodeID, cfg *L2CLConfig) { + cfg.VerifierUnsafeOnly = false + }) +} + func DefaultL2CLConfig() *L2CLConfig { return &L2CLConfig{ - SequencerSyncMode: nodeSync.CLSync, - VerifierSyncMode: nodeSync.CLSync, - SafeDBPath: "", - IsSequencer: false, - IndexingMode: false, - EnableReqRespSync: true, - UseReqRespSync: true, - NoDiscovery: false, + SequencerSyncMode: nodeSync.CLSync, + VerifierSyncMode: nodeSync.CLSync, + SafeDBPath: "", + IsSequencer: false, + IndexingMode: false, + EnableReqRespSync: true, + UseReqRespSync: true, + NoDiscovery: false, + SequencerUnsafeOnly: false, + VerifierUnsafeOnly: false, } } diff --git a/op-devstack/sysgo/l2_cl_kona.go b/op-devstack/sysgo/l2_cl_kona.go index 43bc28f5fa8..b9dfe61c327 100644 --- a/op-devstack/sysgo/l2_cl_kona.go +++ b/op-devstack/sysgo/l2_cl_kona.go @@ -111,12 +111,12 @@ func (k *KonaNode) Start() { metricsTargetChan <- NewPrometheusMetricsTarget(parsedUrl.Hostname(), parsedUrl.Port(), false) } } - stdOutLogs := logpipe.LogProcessor(func(line []byte) { + stdOutLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logOut(e) onLogEntry(e) }) - stdErrLogs := logpipe.LogProcessor(func(line []byte) { + stdErrLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logErr(e) }) @@ -181,7 +181,7 @@ func WithKonaNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack l1CL, ok := orch.l1CLs.Get(l1CLID) require.True(ok, "l1 CL node required") - l2EL, ok := orch.l2ELs.Get(l2ELID) + l2EL, ok := orch.GetL2EL(l2ELID) require.True(ok, "l2 EL node required") cfg := DefaultL2CLConfig() diff --git a/op-devstack/sysgo/l2_cl_opnode.go b/op-devstack/sysgo/l2_cl_opnode.go index 6f867b20313..ddc037f3a7d 100644 --- a/op-devstack/sysgo/l2_cl_opnode.go +++ b/op-devstack/sysgo/l2_cl_opnode.go @@ -73,7 +73,26 @@ func (n *OpNode) hydrate(system stack.ExtensibleSystem) { l2Net := system.L2Network(stack.L2NetworkID(n.id.ChainID())) l2Net.(stack.ExtensibleL2Network).AddL2CLNode(sysL2CL) if n.el != nil { - sysL2CL.(stack.LinkableL2CLNode).LinkEL(l2Net.L2ELNode(n.el)) + for _, el := range l2Net.L2ELNodes() { + if el.ID() == *n.el { + sysL2CL.(stack.LinkableL2CLNode).LinkEL(el) + return + } + } + rbID := stack.RollupBoostNodeID(*n.el) + for _, rb := range l2Net.RollupBoostNodes() { + if rb.ID() == rbID { + sysL2CL.(stack.LinkableL2CLNode).LinkRollupBoostNode(rb) + return + } + } + oprbID := stack.OPRBuilderNodeID(*n.el) + for _, oprb := range l2Net.OPRBuilderNodes() { + if oprb.ID() == oprbID { + sysL2CL.(stack.LinkableL2CLNode).LinkOPRBuilderNode(oprb) + return + } + } } } @@ -160,7 +179,7 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L require.True(ok, "l1 CL node required") // Get the L2EL node (which can be a regular EL node or a SyncTesterEL) - l2EL, ok := orch.l2ELs.Get(l2ELID) + l2EL, ok := orch.GetL2EL(l2ELID) require.True(ok, "l2 EL node required") // Get dependency set from cluster if available @@ -174,12 +193,16 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L L2CLOptionBundle(opts).Apply(p, l2CLID, cfg) // apply specific options syncMode := cfg.VerifierSyncMode + unsafeOnly := false if cfg.IsSequencer { syncMode = cfg.SequencerSyncMode // Sanity check, to navigate legacy sync-mode test assumptions. // Can't enable ELSync on the sequencer or it will never start sequencing because // ELSync needs to receive gossip from the sequencer to drive the sync p.Require().NotEqual(nodeSync.ELSync, syncMode, "sequencer cannot use EL sync") + unsafeOnly = cfg.SequencerUnsafeOnly + } else { + unsafeOnly = cfg.VerifierUnsafeOnly } jwtPath, jwtSecret := orch.writeDefaultJWT() @@ -245,9 +268,6 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L // Set the req-resp sync flag as per config p2pConfig.EnableReqRespSync = cfg.EnableReqRespSync - // Get the L2 engine address from the EL node (which can be a regular EL node or a SyncTesterEL) - l2EngineAddr := l2EL.EngineRPC() - nodeCfg := &config.Config{ L1: &config.L1EndpointConfig{ L1NodeAddr: l1EL.UserRPC(), @@ -261,7 +281,7 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L }, L1ChainConfig: l1Net.genesis.Config, L2: &config.L2EndpointConfig{ - L2EngineAddr: l2EngineAddr, + L2EngineAddr: l2EL.EngineRPC(), L2EngineJWTSecret: jwtSecret, }, Beacon: &config.L1BeaconEndpointConfig{ @@ -291,6 +311,9 @@ func WithOpNode(l2CLID stack.L2CLNodeID, l1CLID stack.L1CLNodeID, l1ELID stack.L SyncModeReqResp: cfg.UseReqRespSync, SkipSyncStartCheck: false, SupportsPostFinalizationELSync: false, + UnsafeOnly: unsafeOnly, + L2FollowSourceEndpoint: "", + NeedInitialResetEngine: cfg.IsSequencer && unsafeOnly, }, ConfigPersistence: config.DisabledConfigPersistence{}, Metrics: opmetrics.CLIConfig{}, diff --git a/op-devstack/sysgo/l2_el.go b/op-devstack/sysgo/l2_el.go index 19501a32766..8efb7d390aa 100644 --- a/op-devstack/sysgo/l2_el.go +++ b/op-devstack/sysgo/l2_el.go @@ -16,7 +16,12 @@ type L2ELNode interface { } type L2ELConfig struct { - SupervisorID *stack.SupervisorID + SupervisorID *stack.SupervisorID + P2PAddr string + P2PPort int + P2PNodeKeyHex string + StaticPeers []string + TrustedPeers []string } func L2ELWithSupervisor(supervisorID stack.SupervisorID) L2ELOption { @@ -25,9 +30,25 @@ func L2ELWithSupervisor(supervisorID stack.SupervisorID) L2ELOption { }) } +// L2ELWithP2PConfig sets deterministic P2P identity and static peers for the L2 EL. +func L2ELWithP2PConfig(addr string, port int, nodeKeyHex string, staticPeers, trustedPeers []string) L2ELOption { + return L2ELOptionFn(func(p devtest.P, id stack.L2ELNodeID, cfg *L2ELConfig) { + cfg.P2PAddr = addr + cfg.P2PPort = port + cfg.P2PNodeKeyHex = nodeKeyHex + cfg.StaticPeers = staticPeers + cfg.TrustedPeers = trustedPeers + }) +} + func DefaultL2ELConfig() *L2ELConfig { return &L2ELConfig{ - SupervisorID: nil, + SupervisorID: nil, + P2PAddr: "127.0.0.1", + P2PPort: 0, + P2PNodeKeyHex: "", + StaticPeers: nil, + TrustedPeers: nil, } } diff --git a/op-devstack/sysgo/l2_el_opgeth.go b/op-devstack/sysgo/l2_el_opgeth.go index e53f07ff3ad..34f8175fe10 100644 --- a/op-devstack/sysgo/l2_el_opgeth.go +++ b/op-devstack/sysgo/l2_el_opgeth.go @@ -1,12 +1,16 @@ package sysgo import ( + "strconv" + "strings" "sync" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/log" gn "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-devstack/devtest" @@ -30,6 +34,7 @@ type OpGeth struct { supervisorRPC string l2Geth *geth.GethInstance readOnly bool + cfg *L2ELConfig authRPC string userRPC string @@ -112,11 +117,46 @@ func (n *OpGeth) Start() { func(ethCfg *ethconfig.Config, nodeCfg *gn.Config) error { ethCfg.InteropMessageRPC = n.supervisorRPC ethCfg.InteropMempoolFiltering = true + + listenAddr := n.cfg.P2PAddr + port := n.cfg.P2PPort + listenAddr = listenAddr + ":" + strconv.Itoa(port) + nodeCfg.P2P = p2p.Config{ NoDiscovery: true, - ListenAddr: "127.0.0.1:0", + ListenAddr: listenAddr, MaxPeers: 10, } + + if n.cfg.P2PNodeKeyHex != "" { + priv, err := crypto.HexToECDSA(strings.TrimPrefix(n.cfg.P2PNodeKeyHex, "0x")) + if err != nil { + return err + } + nodeCfg.P2P.PrivateKey = priv + } + if len(n.cfg.StaticPeers) > 0 { + nodes := make([]*enode.Node, 0, len(n.cfg.StaticPeers)) + for _, p := range n.cfg.StaticPeers { + nn, err := enode.Parse(enode.ValidSchemes, p) + if err != nil { + return err + } + nodes = append(nodes, nn) + } + nodeCfg.P2P.StaticNodes = nodes + } + if len(n.cfg.TrustedPeers) > 0 { + nodes := make([]*enode.Node, 0, len(n.cfg.TrustedPeers)) + for _, p := range n.cfg.TrustedPeers { + nn, err := enode.Parse(enode.ValidSchemes, p) + if err != nil { + return err + } + nodes = append(nodes, nn) + } + nodeCfg.P2P.TrustedNodes = nodes + } return nil }) require.NoError(err) @@ -173,6 +213,7 @@ func WithOpGeth(id stack.L2ELNodeID, opts ...L2ELOption) stack.Option[*Orchestra jwtPath: jwtPath, jwtSecret: jwtSecret, supervisorRPC: supervisorRPC, + cfg: cfg, } l2EL.Start() p.Cleanup(func() { diff --git a/op-devstack/sysgo/l2_el_opreth.go b/op-devstack/sysgo/l2_el_opreth.go index c625b7290aa..3377ba09209 100644 --- a/op-devstack/sysgo/l2_el_opreth.go +++ b/op-devstack/sysgo/l2_el_opreth.go @@ -25,7 +25,6 @@ type OpReth struct { mu sync.Mutex id stack.L2ELNodeID - l2Net *L2Network jwtPath string jwtSecret [32]byte authRPC string @@ -134,12 +133,12 @@ func (n *OpReth) Start() { metricsTargetChan <- NewPrometheusMetricsTarget(parsedUrl.Hostname(), parsedUrl.Port(), false) } } - stdOutLogs := logpipe.LogProcessor(func(line []byte) { + stdOutLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logOut(e) onLogEntry(e) }) - stdErrLogs := logpipe.LogProcessor(func(line []byte) { + stdErrLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logErr(e) }) @@ -280,7 +279,6 @@ func WithOpReth(id stack.L2ELNodeID, opts ...L2ELOption) stack.Option[*Orchestra l2EL := &OpReth{ id: id, - l2Net: l2Net, jwtPath: jwtPath, jwtSecret: jwtSecret, authRPC: "", diff --git a/op-devstack/sysgo/l2_metrics_dashboard.go b/op-devstack/sysgo/l2_metrics_dashboard.go index ba7bbc2bf6f..645c51f00f8 100644 --- a/op-devstack/sysgo/l2_metrics_dashboard.go +++ b/op-devstack/sysgo/l2_metrics_dashboard.go @@ -93,11 +93,11 @@ func (g *L2MetricsDashboard) startPrometheus() { logOut := logpipe.ToLogger(g.p.Logger().New("component", "prometheus", "src", "stdout")) logErr := logpipe.ToLogger(g.p.Logger().New("component", "prometheus", "src", "stderr")) - stdOutLogs := logpipe.LogProcessor(func(line []byte) { + stdOutLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logOut(e) }) - stdErrLogs := logpipe.LogProcessor(func(line []byte) { + stdErrLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logErr(e) }) @@ -116,11 +116,11 @@ func (g *L2MetricsDashboard) startGrafana() { logOut := logpipe.ToLogger(g.p.Logger().New("component", "grafana", "src", "stdout")) logErr := logpipe.ToLogger(g.p.Logger().New("component", "grafana", "src", "stderr")) - stdOutLogs := logpipe.LogProcessor(func(line []byte) { + stdOutLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logOut(e) }) - stdErrLogs := logpipe.LogProcessor(func(line []byte) { + stdErrLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logErr(e) }) diff --git a/op-devstack/sysgo/op_rbuilder.go b/op-devstack/sysgo/op_rbuilder.go new file mode 100644 index 00000000000..4bab3d0002e --- /dev/null +++ b/op-devstack/sysgo/op_rbuilder.go @@ -0,0 +1,470 @@ +// Moved from fb_OPRbuilderNode_real.go +package sysgo + +import ( + "encoding/hex" + "encoding/json" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/shim" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/logpipe" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" +) + +type OPRBuilderNode struct { + mu sync.Mutex + + id stack.OPRBuilderNodeID + rollupCfg *rollup.Config + + wsProxyURL string + wsProxy *tcpproxy.Proxy + + rpcProxyURL string + rpcProxy *tcpproxy.Proxy + + authProxyURL string + authProxy *tcpproxy.Proxy + + logger log.Logger + p devtest.P + + sub *SubProcess + cfg *OPRBuilderNodeConfig //nolint:unused,structcheck // configuration retained for restarts and JWT lookups +} + +var _ hydrator = (*OPRBuilderNode)(nil) +var _ stack.Lifecycle = (*OPRBuilderNode)(nil) +var _ L2ELNode = (*OPRBuilderNode)(nil) + +// OPRBuilderNodeConfig contains configuration used to generate the op-OPRbuilderNode CLI. +// Callers can modify the defaults via OPRbuilderNodeOption functions. +type OPRBuilderNodeConfig struct { + // Chain selector (defaults to "dev" to avoid mainnet imports during tests) + Chain string + + // DataDir for op-OPRbuilderNode. If empty, a temp dir is created and cleaned up. + DataDir string + + // Logging formats + LogStdoutFormat string // e.g. "json" + LogFileFormat string // e.g. "json" + + // Flashblocks websocket bind address (host) + FlashblocksAddr string + // Flashblocks websocket port. 0 means auto-allocate an available local port. + FlashblocksPort int + // EnableFlashblocks enables the flashblocks feature. + EnableFlashblocks bool + + // --http + EnableRPC bool + RPCAPI string + RPCAddr string + RPCPort int + RPCJWTPath string + + AuthRPCJWTPath string + AuthRPCAddr string + AuthRPCPort int + + // P2P + P2PPort int + P2PAddr string + P2PNodeKeyHex string + StaticPeers []string + TrustedPeers []string + + // Misc process toggles + WithUnusedPorts bool // choose unused ports for subsystems + DisableDiscovery bool // avoid discv5 UDP socket collisions + + Full bool + + // ExtraArgs are appended to the generated CLI allowing callers to override defaults + // if the binary respects "last flag wins". + ExtraArgs []string + // Env is passed to the subprocess environment. + Env []string +} + +func DefaultOPRbuilderNodeConfig() *OPRBuilderNodeConfig { + return &OPRBuilderNodeConfig{ + EnableFlashblocks: true, + FlashblocksAddr: "127.0.0.1", + FlashblocksPort: 0, + EnableRPC: true, + RPCAPI: "admin,web3,debug,eth,txpool,net,miner", + RPCAddr: "127.0.0.1", + RPCPort: 0, + RPCJWTPath: "", + AuthRPCAddr: "127.0.0.1", + AuthRPCPort: 0, + AuthRPCJWTPath: "", + P2PAddr: "127.0.0.1", + P2PPort: 0, + P2PNodeKeyHex: "", + StaticPeers: nil, + TrustedPeers: nil, + Full: true, + LogStdoutFormat: "json", + LogFileFormat: "json", + Chain: "dev", + WithUnusedPorts: false, + DisableDiscovery: true, + DataDir: "", + ExtraArgs: nil, + Env: nil, + } +} + +func (cfg *OPRBuilderNodeConfig) LaunchSpec(p devtest.P) (args []string, env []string) { + p.Require().NotNil(cfg, "nil OPRbuilderNodeConfig") + + env = append([]string(nil), cfg.Env...) + args = make([]string, 0, len(cfg.ExtraArgs)+8) + + args = append(args, "node") + + if cfg.EnableFlashblocks { + if cfg.FlashblocksAddr == "" { + cfg.FlashblocksAddr = "127.0.0.1" + } + if cfg.FlashblocksPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate flashblocks port") + portVal, err := strconv.Atoi(portStr) + p.Require().NoError(err, "parse flashblocks port") + cfg.FlashblocksPort = portVal + } + fbPortStr := strconv.Itoa(cfg.FlashblocksPort) + args = append(args, "--flashblocks.enabled") + args = append(args, "--flashblocks.addr="+cfg.FlashblocksAddr, "--flashblocks.port="+fbPortStr) + } + + // P2P configuration: enforce deterministic identity and static peering to the sequencer EL. + if cfg.P2PNodeKeyHex != "" { + key := strings.TrimPrefix(cfg.P2PNodeKeyHex, "0x") + _, err := hex.DecodeString(key) + p.Require().NoError(err, "decode p2p node key") + keyPath := filepath.Join(p.TempDir(), "oprbuilder-nodekey") + p.Require().NoError(os.WriteFile(keyPath, []byte(key), 0o600), "write p2p node key") + args = append(args, "--p2p-secret-key", keyPath) + } + if cfg.P2PAddr != "" { + args = append(args, "--addr", cfg.P2PAddr) + } + if len(cfg.StaticPeers) > 0 { + args = append(args, "--bootnodes", strings.Join(cfg.StaticPeers, ",")) + } + if len(cfg.TrustedPeers) > 0 { + args = append(args, "--trusted-peers", strings.Join(cfg.TrustedPeers, ",")) + } + + if cfg.EnableRPC { + args = append(args, "--http") + args = append(args, "--http.addr="+cfg.RPCAddr) + if cfg.RPCPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate rpc port") + portVal, err := strconv.Atoi(portStr) + p.Require().NoError(err, "parse rpc port") + cfg.RPCPort = portVal + } + rpcPortStr := strconv.Itoa(cfg.RPCPort) + args = append(args, "--http.port="+rpcPortStr) + args = append(args, "--http.api="+cfg.RPCAPI) + + } + + if cfg.AuthRPCAddr != "" { + args = append(args, "--authrpc.addr="+cfg.AuthRPCAddr) + } + if cfg.AuthRPCPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate auth rpc port") + portVal, err := strconv.Atoi(portStr) + p.Require().NoError(err, "parse auth rpc port") + cfg.AuthRPCPort = portVal + } + args = append(args, "--authrpc.port="+strconv.Itoa(cfg.AuthRPCPort)) + if cfg.AuthRPCJWTPath != "" { + args = append(args, "--authrpc.jwtsecret="+cfg.AuthRPCJWTPath) + } + + if cfg.Full { + args = append(args, "--full") + } + + if cfg.LogStdoutFormat != "" { + args = append(args, "--log.stdout.format="+cfg.LogStdoutFormat) + } + if cfg.LogFileFormat != "" { + args = append(args, "--log.file.format="+cfg.LogFileFormat) + } + if cfg.Chain != "" { + args = append(args, "--chain="+cfg.Chain) + } + if cfg.WithUnusedPorts { + args = append(args, "--with-unused-ports") + } + if cfg.DisableDiscovery { + args = append(args, "--disable-discovery") + } + + if !cfg.WithUnusedPorts { + if cfg.P2PPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate p2p port") + portVal, err := strconv.Atoi(portStr) + p.Require().NoError(err, "parse p2p port") + cfg.P2PPort = portVal + } + args = append(args, "--port="+strconv.Itoa(cfg.P2PPort)) + } + + if cfg.DataDir == "" { + tmpDir, err := os.MkdirTemp("", "op-OPRBuilderNode-datadir-*") + p.Require().NoError(err, "create temp datadir for op-OPRBuilderNode") + args = append(args, "--datadir="+tmpDir) + p.Cleanup(func() { _ = os.RemoveAll(tmpDir) }) + } else { + args = append(args, "--datadir="+cfg.DataDir) + } + + args = append(args, cfg.ExtraArgs...) + + return args, env +} + +type OPRBuilderNodeOption interface { + Apply(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) +} + +type OPRBuilderNodeOptionFn func(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) + +var _ OPRBuilderNodeOption = OPRBuilderNodeOptionFn(nil) + +func (fn OPRBuilderNodeOptionFn) Apply(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) { + fn(p, id, cfg) +} + +// OPRBuilderNodeOptionBundle applies multiple OPRBuilderNodeOptions in order. +type OPRBuilderNodeOptionBundle []OPRBuilderNodeOption + +var _ OPRBuilderNodeOption = OPRBuilderNodeOptionBundle(nil) + +func (b OPRBuilderNodeOptionBundle) Apply(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) { + for _, opt := range b { + p.Require().NotNil(opt, "cannot Apply nil OPRBuilderNodeOption") + opt.Apply(p, id, cfg) + } +} + +// OPRBuilderWithP2PConfig sets deterministic P2P identity and static peers for the builder EL. +func OPRBuilderWithP2PConfig(addr string, port int, nodeKeyHex string, staticPeers, trustedPeers []string) OPRBuilderNodeOption { + return OPRBuilderNodeOptionFn(func(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) { + cfg.P2PAddr = addr + cfg.P2PPort = port + cfg.P2PNodeKeyHex = nodeKeyHex + cfg.StaticPeers = staticPeers + cfg.TrustedPeers = trustedPeers + }) +} + +// OPRBuilderWithNodeIdentity applies an ELNodeIdentity directly to the builder EL. +func OPRBuilderWithNodeIdentity(identity *ELNodeIdentity, addr string, staticPeers, trustedPeers []string) OPRBuilderNodeOption { + return OPRBuilderNodeOptionFn(func(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) { + cfg.P2PAddr = addr + cfg.P2PPort = identity.Port + cfg.P2PNodeKeyHex = identity.KeyHex() + cfg.StaticPeers = staticPeers + cfg.TrustedPeers = trustedPeers + }) +} + +func OPRBuilderNodeWithExtraArgs(args ...string) OPRBuilderNodeOption { + return OPRBuilderNodeOptionFn(func(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) { + cfg.ExtraArgs = append(cfg.ExtraArgs, args...) + }) +} + +func OPRBuilderNodeWithEnv(env ...string) OPRBuilderNodeOption { + return OPRBuilderNodeOptionFn(func(p devtest.P, id stack.OPRBuilderNodeID, cfg *OPRBuilderNodeConfig) { + cfg.Env = append(cfg.Env, env...) + }) +} + +func (b *OPRBuilderNode) hydrate(system stack.ExtensibleSystem) { + elRPC, err := client.NewRPC(system.T().Ctx(), system.Logger(), b.rpcProxyURL, client.WithLazyDial()) + system.T().Require().NoError(err) + system.T().Cleanup(elRPC.Close) + + node := shim.NewOPRBuilderNode(shim.OPRBuilderNodeConfig{ + ID: b.id, + ELNodeConfig: shim.ELNodeConfig{ + CommonConfig: shim.NewCommonConfig(system.T()), + Client: elRPC, + ChainID: b.id.ChainID(), + }, + RollupCfg: b.rollupCfg, + FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ + CommonConfig: shim.NewCommonConfig(system.T()), + ID: stack.NewFlashblocksWSClientID(b.id.Key(), b.id.ChainID()), + WsUrl: b.wsProxyURL, + WsHeaders: nil, + }), + }) + system.L2Network(stack.L2NetworkID(b.id.ChainID())).(stack.ExtensibleL2Network).AddOPRBuilderNode(node) +} + +func (b *OPRBuilderNode) Start() { + b.mu.Lock() + defer b.mu.Unlock() + if b.sub != nil { + b.logger.Warn("OPRbuilderNode already started") + return + } + cfg := b.cfg + b.p.Require().NotNil(cfg, "OPRbuilderNode config not initialized") + + if b.wsProxy == nil { + b.wsProxy = tcpproxy.New(b.p.Logger()) + b.p.Require().NoError(b.wsProxy.Start()) + b.wsProxyURL = "ws://" + b.wsProxy.Addr() + b.p.Cleanup(func() { b.wsProxy.Close() }) + } + + if b.rpcProxy == nil { + b.rpcProxy = tcpproxy.New(b.p.Logger()) + b.p.Require().NoError(b.rpcProxy.Start()) + b.rpcProxyURL = "http://" + b.rpcProxy.Addr() + b.p.Cleanup(func() { b.rpcProxy.Close() }) + } + + if cfg.EnableRPC && b.authProxy == nil { + b.authProxy = tcpproxy.New(b.p.Logger()) + b.p.Require().NoError(b.authProxy.Start()) + b.authProxyURL = "http://" + b.authProxy.Addr() + b.p.Cleanup(func() { b.authProxy.Close() }) + } + + args, env := cfg.LaunchSpec(b.p) + + // Forward structured logs to Go logger + logOut := logpipe.ToLogger(b.logger.New("component", "op-OPRbuilderNode", "src", "stdout")) + logErr := logpipe.ToLogger(b.logger.New("component", "op-OPRbuilderNode", "src", "stderr")) + + stdOut := logpipe.LogCallback(func(line []byte) { + logOut(logpipe.ParseRustStructuredLogs(line)) + }) + stdErr := logpipe.LogCallback(func(line []byte) { + logErr(logpipe.ParseRustStructuredLogs(line)) + }) + + b.sub = NewSubProcess(b.p, stdOut, stdErr) + + exec := os.Getenv("OP_RBUILDER_EXEC_PATH") + b.p.Require().NotEmpty(exec, "OP_RBUILDER_EXEC_PATH must be set") + + err := b.sub.Start(exec, args, env) + b.p.Require().NoError(err, "start OPRBuilderNode") + + const readinessTimeout = 15 * time.Second + + if cfg.EnableRPC { + rpcUpstreamHostport := net.JoinHostPort(cfg.RPCAddr, strconv.Itoa(cfg.RPCPort)) + rpcUpstreamURL := "http://" + rpcUpstreamHostport + waitTCPReady(b.p, rpcUpstreamURL, readinessTimeout) + b.logger.Info("OPRBuilderNode upstream RPC ready", "rpc", rpcUpstreamURL) + b.rpcProxy.SetUpstream(ProxyAddr(b.p.Require(), rpcUpstreamURL)) + waitTCPReady(b.p, b.rpcProxyURL, readinessTimeout) + b.logger.Info("OPRBuilderNode proxy RPC ready", "proxy_rpc", b.rpcProxyURL) + + authUpstreamHostport := net.JoinHostPort(cfg.RPCAddr, strconv.Itoa(cfg.AuthRPCPort)) + authUpstreamURL := "http://" + authUpstreamHostport + waitTCPReady(b.p, authUpstreamURL, readinessTimeout) + b.logger.Info("OPRBuilderNode upstream auth RPC ready", "auth_rpc", authUpstreamURL) + b.authProxy.SetUpstream(ProxyAddr(b.p.Require(), authUpstreamURL)) + waitTCPReady(b.p, b.authProxyURL, readinessTimeout) + b.logger.Info("OPRBuilderNode proxy auth RPC ready", "proxy_auth_rpc", b.authProxyURL) + } + + if cfg.EnableFlashblocks { + wsUpstreamHostport := net.JoinHostPort(cfg.FlashblocksAddr, strconv.Itoa(cfg.FlashblocksPort)) + wsUpstreamURL := "ws://" + wsUpstreamHostport + waitWSReady(b.p, wsUpstreamURL, readinessTimeout) + b.logger.Info("OPRBuilderNode upstream WS ready", "ws", wsUpstreamURL) + b.wsProxy.SetUpstream(ProxyAddr(b.p.Require(), wsUpstreamURL)) + waitWSReady(b.p, b.wsProxyURL, readinessTimeout) + b.logger.Info("OPRBuilderNode proxy WS ready", "proxy_ws", b.wsProxyURL) + } +} + +func (b *OPRBuilderNode) Stop() { + b.mu.Lock() + defer b.mu.Unlock() + if b.sub == nil { + b.logger.Warn("OPRbuilderNode already stopped") + return + } + b.p.Require().NoError(b.sub.Stop(true)) + b.sub = nil +} + +// WithOPRBuilderNode constructs and starts an OPRbuilderNode using the provided options. +func WithOPRBuilderNode(id stack.OPRBuilderNodeID, opts ...OPRBuilderNodeOption) stack.Option[*Orchestrator] { + return stack.AfterDeploy(func(orch *Orchestrator) { + p := orch.P().WithCtx(stack.ContextWithID(orch.P().Ctx(), id)) + l2Net, ok := orch.l2Nets.Get(id.ChainID()) + p.Require().True(ok, "l2 network required") + + tempDir := p.TempDir() + data, err := json.Marshal(l2Net.genesis) + p.Require().NoError(err, "must json-encode genesis") + chainConfigPath := filepath.Join(tempDir, "genesis.json") + p.Require().NoError(os.WriteFile(chainConfigPath, data, 0o644), "must write genesis file") + + // Build config from options + cfg := DefaultOPRbuilderNodeConfig() + cfg.AuthRPCJWTPath, _ = orch.writeDefaultJWT() + cfg.Chain = chainConfigPath + OPRBuilderNodeOptionBundle(opts).Apply(orch.P(), id, cfg) + + rb := &OPRBuilderNode{ + id: id, + logger: p.Logger(), + p: p, + rollupCfg: l2Net.rollupCfg, + cfg: cfg, + } + p.Logger().Info("Starting OPRbuilderNode") + rb.Start() + p.Cleanup(rb.Stop) + orch.oprbuilderNodes.Set(id, rb) + }) +} + +func (b *OPRBuilderNode) EngineRPC() string { + return b.authProxyURL +} + +func (b *OPRBuilderNode) JWTPath() string { + return b.cfg.AuthRPCJWTPath +} + +func (b *OPRBuilderNode) UserRPC() string { + return b.rpcProxyURL +} diff --git a/op-devstack/sysgo/orchestrator.go b/op-devstack/sysgo/orchestrator.go index 90e0b261ebf..574af454c9e 100644 --- a/op-devstack/sysgo/orchestrator.go +++ b/op-devstack/sysgo/orchestrator.go @@ -37,19 +37,21 @@ type Orchestrator struct { SyncTesterELOptions SyncTesterELOptionBundle deployerPipelineOptions []DeployerPipelineOption - superchains locks.RWMap[stack.SuperchainID, *Superchain] - clusters locks.RWMap[stack.ClusterID, *Cluster] - l1Nets locks.RWMap[eth.ChainID, *L1Network] - l2Nets locks.RWMap[eth.ChainID, *L2Network] - l1ELs locks.RWMap[stack.L1ELNodeID, L1ELNode] - l1CLs locks.RWMap[stack.L1CLNodeID, *L1CLNode] - l2ELs locks.RWMap[stack.L2ELNodeID, L2ELNode] - l2CLs locks.RWMap[stack.L2CLNodeID, L2CLNode] - supervisors locks.RWMap[stack.SupervisorID, Supervisor] - testSequencers locks.RWMap[stack.TestSequencerID, *TestSequencer] - batchers locks.RWMap[stack.L2BatcherID, *L2Batcher] - challengers locks.RWMap[stack.L2ChallengerID, *L2Challenger] - proposers locks.RWMap[stack.L2ProposerID, *L2Proposer] + superchains locks.RWMap[stack.SuperchainID, *Superchain] + clusters locks.RWMap[stack.ClusterID, *Cluster] + l1Nets locks.RWMap[eth.ChainID, *L1Network] + l2Nets locks.RWMap[eth.ChainID, *L2Network] + l1ELs locks.RWMap[stack.L1ELNodeID, L1ELNode] + l1CLs locks.RWMap[stack.L1CLNodeID, *L1CLNode] + l2ELs locks.RWMap[stack.L2ELNodeID, L2ELNode] + l2CLs locks.RWMap[stack.L2CLNodeID, L2CLNode] + supervisors locks.RWMap[stack.SupervisorID, Supervisor] + testSequencers locks.RWMap[stack.TestSequencerID, *TestSequencer] + batchers locks.RWMap[stack.L2BatcherID, *L2Batcher] + challengers locks.RWMap[stack.L2ChallengerID, *L2Challenger] + proposers locks.RWMap[stack.L2ProposerID, *L2Proposer] + rollupBoosts locks.RWMap[stack.RollupBoostNodeID, *RollupBoostNode] + oprbuilderNodes locks.RWMap[stack.OPRBuilderNodeID, *OPRBuilderNode] // service name => prometheus endpoints to scrape l2MetricsEndpoints locks.RWMap[string, []PrometheusMetricsTarget] @@ -91,6 +93,29 @@ func (o *Orchestrator) EnableTimeTravel() { } } +// GetL2EL attempts to find an L2 EL node by checking various collections of EL-like nodes. +// It returns the L2ELNode interface if found in the standard L2ELs collection, +// or the raw node object if found in other collections (e.g. RollupBoostNode). +func (o *Orchestrator) GetL2EL(id stack.L2ELNodeID) (L2ELNode, bool) { + if el, ok := o.l2ELs.Get(id); ok { + return el, true + } + + // Check RollupBoost + rbID := stack.NewRollupBoostNodeID(id.Key(), id.ChainID()) + if rb, ok := o.rollupBoosts.Get(rbID); ok { + return rb, true + } + + // Check op-rbuilder + oprbID := stack.NewOPRBuilderNodeID(id.Key(), id.ChainID()) + if oprbuilder, ok := o.oprbuilderNodes.Get(oprbID); ok { + return oprbuilder, true + } + + return nil, false +} + var _ stack.Orchestrator = (*Orchestrator)(nil) func NewOrchestrator(p devtest.P, hook stack.SystemHook) *Orchestrator { @@ -129,6 +154,8 @@ func (o *Orchestrator) Hydrate(sys stack.ExtensibleSystem) { o.l1ELs.Range(rangeHydrateFn[stack.L1ELNodeID, L1ELNode](sys)) o.l1CLs.Range(rangeHydrateFn[stack.L1CLNodeID, *L1CLNode](sys)) o.l2ELs.Range(rangeHydrateFn[stack.L2ELNodeID, L2ELNode](sys)) + o.oprbuilderNodes.Range(rangeHydrateFn[stack.OPRBuilderNodeID, *OPRBuilderNode](sys)) + o.rollupBoosts.Range(rangeHydrateFn[stack.RollupBoostNodeID, *RollupBoostNode](sys)) o.l2CLs.Range(rangeHydrateFn[stack.L2CLNodeID, L2CLNode](sys)) o.supervisors.Range(rangeHydrateFn[stack.SupervisorID, Supervisor](sys)) o.testSequencers.Range(rangeHydrateFn[stack.TestSequencerID, *TestSequencer](sys)) diff --git a/op-devstack/sysgo/rollup_boost.go b/op-devstack/sysgo/rollup_boost.go new file mode 100644 index 00000000000..90258e40a21 --- /dev/null +++ b/op-devstack/sysgo/rollup_boost.go @@ -0,0 +1,406 @@ +package sysgo + +import ( + "net" + "net/http" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/shim" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/logpipe" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" +) + +// RollupBoostNode is a lightweight sysgo-managed process wrapper around a rollup-boost +// WebSocket stream source. It exposes a stable proxied ws URL and hydrates the L2 +// network with a FlashblocksWSClient shim that points at it. +type RollupBoostNode struct { + mu sync.Mutex + + id stack.RollupBoostNodeID + wsProxyURL string + wsProxy *tcpproxy.Proxy + + rpcProxyURL string + rpcProxy *tcpproxy.Proxy + + header http.Header + + logger log.Logger + p devtest.P + + sub *SubProcess + + cfg *RollupBoostConfig +} + +var _ hydrator = (*RollupBoostNode)(nil) +var _ stack.Lifecycle = (*RollupBoostNode)(nil) +var _ L2ELNode = (*RollupBoostNode)(nil) + +func (r *RollupBoostNode) hydrate(system stack.ExtensibleSystem) { + elRPC, err := client.NewRPC(system.T().Ctx(), system.Logger(), r.rpcProxyURL, client.WithLazyDial()) + system.T().Require().NoError(err) + system.T().Cleanup(elRPC.Close) + + node := shim.NewRollupBoostNode(shim.RollupBoostNodeConfig{ + ID: r.id, + ELNodeConfig: shim.ELNodeConfig{ + CommonConfig: shim.NewCommonConfig(system.T()), + Client: elRPC, + ChainID: r.id.ChainID(), + }, + RollupCfg: system.L2Network(stack.L2NetworkID(r.id.ChainID())).RollupConfig(), + FlashblocksWsClient: shim.NewFlashblocksWSClient(shim.FlashblocksWSClientConfig{ + CommonConfig: shim.NewCommonConfig(system.T()), + ID: stack.NewFlashblocksWSClientID(r.id.Key(), r.id.ChainID()), + WsUrl: r.wsProxyURL, + WsHeaders: r.header, + }), + }) + system.L2Network(stack.L2NetworkID(r.id.ChainID())).(stack.ExtensibleL2Network).AddRollupBoostNode(node) +} + +func (r *RollupBoostNode) Start() { + r.mu.Lock() + defer r.mu.Unlock() + if r.sub != nil { + r.logger.Warn("rollup-boost already started") + return + } + + cfg := r.cfg + r.p.Require().NotNil(cfg, "rollup-boost config not initialized") + + args, env := cfg.LaunchSpec(r.p) + + if r.wsProxy == nil { + r.wsProxy = tcpproxy.New(r.p.Logger()) + r.p.Require().NoError(r.wsProxy.Start()) + r.wsProxyURL = "ws://" + r.wsProxy.Addr() + r.p.Cleanup(func() { r.wsProxy.Close() }) + } + + if r.rpcProxy == nil { + r.rpcProxy = tcpproxy.New(r.p.Logger()) + r.p.Require().NoError(r.rpcProxy.Start()) + r.rpcProxyURL = "http://" + r.rpcProxy.Addr() + r.p.Cleanup(func() { r.rpcProxy.Close() }) + } + + // Parse Rust-structured logs and forward into Go logger with attributes + logOut := logpipe.ToLogger(r.logger.New("stream", "stdout")) + logErr := logpipe.ToLogger(r.logger.New("stream", "stderr")) + + stdOut := logpipe.LogCallback(func(line []byte) { + logOut(logpipe.ParseRustStructuredLogs(line)) + }) + stdErr := logpipe.LogCallback(func(line []byte) { + logErr(logpipe.ParseRustStructuredLogs(line)) + }) + + r.sub = NewSubProcess(r.p, stdOut, stdErr) + + exec := os.Getenv("ROLLUP_BOOST_EXEC_PATH") + r.p.Require().NotEmpty(exec, "ROLLUP_BOOST_EXEC_PATH must be set") + + err := r.sub.Start(exec, args, env) + r.p.Require().NoError(err, "start rollup-boost") + + rpcUpstreamURL := "http://" + cfg.RPCHost + ":" + strconv.Itoa(int(cfg.RPCPort)) + waitTCPReady(r.p, rpcUpstreamURL, 5*time.Second) + r.logger.Info("rollup-boost upstream RPC ready", "rpc", rpcUpstreamURL) + r.rpcProxy.SetUpstream(ProxyAddr(r.p.Require(), rpcUpstreamURL)) + waitTCPReady(r.p, r.rpcProxyURL, 10*time.Second) + r.logger.Info("rollup-boost proxy RPC ready", "proxy_rpc", r.rpcProxyURL) + + // WS: wait for upstream first, then configure and test proxy + if cfg.EnableFlashblocks { + wsUpstreamHostport := net.JoinHostPort(cfg.FlashblocksHost, strconv.Itoa(cfg.FlashblocksPort)) + wsUpstreamURL := "ws://" + wsUpstreamHostport + + // Wait for upstream WS TCP endpoint + waitTCPReady(r.p, wsUpstreamURL, 5*time.Second) + r.logger.Info("rollup-boost upstream WS ready", "upstream_ws", wsUpstreamURL) + + r.wsProxy.SetUpstream(ProxyAddr(r.p.Require(), wsUpstreamURL)) + waitWSReady(r.p, r.wsProxyURL, 10*time.Second) + r.logger.Info("rollup-boost proxy WS ready", "proxy_ws", r.wsProxyURL) + } +} + +func (r *RollupBoostNode) Stop() { + r.mu.Lock() + defer r.mu.Unlock() + if r.sub == nil { + r.logger.Warn("rollup-boost already stopped") + return + } + r.p.Require().NoError(r.sub.Stop(true)) + r.sub = nil +} + +// WithRollupBoost starts a rollup-boost process using the provided options +// and registers a FlashblocksWSClient on the target L2 chain. +// l2ELID is required to link the proxy to the L2 EL it serves. +func WithRollupBoost(id stack.RollupBoostNodeID, l2ELID stack.L2ELNodeID, opts ...RollupBoostOption) stack.Option[*Orchestrator] { + return stack.AfterDeploy(func(orch *Orchestrator) { + p := orch.P().WithCtx(stack.ContextWithID(orch.P().Ctx(), id)) + logger := p.Logger() + + // Build config from options and derive sensible defaults + cfg := DefaultRollupBoostConfig() + RollupBoostOptionBundle(opts).Apply(orch, id, cfg) + // Source L2 engine/JWT from the L2 EL object (mandatory) + if l2EL, ok := orch.l2ELs.Get(l2ELID); ok { + engineRPC := l2EL.EngineRPC() + switch { + case strings.HasPrefix(engineRPC, "ws://"): + engineRPC = "http://" + strings.TrimPrefix(engineRPC, "ws://") + case strings.HasPrefix(engineRPC, "wss://"): + engineRPC = "https://" + strings.TrimPrefix(engineRPC, "wss://") + } + cfg.L2EngineURL = engineRPC + cfg.L2JWTPath = l2EL.JWTPath() + } + // Normalize builder URL and fallback JWT will be handled after builder link options are applied below. + + r := &RollupBoostNode{ + id: id, + logger: logger, + p: p, + cfg: cfg, + header: cfg.Headers, + } + // Apply any node-level link options + for _, opt := range opts { + if linkOpt, ok := opt.(interface { + applyNode(p devtest.P, id stack.RollupBoostNodeID, r *RollupBoostNode) + }); ok { + linkOpt.applyNode(p, id, r) + } + } + logger.Info("Starting rollup-boost") + r.Start() + p.Cleanup(r.Stop) + // Register for hydration + orch.rollupBoosts.Set(id, r) + }) +} + +// RollupBoostConfig configures the rollup-boost process CLI and environment. +type RollupBoostConfig struct { + // RPC endpoint for rollup-boost itself + RPCHost string + RPCPort uint16 + + // Flashblocks proxy WebSocket exposure + EnableFlashblocks bool + FlashblocksHost string + FlashblocksPort int + + // L2 engine connection details (HTTP(S)) + L2EngineURL string + L2JWTPath string + + // Builder engine connection details (HTTP(S)) + BuilderURL string + BuilderJWTPath string + FlashblocksBuilderURL string // upstream builder WS url (e.g. op-rbuilder ws) + + // Other settings + ExecutionMode string // e.g. "enabled" + LogFormat string // e.g. "json" + + // Debug server + DebugHost string + DebugPort int + + // Optional WS headers to expose to clients through the proxy + Headers http.Header + + // Env variables for the subprocess + Env []string + // ExtraArgs appended to the generated CLI (last-flag-wins semantics) + ExtraArgs []string +} + +func DefaultRollupBoostConfig() *RollupBoostConfig { + return &RollupBoostConfig{ + RPCHost: "127.0.0.1", + RPCPort: 0, + EnableFlashblocks: true, + FlashblocksHost: "127.0.0.1", + FlashblocksPort: 0, + FlashblocksBuilderURL: "", + L2EngineURL: "", + L2JWTPath: "", + BuilderURL: "127.0.0.1:8551", // normalized to http:// later + BuilderJWTPath: "", + ExecutionMode: "enabled", + LogFormat: "json", + DebugHost: "127.0.0.1", + DebugPort: 0, + Headers: http.Header{}, + Env: nil, + ExtraArgs: nil, + } +} + +func (cfg *RollupBoostConfig) LaunchSpec(p devtest.P) (args []string, env []string) { + p.Require().NotNil(cfg, "nil RollupBoostConfig") + + env = append([]string(nil), cfg.Env...) + args = make([]string, 0, len(cfg.ExtraArgs)+16) + + if cfg.EnableFlashblocks { + if cfg.FlashblocksHost == "" { + cfg.FlashblocksHost = "127.0.0.1" + } + if cfg.FlashblocksPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate flashblocks port") + portVal, err := strconv.Atoi(portStr) + p.Require().NoError(err, "parse flashblocks port") + cfg.FlashblocksPort = portVal + } + fbPortStr := strconv.Itoa(cfg.FlashblocksPort) + args = append(args, "--flashblocks", "--flashblocks-host="+cfg.FlashblocksHost, "--flashblocks-port="+fbPortStr) + if cfg.FlashblocksBuilderURL != "" { + args = append(args, "--flashblocks-builder-url="+cfg.FlashblocksBuilderURL) + } + } + + if cfg.RPCPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate rollup-boost rpc port") + portVal, err := strconv.ParseUint(portStr, 10, 16) + p.Require().NoError(err, "parse rollup-boost rpc port") + cfg.RPCPort = uint16(portVal) + } + p.Require().True(cfg.RPCPort > 0, "RPCPort must be > 0") + args = append(args, "--rpc-host="+cfg.RPCHost, "--rpc-port="+strconv.Itoa(int(cfg.RPCPort))) + + if cfg.L2EngineURL != "" { + args = append(args, "--l2-url="+ensureHTTPURL(cfg.L2EngineURL)) + } + if cfg.L2JWTPath != "" { + args = append(args, "--l2-jwt-path="+cfg.L2JWTPath) + } + if cfg.BuilderURL != "" { + args = append(args, "--builder-url="+ensureHTTPURL(cfg.BuilderURL)) + } + if cfg.BuilderJWTPath != "" { + args = append(args, "--builder-jwt-path="+cfg.BuilderJWTPath) + } + + if cfg.ExecutionMode != "" { + args = append(args, "--execution-mode="+cfg.ExecutionMode) + } + if cfg.LogFormat != "" { + args = append(args, "--log-format="+cfg.LogFormat) + } + + if cfg.DebugHost == "" { + cfg.DebugHost = "127.0.0.1" + } + if cfg.DebugPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate rollup-boost debug port") + portVal, err := strconv.Atoi(portStr) + p.Require().NoError(err, "parse rollup-boost debug port") + cfg.DebugPort = portVal + } + args = append(args, "--debug-host="+cfg.DebugHost, "--debug-server-port="+strconv.Itoa(cfg.DebugPort)) + + args = append(args, cfg.ExtraArgs...) + + return args, env +} + +type RollupBoostOption interface { + Apply(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) +} + +type RollupBoostOptionFn func(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) + +var _ RollupBoostOption = RollupBoostOptionFn(nil) + +func (fn RollupBoostOptionFn) Apply(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) { + fn(orch, id, cfg) +} + +type RollupBoostOptionBundle []RollupBoostOption + +var _ RollupBoostOption = RollupBoostOptionBundle(nil) + +func (b RollupBoostOptionBundle) Apply(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) { + for _, opt := range b { + orch.P().Require().NotNil(opt, "cannot Apply nil RollupBoostOption") + opt.Apply(orch, id, cfg) + } +} + +// Convenience options +func RollupBoostWithExecutionMode(mode string) RollupBoostOption { + return RollupBoostOptionFn(func(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) { + cfg.ExecutionMode = mode + }) +} + +func RollupBoostWithEnv(env ...string) RollupBoostOption { + return RollupBoostOptionFn(func(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) { + cfg.Env = append(cfg.Env, env...) + }) +} + +func RollupBoostWithExtraArgs(args ...string) RollupBoostOption { + return RollupBoostOptionFn(func(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) { + cfg.ExtraArgs = append(cfg.ExtraArgs, args...) + }) +} + +func RollupBoostWithBuilderNode(id stack.OPRBuilderNodeID) RollupBoostOption { + return RollupBoostOptionFn(func(orch *Orchestrator, rbID stack.RollupBoostNodeID, cfg *RollupBoostConfig) { + builderNode, ok := orch.oprbuilderNodes.Get(id) + if !ok { + orch.P().Require().FailNow("builder node not found") + } + cfg.BuilderURL = ensureHTTPURL(builderNode.authProxyURL) + cfg.BuilderJWTPath = builderNode.cfg.AuthRPCJWTPath + cfg.FlashblocksBuilderURL = builderNode.wsProxyURL + }) +} + +func RollupBoostWithFlashblocksDisabled() RollupBoostOption { + return RollupBoostOptionFn(func(orch *Orchestrator, id stack.RollupBoostNodeID, cfg *RollupBoostConfig) { + cfg.EnableFlashblocks = false + }) +} + +func ensureHTTPURL(u string) string { + if strings.Contains(u, "://") { + return u + } + return "http://" + u +} + +func (r *RollupBoostNode) EngineRPC() string { + return r.rpcProxyURL +} + +func (r *RollupBoostNode) JWTPath() string { + return r.cfg.L2JWTPath +} + +func (r *RollupBoostNode) UserRPC() string { + return r.rpcProxyURL +} diff --git a/op-devstack/sysgo/subproc.go b/op-devstack/sysgo/subproc.go index d2f5cb202ae..cb60e013444 100644 --- a/op-devstack/sysgo/subproc.go +++ b/op-devstack/sysgo/subproc.go @@ -15,17 +15,20 @@ type SubProcess struct { p devtest.P cmd *exec.Cmd - stdOutLogs logpipe.LogProcessor - stdErrLogs logpipe.LogProcessor + stdOutCallback logpipe.LogCallback + stdErrCallback logpipe.LogCallback + + stdOutProc *logpipe.LineBuffer + stdErrProc *logpipe.LineBuffer mu sync.Mutex } -func NewSubProcess(p devtest.P, stdOutLogs, stdErrLogs logpipe.LogProcessor) *SubProcess { +func NewSubProcess(p devtest.P, stdOutCallback, stdErrCallback logpipe.LogCallback) *SubProcess { return &SubProcess{ - p: p, - stdOutLogs: stdOutLogs, - stdErrLogs: stdErrLogs, + p: p, + stdOutCallback: stdOutCallback, + stdErrCallback: stdErrCallback, } } @@ -35,14 +38,21 @@ func (sp *SubProcess) Start(cmdPath string, args []string, env []string) error { if sp.cmd != nil { return fmt.Errorf("process is still running (PID: %d)", sp.cmd.Process.Pid) } + sp.p.Logger().Info("Starting subprocess", "cmd", cmdPath, "args", args) + + stdOutProc := logpipe.NewLineBuffer(sp.stdOutCallback) + stdErrProc := logpipe.NewLineBuffer(sp.stdErrCallback) + cmd := exec.Command(cmdPath, args...) cmd.Env = append(os.Environ(), env...) - cmd.Stdout = sp.stdOutLogs - cmd.Stderr = sp.stdErrLogs + cmd.Stdout = stdOutProc + cmd.Stderr = stdErrProc if err := cmd.Start(); err != nil { return err } sp.cmd = cmd + sp.stdOutProc = stdOutProc + sp.stdErrProc = stdErrProc sp.p.Cleanup(func() { err := sp.Stop(true) if err != nil { @@ -75,6 +85,14 @@ func (sp *SubProcess) Stop(interrupt bool) error { sp.p.Logger().Info("Sub-process gracefully exited") } + if sp.stdOutProc != nil { + _ = sp.stdOutProc.Close() + sp.stdOutProc = nil + } + if sp.stdErrProc != nil { + _ = sp.stdErrProc.Close() + sp.stdErrProc = nil + } sp.cmd = nil return nil } diff --git a/op-devstack/sysgo/subproc_test.go b/op-devstack/sysgo/subproc_test.go index e44e039aa9c..8e5a3c37fdf 100644 --- a/op-devstack/sysgo/subproc_test.go +++ b/op-devstack/sysgo/subproc_test.go @@ -26,11 +26,11 @@ func TestSubProcess(gt *testing.T) { p := devtest.NewP(context.Background(), logger, onFailNow, onSkipNow) gt.Cleanup(p.Close) - logProc := logpipe.LogProcessor(func(line []byte) { + logCallback := logpipe.LogCallback(func(line []byte) { logger.Info(string(line)) tLog.Info("Sub-process logged message", "line", string(line)) }) - sp := NewSubProcess(p, logProc, logProc) + sp := NewSubProcess(p, logCallback, logCallback) gt.Log("Running first sub-process") testSleep(gt, capt, sp) diff --git a/op-devstack/sysgo/supervisor_kona.go b/op-devstack/sysgo/supervisor_kona.go index b4473d9e73d..8d4b925b570 100644 --- a/op-devstack/sysgo/supervisor_kona.go +++ b/op-devstack/sysgo/supervisor_kona.go @@ -82,12 +82,12 @@ func (s *KonaSupervisor) Start() { userRPC <- "http://" + e.FieldValue("addr").(string) } } - stdOutLogs := logpipe.LogProcessor(func(line []byte) { + stdOutLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logOut(e) onLogEntry(e) }) - stdErrLogs := logpipe.LogProcessor(func(line []byte) { + stdErrLogs := logpipe.LogCallback(func(line []byte) { e := logpipe.ParseRustStructuredLogs(line) logErr(e) }) diff --git a/op-devstack/sysgo/system.go b/op-devstack/sysgo/system.go index 1fa516fe381..ef5e17a8a91 100644 --- a/op-devstack/sysgo/system.go +++ b/op-devstack/sysgo/system.go @@ -1,6 +1,9 @@ package sysgo import ( + "os" + "strings" + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-devstack/stack" @@ -574,3 +577,108 @@ func ProofSystem(dest *DefaultMinimalSystemIDs) stack.Option[*Orchestrator] { opt.Add(WithCannonGameTypeAdded(ids.L1EL, ids.L2.ChainID())) return opt } + +type SingleChainSystemWithFlashblocksIDs struct { + L1 stack.L1NetworkID + L1EL stack.L1ELNodeID + L1CL stack.L1CLNodeID + + L2 stack.L2NetworkID + L2CL stack.L2CLNodeID + L2EL stack.L2ELNodeID + L2Builder stack.OPRBuilderNodeID + L2RollupBoost stack.RollupBoostNodeID + + L2Batcher stack.L2BatcherID + L2Proposer stack.L2ProposerID + L2Challenger stack.L2ChallengerID + + TestSequencer stack.TestSequencerID +} + +func NewDefaultSingleChainSystemWithFlashblocksIDs(l1ID, l2ID eth.ChainID) SingleChainSystemWithFlashblocksIDs { + ids := SingleChainSystemWithFlashblocksIDs{ + L1: stack.L1NetworkID(l1ID), + L1EL: stack.NewL1ELNodeID("l1", l1ID), + L1CL: stack.NewL1CLNodeID("l1", l1ID), + L2: stack.L2NetworkID(l2ID), + L2CL: stack.NewL2CLNodeID("sequencer", l2ID), + L2EL: stack.NewL2ELNodeID("sequencer", l2ID), + L2Builder: stack.NewOPRBuilderNodeID("sequencer", l2ID), + L2RollupBoost: stack.NewRollupBoostNodeID("rollup-boost", l2ID), + L2Batcher: stack.NewL2BatcherID("main", l2ID), + L2Proposer: stack.NewL2ProposerID("main", l2ID), + L2Challenger: stack.NewL2ChallengerID("main", l2ID), + TestSequencer: "test-sequencer", + } + return ids +} + +func DefaultSingleChainSystemWithFlashblocks(dest *SingleChainSystemWithFlashblocksIDs) stack.Option[*Orchestrator] { + ids := NewDefaultSingleChainSystemWithFlashblocksIDs(DefaultL1ID, DefaultL2AID) + return singleChainSystemWithFlashblocksOpts(&ids, dest) +} + +func singleChainSystemWithFlashblocksOpts(ids *SingleChainSystemWithFlashblocksIDs, dest *SingleChainSystemWithFlashblocksIDs) stack.CombinedOption[*Orchestrator] { + opt := stack.Combine[*Orchestrator]() + // Precompute deterministic P2P identity and peering between sequencer EL and op-rbuilder EL. + seqID := NewELNodeIdentity("127.0.0.1", 0) + builderID := NewELNodeIdentity("127.0.0.1", 30303) // use default reth p2p port + + var missingEnv []string + if os.Getenv("OP_RBUILDER_EXEC_PATH") == "" { + missingEnv = append(missingEnv, "OP_RBUILDER_EXEC_PATH") + } + if os.Getenv("ROLLUP_BOOST_EXEC_PATH") == "" { + missingEnv = append(missingEnv, "ROLLUP_BOOST_EXEC_PATH") + } + if len(missingEnv) > 0 { + missing := strings.Join(missingEnv, ", ") + opt.Add(stack.BeforeDeploy(func(o *Orchestrator) { + o.P().Logger().Warn("Skipping single-chain flashblocks system; missing executables", "missing_env", missing) + o.P().SkipNow() + })) + return opt + } + + opt.Add(stack.BeforeDeploy(func(o *Orchestrator) { + o.P().Logger().Info("Setting up") + })) + + opt.Add(WithMnemonicKeys(devkeys.TestMnemonic)) + + opt.Add(WithDeployer(), + WithDeployerOptions( + WithLocalContractSources(), + WithCommons(ids.L1.ChainID()), + WithPrefundedL2(ids.L1.ChainID(), ids.L2.ChainID()), + ), + ) + + opt.Add(WithL1Nodes(ids.L1EL, ids.L1CL)) + + opt.Add(WithL2ELNode(ids.L2EL, L2ELWithP2PConfig("127.0.0.1", seqID.Port, seqID.KeyHex(), []string{builderID.Enode}, nil))) + opt.Add(WithOPRBuilderNode(ids.L2Builder, OPRBuilderWithNodeIdentity(builderID, "127.0.0.1", []string{seqID.Enode}, []string{seqID.Enode}))) + opt.Add(WithRollupBoost(ids.L2RollupBoost, ids.L2EL, RollupBoostWithBuilderNode(ids.L2Builder))) + + opt.Add(WithL2CLNode(ids.L2CL, ids.L1CL, ids.L1EL, stack.L2ELNodeID(ids.L2RollupBoost), L2CLSequencer())) + + opt.Add(WithBatcher(ids.L2Batcher, ids.L1EL, ids.L2CL, ids.L2EL)) + opt.Add(WithProposer(ids.L2Proposer, ids.L1EL, &ids.L2CL, nil)) + + opt.Add(WithFaucets([]stack.L1ELNodeID{ids.L1EL}, []stack.L2ELNodeID{ids.L2EL})) + + opt.Add(WithTestSequencer(ids.TestSequencer, ids.L1CL, ids.L2CL, ids.L1EL, ids.L2EL)) + + opt.Add(WithL2Challenger(ids.L2Challenger, ids.L1EL, ids.L1CL, nil, nil, &ids.L2CL, []stack.L2ELNodeID{ + ids.L2EL, + })) + + opt.Add(WithL2MetricsDashboard()) + + opt.Add(stack.Finally(func(orch *Orchestrator) { + *dest = *ids + })) + + return opt +} diff --git a/op-devstack/sysgo/system_singlechain_twoverifiers.go b/op-devstack/sysgo/system_singlechain_twoverifiers.go new file mode 100644 index 00000000000..9b47b2b0f04 --- /dev/null +++ b/op-devstack/sysgo/system_singlechain_twoverifiers.go @@ -0,0 +1,43 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type DefaultSingleChainTwoVerifiersSystemIDs struct { + DefaultSingleChainMultiNodeSystemIDs + + L2CLC stack.L2CLNodeID + L2ELC stack.L2ELNodeID +} + +func NewDefaultSingleChainTwoVerifiersSystemIDs(l1ID, l2ID eth.ChainID) DefaultSingleChainTwoVerifiersSystemIDs { + return DefaultSingleChainTwoVerifiersSystemIDs{ + DefaultSingleChainMultiNodeSystemIDs: NewDefaultSingleChainMultiNodeSystemIDs(l1ID, l2ID), + L2CLC: stack.NewL2CLNodeID("c", l2ID), + L2ELC: stack.NewL2ELNodeID("c", l2ID), + } +} + +func DefaultSingleChainTwoVerifiersSystem(dest *DefaultSingleChainTwoVerifiersSystemIDs) stack.Option[*Orchestrator] { + ids := NewDefaultSingleChainTwoVerifiersSystemIDs(DefaultL1ID, DefaultL2AID) + + opt := stack.Combine[*Orchestrator]() + opt.Add(DefaultSingleChainMultiNodeSystem(&dest.DefaultSingleChainMultiNodeSystemIDs)) + + opt.Add(WithL2ELNode(ids.L2ELC)) + // Specific options are applied after global options + // this means unsafeOnly is always disabled for the second verifier + opt.Add(WithL2CLNode(ids.L2CLC, ids.L1CL, ids.L1EL, ids.L2ELC, L2CLVerifierDisableUnsafeOnly())) + + opt.Add(WithL2CLP2PConnection(ids.L2CL, ids.L2CLC)) + opt.Add(WithL2ELP2PConnection(ids.L2EL, ids.L2ELC)) + opt.Add(WithL2CLP2PConnection(ids.L2CLB, ids.L2CLC)) + opt.Add(WithL2ELP2PConnection(ids.L2ELB, ids.L2ELC)) + + opt.Add(stack.Finally(func(orch *Orchestrator) { + *dest = ids + })) + return opt +} diff --git a/op-devstack/sysgo/test_sequencer.go b/op-devstack/sysgo/test_sequencer.go index 22f9cf011cc..9937dc8cb56 100644 --- a/op-devstack/sysgo/test_sequencer.go +++ b/op-devstack/sysgo/test_sequencer.go @@ -210,6 +210,8 @@ func WithTestSequencer(testSequencerID stack.TestSequencerID, l1CLID stack.L1CLN }, } + logger.Info("Configuring test sequencer", "l1EL", l1EL.UserRPC(), "l2EL", l2EL.UserRPC(), "l2CL", l2CL.UserRPC()) + jobs := work.NewJobRegistry() ensemble, err := v.Start(context.Background(), &work.StartOpts{ Log: logger, diff --git a/op-devstack/sysgo/util.go b/op-devstack/sysgo/util.go index 33ac3aa600f..68fb9d0eedd 100644 --- a/op-devstack/sysgo/util.go +++ b/op-devstack/sysgo/util.go @@ -1,11 +1,19 @@ package sysgo import ( + "context" "errors" "fmt" "net" + "net/url" "os" + "strconv" "sync" + "time" + + "github.com/coder/websocket" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/stretchr/testify/assert" ) // getEnvVarOrDefault returns the value of the provided env var or the provided default value if unset. @@ -28,8 +36,6 @@ func propagateEnvVarOrDefault(envVarName string, defaultValue string) string { } } -// NB: arbitrary start port with a low probability of conflict -var availableLocalPortStart = 20_000 var availableLocalPortMutex sync.Mutex // getAvailableLocalPort searches for and returns a currently unused local port. @@ -38,15 +44,49 @@ func getAvailableLocalPort() (string, error) { availableLocalPortMutex.Lock() defer availableLocalPortMutex.Unlock() - for port := availableLocalPortStart; port < 65_535; port++ { - ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - continue - } - _ = ln.Close() - availableLocalPortStart = port + 1 - return fmt.Sprintf("%d", port), nil + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return "", fmt.Errorf("could not listen on ephemeral port: %w", err) } + defer ln.Close() + + addr, ok := ln.Addr().(*net.TCPAddr) + if !ok { + return "", errors.New("listener did not return a TCP addr") + } + return strconv.Itoa(addr.Port), nil +} - return "", errors.New("could not find open port") +// waitTCPReady parses a URL and waits for its TCP endpoint to become ready using EventuallyWithT. +func waitTCPReady(p devtest.P, rawURL string, timeout time.Duration) { + p.Helper() + u, err := url.Parse(rawURL) + p.Require().NoError(err, "parse URL: %s", rawURL) + p.Require().NotEmpty(u.Host, "URL has no host: %s", rawURL) + waitMsg := fmt.Sprintf("TCP endpoint %s not ready within %v", u.Host, timeout) + p.Require().EventuallyWithT(func(c *assert.CollectT) { + conn, err := net.DialTimeout("tcp", u.Host, 300*time.Millisecond) + if err == nil { + _ = conn.Close() + } + assert.NoError(c, err, "TCP connection to %s should succeed", u.Host) + }, timeout, 100*time.Millisecond, waitMsg) +} + +// waitWSReady attempts an actual WebSocket handshake to confirm readiness using EventuallyWithT. +func waitWSReady(p devtest.P, rawURL string, timeout time.Duration) { + p.Helper() + waitWSMsg := fmt.Sprintf("WebSocket endpoint %s not ready within %v", rawURL, timeout) + p.Require().EventuallyWithT(func(c *assert.CollectT) { + ctx, cancel := context.WithTimeout(context.Background(), 750*time.Millisecond) + conn, resp, err := websocket.Dial(ctx, rawURL, nil) + cancel() + if resp != nil && resp.Body != nil { + _ = resp.Body.Close() + } + if conn != nil { + _ = conn.Close(websocket.StatusNormalClosure, "") + } + assert.NoError(c, err, "WebSocket handshake to %s should succeed", rawURL) + }, timeout, 100*time.Millisecond, waitWSMsg) } diff --git a/op-dispute-mon/mon/extract/caller.go b/op-dispute-mon/mon/extract/caller.go index ea6ee0c3af0..366add6e54a 100644 --- a/op-dispute-mon/mon/extract/caller.go +++ b/op-dispute-mon/mon/extract/caller.go @@ -50,18 +50,18 @@ func (g *GameCallerCreator) CreateContract(ctx context.Context, game gameTypes.G if fdg, ok := g.cache.Get(game.Proxy); ok { return fdg, nil } - switch faultTypes.GameType(game.GameType) { - case faultTypes.CannonGameType, - faultTypes.PermissionedGameType, - faultTypes.CannonKonaGameType, - faultTypes.AsteriscGameType, - faultTypes.AlphabetGameType, - faultTypes.FastGameType, - faultTypes.AsteriscKonaGameType, - faultTypes.SuperCannonGameType, - faultTypes.SuperPermissionedGameType, - faultTypes.SuperCannonKonaGameType, - faultTypes.SuperAsteriscKonaGameType: + switch gameTypes.GameType(game.GameType) { + case gameTypes.CannonGameType, + gameTypes.PermissionedGameType, + gameTypes.CannonKonaGameType, + gameTypes.AsteriscGameType, + gameTypes.AlphabetGameType, + gameTypes.FastGameType, + gameTypes.AsteriscKonaGameType, + gameTypes.SuperCannonGameType, + gameTypes.SuperPermissionedGameType, + gameTypes.SuperCannonKonaGameType, + gameTypes.SuperAsteriscKonaGameType: fdg, err := contracts.NewFaultDisputeGameContract(ctx, g.m, game.Proxy, g.caller) if err != nil { return nil, fmt.Errorf("failed to create fault dispute game contract: %w", err) diff --git a/op-dispute-mon/mon/extract/caller_test.go b/op-dispute-mon/mon/extract/caller_test.go index 585ab609392..119223a5431 100644 --- a/op-dispute-mon/mon/extract/caller_test.go +++ b/op-dispute-mon/mon/extract/caller_test.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum/go-ethereum/common" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/sources/batching" batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" @@ -29,47 +28,47 @@ func TestMetadataCreator_CreateContract(t *testing.T) { }{ { name: "validCannonGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.CannonGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.CannonGameType), Proxy: fdgAddr}, }, { name: "validPermissionedGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.PermissionedGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.PermissionedGameType), Proxy: fdgAddr}, }, { name: "validCannonKonaGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.CannonKonaGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.CannonKonaGameType), Proxy: fdgAddr}, }, { name: "validAsteriscGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.AsteriscGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.AsteriscGameType), Proxy: fdgAddr}, }, { name: "validAlphabetGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.AlphabetGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.AlphabetGameType), Proxy: fdgAddr}, }, { name: "validFastGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.FastGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.FastGameType), Proxy: fdgAddr}, }, { name: "validAsteriscKonaGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.AsteriscKonaGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.AsteriscKonaGameType), Proxy: fdgAddr}, }, { name: "validSuperCannonGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.SuperCannonGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.SuperCannonGameType), Proxy: fdgAddr}, }, { name: "validSuperPermissionedGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.SuperPermissionedGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.SuperPermissionedGameType), Proxy: fdgAddr}, }, { name: "validSuperCannonKonaGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.SuperCannonKonaGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.SuperCannonKonaGameType), Proxy: fdgAddr}, }, { name: "validSuperAsteriscKonaGameType", - game: types.GameMetadata{GameType: uint32(faultTypes.SuperAsteriscKonaGameType), Proxy: fdgAddr}, + game: types.GameMetadata{GameType: uint32(types.SuperAsteriscKonaGameType), Proxy: fdgAddr}, }, { name: "InvalidGameType", @@ -101,10 +100,10 @@ func TestMetadataCreator_CreateContract(t *testing.T) { func setupMetadataLoaderTest(t *testing.T, gameType uint32) (*batching.MultiCaller, *mockCacheMetrics) { fdgAbi := snapshots.LoadFaultDisputeGameABI() - if gameType == uint32(faultTypes.SuperPermissionedGameType) || - gameType == uint32(faultTypes.SuperCannonGameType) || - gameType == uint32(faultTypes.SuperCannonKonaGameType) || - gameType == uint32(faultTypes.SuperAsteriscKonaGameType) { + if gameType == uint32(types.SuperPermissionedGameType) || + gameType == uint32(types.SuperCannonGameType) || + gameType == uint32(types.SuperCannonKonaGameType) || + gameType == uint32(types.SuperAsteriscKonaGameType) { fdgAbi = snapshots.LoadSuperFaultDisputeGameABI() } stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi) diff --git a/op-e2e/e2eutils/challenger/helper.go b/op-e2e/e2eutils/challenger/helper.go index 2b4816bd53e..4863019a197 100644 --- a/op-e2e/e2eutils/challenger/helper.go +++ b/op-e2e/e2eutils/challenger/helper.go @@ -10,6 +10,7 @@ import ( "testing" "time" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" shared "github.com/ethereum-optimism/optimism/op-devstack/shared/challenger" "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" @@ -23,7 +24,6 @@ import ( challenger "github.com/ethereum-optimism/optimism/op-challenger" "github.com/ethereum-optimism/optimism/op-challenger/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/cliapp" @@ -135,33 +135,33 @@ func handleOptError(t *testing.T, opt shared.Option) Option { func WithCannon(t *testing.T, system System) Option { return func(c *config.Config) { handleOptError(t, shared.WithCannonConfig(system.RollupCfgs(), system.L1Genesis(), system.L2Geneses(), system.PrestateVariant()))(c) - handleOptError(t, shared.WithCannonTraceType())(c) + handleOptError(t, shared.WithCannonGameType())(c) } } func WithPermissioned(t *testing.T, system System) Option { return func(c *config.Config) { handleOptError(t, shared.WithCannonConfig(system.RollupCfgs(), system.L1Genesis(), system.L2Geneses(), system.PrestateVariant()))(c) - handleOptError(t, shared.WithPermissionedTraceType())(c) + handleOptError(t, shared.WithPermissionedGameType())(c) } } func WithSuperCannon(t *testing.T, system System) Option { return func(c *config.Config) { handleOptError(t, shared.WithCannonConfig(system.RollupCfgs(), system.L1Genesis(), system.L2Geneses(), system.PrestateVariant()))(c) - handleOptError(t, shared.WithSuperCannonTraceType())(c) + handleOptError(t, shared.WithSuperCannonGameType())(c) } } func WithAlphabet() Option { return func(c *config.Config) { - c.TraceTypes = append(c.TraceTypes, types.TraceTypeAlphabet) + c.GameTypes = append(c.GameTypes, gameTypes.AlphabetGameType) } } func WithFastGames() Option { return func(c *config.Config) { - c.TraceTypes = append(c.TraceTypes, types.TraceTypeFast) + c.GameTypes = append(c.GameTypes, gameTypes.FastGameType) } } diff --git a/op-e2e/e2eutils/disputegame/cannon_helper.go b/op-e2e/e2eutils/disputegame/cannon_helper.go index 99c26aa5354..c214fb6410d 100644 --- a/op-e2e/e2eutils/disputegame/cannon_helper.go +++ b/op-e2e/e2eutils/disputegame/cannon_helper.go @@ -19,6 +19,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" @@ -465,7 +466,7 @@ func (g *CannonHelper) createCannonTraceProvider(ctx context.Context, l2Node str localContext = split.CreateLocalContext(pre, post) dir := filepath.Join(cfg.Datadir, "cannon-trace") subdir := filepath.Join(dir, localContext.Hex()) - return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics.ToTypedVmMetrics(types.TraceTypeCannon.String()), cfg, localInputs, subdir, g.splitGame.MaxDepth(ctx)-splitDepth-1), nil + return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics.ToTypedVmMetrics(gameTypes.CannonGameType.String()), cfg, localInputs, subdir, g.splitGame.MaxDepth(ctx)-splitDepth-1), nil }) claims, err := g.splitGame.Game.GetAllClaims(ctx, rpcblock.Latest) diff --git a/op-e2e/e2eutils/disputegame/helper.go b/op-e2e/e2eutils/disputegame/helper.go index 33f8050750d..e5fb8c6ca3c 100644 --- a/op-e2e/e2eutils/disputegame/helper.go +++ b/op-e2e/e2eutils/disputegame/helper.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" - challengerTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" shared "github.com/ethereum-optimism/optimism/op-devstack/shared/challenger" "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" @@ -153,7 +153,7 @@ func (h *FactoryHelper) PreimageHelper(ctx context.Context) *preimage.Helper { caller := batching.NewMultiCaller(h.Client.Client(), batching.DefaultBatchSize) dgf, err := contracts.NewDisputeGameFactoryContract(ctx, metrics.NoopContractMetrics, h.FactoryAddr, caller) h.Require.NoError(err) - vm, err := dgf.GetGameVm(ctx, challengerTypes.GameType(cannonGameType)) + vm, err := dgf.GetGameVm(ctx, gameTypes.GameType(cannonGameType)) h.Require.NoError(err) oracle, err := vm.Oracle(ctx) h.Require.NoError(err) diff --git a/op-e2e/e2eutils/disputegame/super_cannon_helper.go b/op-e2e/e2eutils/disputegame/super_cannon_helper.go index 0a0967dfac9..e441a938a18 100644 --- a/op-e2e/e2eutils/disputegame/super_cannon_helper.go +++ b/op-e2e/e2eutils/disputegame/super_cannon_helper.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" @@ -169,7 +170,7 @@ func (g *SuperCannonGameHelper) createSuperCannonTraceProvider(ctx context.Conte localContext = split.CreateLocalContext(pre, post) dir := filepath.Join(cfg.Datadir, "super-cannon-trace") subdir := filepath.Join(dir, localContext.Hex()) - return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics.ToTypedVmMetrics(types.TraceTypeCannon.String()), cfg, localInputs, subdir, g.splitGame.MaxDepth(ctx)-splitDepth-1), nil + return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics.ToTypedVmMetrics(gameTypes.SuperCannonGameType.String()), cfg, localInputs, subdir, g.splitGame.MaxDepth(ctx)-splitDepth-1), nil }) claims, err := g.splitGame.Game.GetAllClaims(ctx, rpcblock.Latest) diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index 17f19f6dcbf..3c19a379a91 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -16,7 +16,7 @@ import ( "testing" "time" - faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" @@ -926,9 +926,9 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, } // L2Output Submitter - respectedGameType := faultTypes.PermissionedGameType + respectedGameType := gameTypes.PermissionedGameType if cfg.AllocType == config.AllocTypeFastGame { - respectedGameType = faultTypes.FastGameType + respectedGameType = gameTypes.FastGameType } proposerCLIConfig := &l2os.CLIConfig{ L1EthRpc: sys.EthInstances[RoleL1].UserRPC().RPC(), diff --git a/op-node/node/node.go b/op-node/node/node.go index dd8c42cd18b..c81085a4268 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -992,3 +992,14 @@ func (n *OpNode) getP2PNodeIfEnabled() *p2p.NodeP2P { defer n.p2pMu.Unlock() return n.p2pNode } + +func (n *OpNode) SafeDB() SafeDBReader { + return n.safeDB +} + +func (n *OpNode) SyncStatus() *eth.SyncStatus { + if n.l2Driver == nil || n.l2Driver.StatusTracker == nil { + return ð.SyncStatus{} + } + return n.l2Driver.StatusTracker.SyncStatus() +} diff --git a/op-node/rollup/driver/driver.go b/op-node/rollup/driver/driver.go index 44a6c4e3227..63f552080ec 100644 --- a/op-node/rollup/driver/driver.go +++ b/op-node/rollup/driver/driver.go @@ -269,8 +269,23 @@ func (s *Driver) eventLoop() { syncCheckInterval := time.Duration(s.SyncDeriver.Config.BlockTime) * time.Second * 2 altSyncTicker := time.NewTicker(syncCheckInterval) defer altSyncTicker.Stop() + lastUnsafeL2 := s.SyncDeriver.Engine.UnsafeL2Head() + unsafeOnly := s.SyncDeriver.SyncCfg.UnsafeOnly + + resetAltSync := func(newHead eth.L2BlockRef, derivationReady bool) { + s.log.Debug( + "altSyncTicker reset", + "head", newHead, + "lastUnsafeL2", lastUnsafeL2, + "derivationReady", derivationReady, + "unsafeOnly", unsafeOnly, + ) + lastUnsafeL2 = newHead + altSyncTicker.Reset(syncCheckInterval) + } + for { if s.driverCtx.Err() != nil { // don't try to schedule/handle more work when we are closing. return @@ -278,12 +293,15 @@ func (s *Driver) eventLoop() { planSequencerAction() - // If the engine is not ready, or if the L2 head is actively changing, then reset the alt-sync: - // there is no need to request L2 blocks when we are syncing already. - if head := s.SyncDeriver.Engine.UnsafeL2Head(); head != lastUnsafeL2 || !s.SyncDeriver.Derivation.DerivationReady() { - s.log.Debug("altSyncTicker reset", "head", head, "lastUnsafeL2", lastUnsafeL2, "derivationReady", s.SyncDeriver.Derivation.DerivationReady()) - lastUnsafeL2 = head - altSyncTicker.Reset(syncCheckInterval) + head := s.SyncDeriver.Engine.UnsafeL2Head() + derivationReady := s.SyncDeriver.Derivation.DerivationReady() + + if lastUnsafeL2 != head { + // Unsafe head changed: reset alt-sync to avoid redundant L2 requests while syncing. + resetAltSync(head, derivationReady) + } else if !unsafeOnly && !derivationReady { + // Derivation enabled but not yet ready: reset alt-sync while it catches up. + resetAltSync(head, derivationReady) } select { diff --git a/op-node/rollup/driver/sync_deriver.go b/op-node/rollup/driver/sync_deriver.go index a2b1561bfd1..bd5f29b0044 100644 --- a/op-node/rollup/driver/sync_deriver.go +++ b/op-node/rollup/driver/sync_deriver.go @@ -230,13 +230,21 @@ func (s *SyncDeriver) SyncStep() { s.Engine.TryUpdateEngine(s.Ctx) if s.Engine.IsEngineInitialELSyncing() { - // The pipeline cannot move forwards if doing EL sync. - s.Log.Debug("Rollup driver is backing off because execution engine is syncing.", + // The pipeline cannot move forwards if doing initial EL sync. + s.Log.Debug("Rollup driver is backing off because execution engine is performing initial EL sync.", "unsafe_head", s.Engine.UnsafeL2Head()) s.StepDeriver.ResetStepBackoff(s.Ctx) return } + if s.SyncCfg.UnsafeOnly { + if s.SyncCfg.NeedInitialResetEngine { + // May need a single reset to trigger sequencer block building + s.Engine.TryInitialResetEngineForSequencer(s.Ctx) + } + return + } + // Any now processed forkchoice updates will trigger CL-sync payload processing, if any payload is queued up. // Since we don't force attributes to be processed at this point, diff --git a/op-node/rollup/engine/engine_controller.go b/op-node/rollup/engine/engine_controller.go index 306c40c2cc4..f97ba854f11 100644 --- a/op-node/rollup/engine/engine_controller.go +++ b/op-node/rollup/engine/engine_controller.go @@ -184,7 +184,6 @@ func NewEngineController(ctx context.Context, engine ExecEngine, log log.Logger, unsafePayloads: NewPayloadsQueue(log, maxUnsafePayloadsMemory, payloadMemSize), } } - func (e *EngineController) UnsafeL2Head() eth.L2BlockRef { return e.unsafeHead } @@ -867,11 +866,11 @@ func (e *EngineController) SetOriginSelectorResetter(resetter OriginSelectorForc func (e *EngineController) ForceReset(ctx context.Context, localUnsafe, crossUnsafe, localSafe, crossSafe, finalized eth.L2BlockRef) { e.mu.Lock() defer e.mu.Unlock() - e.forceReset(ctx, localUnsafe, crossUnsafe, localSafe, crossSafe, finalized) + e.forceReset(ctx, localUnsafe, crossUnsafe, localSafe, crossSafe, finalized, false) } // forceReset performs a forced reset to the specified block references -func (e *EngineController) forceReset(ctx context.Context, localUnsafe, crossUnsafe, localSafe, crossSafe, finalized eth.L2BlockRef) { +func (e *EngineController) forceReset(ctx context.Context, localUnsafe, crossUnsafe, localSafe, crossSafe, finalized eth.L2BlockRef, signalOnlySeq bool) { // Reset other components before resetting the engine if e.attributesResetter != nil { e.attributesResetter.ForceReset(ctx, localUnsafe, crossUnsafe, localSafe, crossSafe, finalized) @@ -890,8 +889,19 @@ func (e *EngineController) forceReset(ctx context.Context, localUnsafe, crossUns e.emitter.Emit(ctx, derive.ConfirmPipelineResetEvent{}) } - // Time to apply the changes to the underlying engine - e.tryUpdateEngine(ctx) + if signalOnlySeq { + // Intentionally not propagating ForkchoiceUpdateEvent to other event Deriver avoiding side effects. + // If we do tryUpdateEngine instead, it will eventually emit ForkchoiceUpdateEvent, causing block building + // to never begin. Use fine grained ForkchoiceUpdateInitEvent to only propagate info to the sequencer component. + e.emitter.Emit(ctx, ForkchoiceUpdateInitEvent{ + UnsafeL2Head: e.unsafeHead, + SafeL2Head: e.safeHead, + FinalizedL2Head: e.finalizedHead, + }) + } else { + // Time to apply the changes to the underlying engine + e.tryUpdateEngine(ctx) + } v := EngineResetConfirmedEvent{ LocalUnsafe: e.unsafeHead, @@ -1035,7 +1045,28 @@ func (e *EngineController) onResetEngineRequest(ctx context.Context) { }) return } - e.forceReset(ctx, result.Unsafe, result.Unsafe, result.Safe, result.Safe, result.Finalized) + e.forceReset(ctx, result.Unsafe, result.Unsafe, result.Safe, result.Safe, result.Finalized, false) +} + +// TryInitialResetEngineForSequencer resets engine controller with the info from FindL2Heads and only propagates +// ForkchoiceUpdateEvent info to the sequencer to trigger sequencer block building, but not propagating +// ForkchoiceUpdateEvent to other event Deriver avoiding side effects +func (e *EngineController) TryInitialResetEngineForSequencer(ctx context.Context) { + e.mu.Lock() + defer e.mu.Unlock() + if e.unsafeHead != (eth.L2BlockRef{}) { + // Engine already initialized unsafe head. Early return + return + } + e.log.Info("EngineController Unsafe head was not initialized at the start of the reset") + result, err := sync.FindL2Heads(e.ctx, e.rollupCfg, e.l1, e.engine, e.log, e.syncCfg) + if err != nil { + e.log.Warn("Failed to find L2 Heads to start from while initial reset: %w", err) + // Do not emit ResetEvent because it will end propagating ForkchoiceUpdateEvent + // Because the engine controller failed to initialize, the next SyncStep will retry this method + return + } + e.forceReset(ctx, result.Unsafe, result.Unsafe, result.Safe, result.Safe, result.Finalized, true) } var ErrEngineSyncing = errors.New("engine is syncing") diff --git a/op-node/rollup/engine/events.go b/op-node/rollup/engine/events.go index 5b4711cbef0..175df5b61bf 100644 --- a/op-node/rollup/engine/events.go +++ b/op-node/rollup/engine/events.go @@ -27,6 +27,15 @@ func (ev ForkchoiceUpdateEvent) String() string { return "forkchoice-update" } +// ForkchoiceUpdateInitEvent is only for the sequencer to be signaled during initialization +type ForkchoiceUpdateInitEvent struct { + UnsafeL2Head, SafeL2Head, FinalizedL2Head eth.L2BlockRef +} + +func (ev ForkchoiceUpdateInitEvent) String() string { + return "forkchoice-update-init" +} + // UnsafeUpdateEvent signals that the given block is now considered safe. // This is pre-forkchoice update; the change may not be reflected yet in the EL. type UnsafeUpdateEvent struct { diff --git a/op-node/rollup/engine/payload_success.go b/op-node/rollup/engine/payload_success.go index ad270fb3648..8fe64e592b0 100644 --- a/op-node/rollup/engine/payload_success.go +++ b/op-node/rollup/engine/payload_success.go @@ -29,7 +29,7 @@ func (e *EngineController) onPayloadSuccess(ctx context.Context, ev PayloadSucce e.log.Warn("Successfully built replacement block, resetting chain to continue now", "replacement", ev.Ref) // Change the engine state to make the replacement block the cross-safe head of the chain, // And continue syncing from there. - e.forceReset(ctx, ev.Ref, ev.Ref, ev.Ref, ev.Ref, e.Finalized()) + e.forceReset(ctx, ev.Ref, ev.Ref, ev.Ref, ev.Ref, e.Finalized(), false) e.emitter.Emit(ctx, InteropReplacedBlockEvent{ Envelope: ev.Envelope, Ref: ev.Ref.BlockRef(), diff --git a/op-node/rollup/sequencing/sequencer.go b/op-node/rollup/sequencing/sequencer.go index c536c5bc8b2..9e2b348c93b 100644 --- a/op-node/rollup/sequencing/sequencer.go +++ b/op-node/rollup/sequencing/sequencer.go @@ -198,6 +198,8 @@ func (d *Sequencer) OnEvent(ctx context.Context, ev event.Event) bool { d.onEngineResetConfirmedEvent(x) case engine.ForkchoiceUpdateEvent: d.onForkchoiceUpdate(x) + case engine.ForkchoiceUpdateInitEvent: + d.onForkchoiceUpdate(engine.ForkchoiceUpdateEvent(x)) default: return false } diff --git a/op-node/rollup/sync/config.go b/op-node/rollup/sync/config.go index 636d0c3f804..79b46a223a0 100644 --- a/op-node/rollup/sync/config.go +++ b/op-node/rollup/sync/config.go @@ -77,8 +77,5 @@ type Config struct { UnsafeOnly bool `json:"unsafe_only"` L2FollowSourceEndpoint string `json:"l2_follow_source_endpoint"` -} - -func (c *Config) L2FollowSourceEnabled() bool { - return c.L2FollowSourceEndpoint != "" + NeedInitialResetEngine bool `json:"need_initial_reset_engine"` } diff --git a/op-node/service.go b/op-node/service.go index 48132dcb642..d590750628e 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -380,6 +380,7 @@ func NewSyncConfig(ctx cliiface.Context, log log.Logger) (*sync.Config, error) { SupportsPostFinalizationELSync: engineKind.SupportsPostFinalizationELSync(), UnsafeOnly: unsafeOnly, L2FollowSourceEndpoint: l2FollowSourceEndpoint, + NeedInitialResetEngine: isSequencer && unsafeOnly, } if ctx.Bool(flags.L2EngineSyncEnabled.Name) { cfg.SyncMode = sync.ELSync diff --git a/op-proposer/contracts/disputegamefactory_test.go b/op-proposer/contracts/disputegamefactory_test.go index b3158369591..84c7dae1a98 100644 --- a/op-proposer/contracts/disputegamefactory_test.go +++ b/op-proposer/contracts/disputegamefactory_test.go @@ -204,13 +204,13 @@ func TestHasProposedSince(t *testing.T) { func TestProposalTx(t *testing.T) { stubRpc, factory := setupDisputeGameFactoryTest(t) - traceType := uint32(123) + gameType := uint32(123) outputRoot := common.Hash{0x01} l2BlockNum := common.BigToHash(big.NewInt(456)).Bytes() bond := big.NewInt(49284294829) - stubRpc.SetResponse(factoryAddr, methodInitBonds, rpcblock.Latest, []interface{}{traceType}, []interface{}{bond}) - stubRpc.SetResponse(factoryAddr, methodCreateGame, rpcblock.Latest, []interface{}{traceType, outputRoot, l2BlockNum}, nil) - tx, err := factory.ProposalTx(context.Background(), traceType, outputRoot, uint64(456)) + stubRpc.SetResponse(factoryAddr, methodInitBonds, rpcblock.Latest, []interface{}{gameType}, []interface{}{bond}) + stubRpc.SetResponse(factoryAddr, methodCreateGame, rpcblock.Latest, []interface{}{gameType, outputRoot, l2BlockNum}, nil) + tx, err := factory.ProposalTx(context.Background(), gameType, outputRoot, uint64(456)) require.NoError(t, err) stubRpc.VerifyTxCandidate(tx) require.NotNil(t, tx.Value) diff --git a/op-service/enum/enum.go b/op-service/enum/enum.go index 9b348c806f1..1bf2f21f371 100644 --- a/op-service/enum/enum.go +++ b/op-service/enum/enum.go @@ -1,6 +1,7 @@ package enum import ( + "fmt" "strings" ) @@ -16,3 +17,14 @@ func EnumString[T ~string](values []T) string { } return out.String() } + +func EnumStringer[T fmt.Stringer](values []T) string { + var out strings.Builder + for i, v := range values { + out.WriteString(v.String()) + if i+1 < len(values) { + out.WriteString(", ") + } + } + return out.String() +} diff --git a/op-service/logpipe/line_buffer.go b/op-service/logpipe/line_buffer.go new file mode 100644 index 00000000000..0a6395822e9 --- /dev/null +++ b/op-service/logpipe/line_buffer.go @@ -0,0 +1,62 @@ +package logpipe + +import ( + "io" + "sync" +) + +// LogCallback is the function signature for processing a complete log line. +type LogCallback func(line []byte) + +// LineBuffer is an io.WriteCloser that buffers data across writes and emits complete lines +// to the provided callback, preserving log entries that may span multiple writes. +// Any trailing partial line is flushed when Close is called. +type LineBuffer struct { + mu sync.Mutex + buf []byte + callback LogCallback +} + +// NewLineBuffer creates a new LineBuffer that calls the given callback for each complete line. +func NewLineBuffer(callback LogCallback) *LineBuffer { + return &LineBuffer{callback: callback} +} + +// Write appends data, emitting full lines to the callback. +// Empty lines are ignored. +func (lp *LineBuffer) Write(p []byte) (int, error) { + lp.mu.Lock() + defer lp.mu.Unlock() + + lp.buf = append(lp.buf, p...) + + start := 0 + for i := 0; i < len(lp.buf); i++ { + if lp.buf[i] == '\n' { + line := lp.buf[start:i] + if len(line) > 0 { + lp.callback(line) + } + start = i + 1 + } + } + // Keep any partial trailing line + if start > 0 { + lp.buf = append([]byte(nil), lp.buf[start:]...) + } + return len(p), nil +} + +// Close flushes any buffered partial line. +func (lp *LineBuffer) Close() error { + lp.mu.Lock() + defer lp.mu.Unlock() + + if len(lp.buf) > 0 { + lp.callback(lp.buf) + lp.buf = nil + } + return nil +} + +var _ io.WriteCloser = (*LineBuffer)(nil) diff --git a/op-service/logpipe/line_buffer_test.go b/op-service/logpipe/line_buffer_test.go new file mode 100644 index 00000000000..b28dad1f90d --- /dev/null +++ b/op-service/logpipe/line_buffer_test.go @@ -0,0 +1,45 @@ +package logpipe + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLogProcessorSplitsAcrossWrites(t *testing.T) { + var lines [][]byte + proc := NewLineBuffer(func(line []byte) { + // Copy to avoid aliasing the buffer + lines = append(lines, append([]byte(nil), line...)) + }) + + _, err := proc.Write([]byte("hello ")) + require.NoError(t, err) + _, err = proc.Write([]byte("world\nsecond")) + require.NoError(t, err) + _, err = proc.Write([]byte(" line\nthird line\n")) + require.NoError(t, err) + + require.NoError(t, proc.Close()) + + require.Equal(t, [][]byte{ + []byte("hello world"), + []byte("second line"), + []byte("third line"), + }, lines) +} + +func TestLogProcessorFlushesTrailingPartialLine(t *testing.T) { + var lines [][]byte + proc := NewLineBuffer(func(line []byte) { + lines = append(lines, append([]byte(nil), line...)) + }) + + _, err := proc.Write([]byte("partial line without newline")) + require.NoError(t, err) + require.NoError(t, proc.Close()) + + require.Equal(t, [][]byte{ + []byte("partial line without newline"), + }, lines) +} diff --git a/op-service/logpipe/pipe.go b/op-service/logpipe/pipe.go index 1ee6eeb2520..abc22c09ec7 100644 --- a/op-service/logpipe/pipe.go +++ b/op-service/logpipe/pipe.go @@ -1,7 +1,6 @@ package logpipe import ( - "bufio" "bytes" "encoding/json" "log/slog" @@ -78,22 +77,6 @@ type LogEntry interface { FieldValue(key string) any } -type LogProcessor func(line []byte) - -func (lo LogProcessor) Write(data []byte) (int, error) { - startingLength := len(data) - buf := bytes.NewBuffer(data) - scanner := bufio.NewScanner(buf) - for scanner.Scan() { - lineBytes := scanner.Bytes() - if len(lineBytes) == 0 { - continue // Skip empty lines - } - lo(lineBytes) - } - return startingLength - buf.Len(), scanner.Err() -} - type LogParser func(line []byte) LogEntry func ToLogger(logger log.Logger) func(e LogEntry) { diff --git a/op-service/logpipe/pipe_test.go b/op-service/logpipe/pipe_test.go index df6b7ed9c5a..475c7971d10 100644 --- a/op-service/logpipe/pipe_test.go +++ b/op-service/logpipe/pipe_test.go @@ -15,14 +15,14 @@ import ( func TestWriteToLogProcessor(t *testing.T) { logger, capt := testlog.CaptureLogger(t, log.LevelTrace) - logProc := LogProcessor(func(line []byte) { + proc := NewLineBuffer(func(line []byte) { ToLogger(logger)(ParseRustStructuredLogs(line)) }) - _, err := io.Copy(logProc, strings.NewReader(`{"level": "DEBUG", "fields": {"message": "hello", "foo": 1}}`+"\n")) + _, err := io.Copy(proc, strings.NewReader(`{"level": "DEBUG", "fields": {"message": "hello", "foo": 1}}`+"\n")) require.NoError(t, err) - _, err = io.Copy(logProc, strings.NewReader(`test invalid JSON`+"\n")) + _, err = io.Copy(proc, strings.NewReader(`test invalid JSON`+"\n")) require.NoError(t, err) - _, err = io.Copy(logProc, strings.NewReader(`{"fields": {"message": "world", "bar": "sunny"}, "level": "INFO"}`+"\n")) + _, err = io.Copy(proc, strings.NewReader(`{"fields": {"message": "world", "bar": "sunny"}, "level": "INFO"}`+"\n")) require.NoError(t, err) entry1 := capt.FindLog( diff --git a/op-service/rpc/handler.go b/op-service/rpc/handler.go index 4b8e3caa4b5..ef455d216c2 100644 --- a/op-service/rpc/handler.go +++ b/op-service/rpc/handler.go @@ -93,6 +93,18 @@ func (b *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { b.outer.ServeHTTP(writer, request) } +// DialInProc creates a new in-process RPC client connected to the root RPC server. +// Useful for components that need to call RPC methods on the embedded server without going over the network. +func (b *Handler) DialInProc() (*rpc.Client, error) { + b.rpcRoutesLock.Lock() + defer b.rpcRoutesLock.Unlock() + server, ok := b.rpcRoutes[rootRoute] + if !ok || server == nil { + return nil, fmt.Errorf("root RPC server not available") + } + return rpc.DialInProc(server), nil +} + // AddAPI adds a backend to the given RPC namespace, on the default RPC route of the server. func (b *Handler) AddAPI(api rpc.API) error { return b.AddAPIToRPC(rootRoute, api) diff --git a/op-supernode/README.md b/op-supernode/README.md index 8479ed013ad..7ecfafebdf9 100644 --- a/op-supernode/README.md +++ b/op-supernode/README.md @@ -75,6 +75,8 @@ Components which expose Start/Stop are given a goroutine to work during `op-supe - `Heartbeat` - RPC: `heartbeat_check` produces a random-hex sign of life when called. - Runtime: emits a simple heartbeat message to the logs to show liveness. +- `SuperRoot` + - RPC: `superroot_atTimestamp` produces a SuperRoot from Verified L2 blocks, and includes sync/derivation information for Proofs. ### Quickstart Build: diff --git a/op-supernode/supernode/activity/superroot/superroot.go b/op-supernode/supernode/activity/superroot/superroot.go new file mode 100644 index 00000000000..31d4a9c29fe --- /dev/null +++ b/op-supernode/supernode/activity/superroot/superroot.go @@ -0,0 +1,148 @@ +package superroot + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" + cc "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container" + "github.com/ethereum/go-ethereum" + gethlog "github.com/ethereum/go-ethereum/log" +) + +// Superroot satisfies the RPC Activity interface +// it provides the superroot at a given timestamp for all chains +// along with the current L1s and the verified and optimistic L1:L2 pairs +type Superroot struct { + log gethlog.Logger + chains map[eth.ChainID]cc.ChainContainer +} + +func New(log gethlog.Logger, chains map[eth.ChainID]cc.ChainContainer) *Superroot { + return &Superroot{ + log: log, + chains: chains, + } +} + +func (s *Superroot) ActivityName() string { return "superroot" } + +func (s *Superroot) RPCNamespace() string { return "superroot" } +func (s *Superroot) RPCService() interface{} { return &superrootAPI{s: s} } + +type superrootAPI struct{ s *Superroot } + +// OutputWithSource is the full Output and its source L1 block +type OutputWithSource struct { + Output *eth.OutputResponse + SourceL1 eth.BlockID +} + +// L2WithRequiredL1 is a verified L2 block and the minimum L1 block at which the verification is possible +type L2WithRequiredL1 struct { + L2 eth.BlockID + MinRequiredL1 eth.BlockID +} + +// atTimestampResponse is the response superroot_atTimestamp +// it contains: +// - CurrentL1Derived: the current L1 block that each chain has derived up to (without any verification) +// - CurrentL1Verified: the current L1 block that each verifier has processed up to +// - VerifiedAtTimestamp: the L2 blocks which are fully verified at the given timestamp, and the minimum L1 block at which verification is possible +// - OptimisticAtTimestamp: the L2 blocks which would be applied if verification were assumed to be successful, and their L1 sources +// - SuperRoot: the superroot at the given timestamp using verified L2 blocks +type atTimestampResponse struct { + CurrentL1Derived map[eth.ChainID]eth.BlockID + CurrentL1Verified map[string]eth.BlockID + VerifiedAtTimestamp map[eth.ChainID]L2WithRequiredL1 + OptimisticAtTimestamp map[eth.ChainID]OutputWithSource + MinCurrentL1 eth.BlockID + MinVerifiedRequiredL1 eth.BlockID + SuperRoot eth.Bytes32 +} + +// AtTimestamp computes the super-root at the given timestamp, plus additional information about the current L1s, verified L2s, and optimistic L2s +func (api *superrootAPI) AtTimestamp(ctx context.Context, timestamp uint64) (atTimestampResponse, error) { + return api.s.atTimestamp(ctx, timestamp) +} + +func (s *Superroot) atTimestamp(ctx context.Context, timestamp uint64) (atTimestampResponse, error) { + currentL1Derived := map[eth.ChainID]eth.BlockID{} + // there are no Verification Activities yet, so there is no call to make to collect their CurrentL1 + // this will be replaced with a call to the Verification Activities when they are implemented + currentL1Verified := map[string]eth.BlockID{} + verified := map[eth.ChainID]L2WithRequiredL1{} + optimistic := map[eth.ChainID]OutputWithSource{} + minCurrentL1 := eth.BlockID{} + minVerifiedRequiredL1 := eth.BlockID{} + chainOutputs := make([]eth.ChainIDAndOutput, 0, len(s.chains)) + + // get current l1s + // this informs callers that the chains local views have considered at least up to this L1 block + // but does not guarantee verifiers have processed this L1 block yet. This field is likely unhelpful, but I await feedback to confirm + for chainID, chain := range s.chains { + currentL1, err := chain.CurrentL1(ctx) + if err != nil { + s.log.Warn("failed to get current L1", "chain_id", chainID.String(), "err", err) + return atTimestampResponse{}, err + } + currentL1Derived[chainID] = currentL1.ID() + if currentL1.ID().Number < minCurrentL1.Number || minCurrentL1 == (eth.BlockID{}) { + minCurrentL1 = currentL1.ID() + } + } + + // collect verified and optimistic L2 and L1 blocks at the given timestamp + for chainID, chain := range s.chains { + // verifiedAt returns the L2 block which is fully verified at the given timestamp, and the minimum L1 block at which verification is possible + verifiedL2, verifiedL1, err := chain.VerifiedAt(ctx, timestamp) + if err != nil { + s.log.Warn("failed to get verified L1", "chain_id", chainID.String(), "err", err) + return atTimestampResponse{}, fmt.Errorf("%w: %w", ethereum.NotFound, err) + } + verified[chainID] = L2WithRequiredL1{ + L2: verifiedL2, + MinRequiredL1: verifiedL1, + } + if verifiedL1.Number < minVerifiedRequiredL1.Number || minVerifiedRequiredL1 == (eth.BlockID{}) { + minVerifiedRequiredL1 = verifiedL1 + } + // Compute output root at or before timestamp using the verified L2 block number + outRoot, err := chain.OutputRootAtL2BlockNumber(ctx, verifiedL2.Number) + if err != nil { + s.log.Warn("failed to compute output root at L2 block", "chain_id", chainID.String(), "l2_number", verifiedL2.Number, "err", err) + return atTimestampResponse{}, fmt.Errorf("%w: %w", ethereum.NotFound, err) + } + chainOutputs = append(chainOutputs, eth.ChainIDAndOutput{ChainID: chainID, Output: outRoot}) + // Optimistic output is the full output at the optimistic L2 block for the timestamp + optimisticOut, err := chain.OptimisticOutputAtTimestamp(ctx, timestamp) + if err != nil { + s.log.Warn("failed to get optimistic L1", "chain_id", chainID.String(), "err", err) + return atTimestampResponse{}, fmt.Errorf("%w: %w", ethereum.NotFound, err) + } + // Also include the source L1 for context + _, optimisticL1, err := chain.OptimisticAt(ctx, timestamp) + if err != nil { + s.log.Warn("failed to get optimistic source L1", "chain_id", chainID.String(), "err", err) + return atTimestampResponse{}, fmt.Errorf("%w: %w", ethereum.NotFound, err) + } + optimistic[chainID] = OutputWithSource{ + Output: optimisticOut, + SourceL1: optimisticL1, + } + } + + // Build super root from collected outputs + superV1 := eth.NewSuperV1(timestamp, chainOutputs...) + superRoot := eth.SuperRoot(superV1) + + return atTimestampResponse{ + CurrentL1Derived: currentL1Derived, + CurrentL1Verified: currentL1Verified, + VerifiedAtTimestamp: verified, + OptimisticAtTimestamp: optimistic, + MinCurrentL1: minCurrentL1, + MinVerifiedRequiredL1: minVerifiedRequiredL1, + SuperRoot: superRoot, + }, nil +} diff --git a/op-supernode/supernode/activity/superroot/superroot_test.go b/op-supernode/supernode/activity/superroot/superroot_test.go new file mode 100644 index 00000000000..f85acd8572e --- /dev/null +++ b/op-supernode/supernode/activity/superroot/superroot_test.go @@ -0,0 +1,215 @@ +package superroot + +import ( + "context" + "fmt" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + cc "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container" + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +type mockCC struct { + verL2 eth.BlockID + verL1 eth.BlockID + optL2 eth.BlockID + optL1 eth.BlockID + output eth.Bytes32 + currentL1 eth.BlockRef + + currentL1Err error + verifiedErr error + outputErr error + optimisticErr error +} + +func (m *mockCC) Start(ctx context.Context) error { return nil } +func (m *mockCC) Stop(ctx context.Context) error { return nil } +func (m *mockCC) Pause(ctx context.Context) error { return nil } +func (m *mockCC) Resume(ctx context.Context) error { return nil } + +func (m *mockCC) SafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) { + return eth.L2BlockRef{}, nil +} +func (m *mockCC) SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (eth.BlockID, eth.BlockID, error) { + return eth.BlockID{}, eth.BlockID{}, nil +} +func (m *mockCC) L1AtSafeHead(ctx context.Context, l2 eth.BlockID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} +func (m *mockCC) CurrentL1(ctx context.Context) (eth.BlockRef, error) { + if m.currentL1Err != nil { + return eth.BlockRef{}, m.currentL1Err + } + return m.currentL1, nil +} +func (m *mockCC) VerifiedAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + if m.verifiedErr != nil { + return eth.BlockID{}, eth.BlockID{}, m.verifiedErr + } + return m.verL2, m.verL1, nil +} +func (m *mockCC) OptimisticAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + if m.optimisticErr != nil { + return eth.BlockID{}, eth.BlockID{}, m.optimisticErr + } + return m.optL2, m.optL1, nil +} +func (m *mockCC) OutputRootAtL2BlockNumber(ctx context.Context, l2BlockNum uint64) (eth.Bytes32, error) { + if m.outputErr != nil { + return eth.Bytes32{}, m.outputErr + } + return m.output, nil +} +func (m *mockCC) OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputResponse, error) { + if m.optimisticErr != nil { + return nil, m.optimisticErr + } + // Return minimal output response; tests only assert presence/count + return ð.OutputResponse{}, nil +} + +var _ cc.ChainContainer = (*mockCC)(nil) + +func TestSuperroot_AtTimestamp_Succeeds(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + verL2: eth.BlockID{Number: 100}, + verL1: eth.BlockID{Number: 1000}, + optL2: eth.BlockID{Number: 100}, + optL1: eth.BlockID{Number: 1000}, + output: eth.Bytes32{}, + currentL1: eth.BlockRef{Number: 2000}, + }, + eth.ChainIDFromUInt64(420): &mockCC{ + verL2: eth.BlockID{Number: 200}, + verL1: eth.BlockID{Number: 1100}, + optL2: eth.BlockID{Number: 200}, + optL1: eth.BlockID{Number: 1100}, + output: eth.Bytes32{}, + currentL1: eth.BlockRef{Number: 2100}, + }, + } + s := New(gethlog.New(), chains) + api := &superrootAPI{s: s} + out, err := api.AtTimestamp(context.Background(), 123) + require.NoError(t, err) + require.Len(t, out.CurrentL1Derived, 2) + require.Len(t, out.VerifiedAtTimestamp, 2) + require.Len(t, out.OptimisticAtTimestamp, 2) + // min values + require.Equal(t, uint64(2000), out.MinCurrentL1.Number) + require.Equal(t, uint64(1000), out.MinVerifiedRequiredL1.Number) + // With zero outputs, the superroot will be deterministic, just ensure it's set + _ = out.SuperRoot +} + +func TestSuperroot_AtTimestamp_ComputesSuperRoot(t *testing.T) { + t.Parallel() + out1 := eth.Bytes32{1} + out2 := eth.Bytes32{2} + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + verL2: eth.BlockID{Number: 100}, + verL1: eth.BlockID{Number: 1000}, + optL2: eth.BlockID{Number: 100}, + optL1: eth.BlockID{Number: 1000}, + output: out1, + currentL1: eth.BlockRef{Number: 2000}, + }, + eth.ChainIDFromUInt64(420): &mockCC{ + verL2: eth.BlockID{Number: 200}, + verL1: eth.BlockID{Number: 1100}, + optL2: eth.BlockID{Number: 200}, + optL1: eth.BlockID{Number: 1100}, + output: out2, + currentL1: eth.BlockRef{Number: 2100}, + }, + } + ts := uint64(123) + s := New(gethlog.New(), chains) + api := &superrootAPI{s: s} + resp, err := api.AtTimestamp(context.Background(), ts) + require.NoError(t, err) + + // Compute expected super root + chainOutputs := []eth.ChainIDAndOutput{ + {ChainID: eth.ChainIDFromUInt64(10), Output: out1}, + {ChainID: eth.ChainIDFromUInt64(420), Output: out2}, + } + expected := eth.SuperRoot(eth.NewSuperV1(ts, chainOutputs...)) + require.Equal(t, expected, resp.SuperRoot) +} + +func TestSuperroot_AtTimestamp_ErrorOnCurrentL1(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + currentL1Err: assertErr(), + }, + } + s := New(gethlog.New(), chains) + api := &superrootAPI{s: s} + _, err := api.AtTimestamp(context.Background(), 123) + require.Error(t, err) +} + +func TestSuperroot_AtTimestamp_ErrorOnVerifiedAt(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + verifiedErr: assertErr(), + }, + } + s := New(gethlog.New(), chains) + api := &superrootAPI{s: s} + _, err := api.AtTimestamp(context.Background(), 123) + require.Error(t, err) +} + +func TestSuperroot_AtTimestamp_ErrorOnOutputRoot(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + verL2: eth.BlockID{Number: 100}, + outputErr: assertErr(), + }, + } + s := New(gethlog.New(), chains) + api := &superrootAPI{s: s} + _, err := api.AtTimestamp(context.Background(), 123) + require.Error(t, err) +} + +func TestSuperroot_AtTimestamp_ErrorOnOptimisticAt(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + verL2: eth.BlockID{Number: 100}, + output: eth.Bytes32{1}, + optimisticErr: assertErr(), + }, + } + s := New(gethlog.New(), chains) + api := &superrootAPI{s: s} + _, err := api.AtTimestamp(context.Background(), 123) + require.Error(t, err) +} + +func TestSuperroot_AtTimestamp_EmptyChains(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{} + s := New(gethlog.New(), chains) + api := &superrootAPI{s: s} + out, err := api.AtTimestamp(context.Background(), 123) + require.NoError(t, err) + require.Len(t, out.CurrentL1Derived, 0) + require.Len(t, out.VerifiedAtTimestamp, 0) + require.Len(t, out.OptimisticAtTimestamp, 0) +} + +// assertErr returns a generic error instance used to signal mock failures. +func assertErr() error { return fmt.Errorf("mock error") } diff --git a/op-supernode/supernode/chain_container/chain_container.go b/op-supernode/supernode/chain_container/chain_container.go index 24aab37b4b7..0e262ee94db 100644 --- a/op-supernode/supernode/chain_container/chain_container.go +++ b/op-supernode/supernode/chain_container/chain_container.go @@ -2,6 +2,7 @@ package chain_container import ( "context" + "fmt" "net/http" "path/filepath" "sync/atomic" @@ -9,9 +10,12 @@ import ( opnodecfg "github.com/ethereum-optimism/optimism/op-node/config" rollupNode "github.com/ethereum-optimism/optimism/op-node/node" + "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-supernode/config" + "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container/engine_controller" "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container/virtual_node" gethlog "github.com/ethereum/go-ethereum/log" "github.com/prometheus/client_golang/prometheus" @@ -25,6 +29,17 @@ type ChainContainer interface { Stop(ctx context.Context) error Pause(ctx context.Context) error Resume(ctx context.Context) error + + SafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) + SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (l1 eth.BlockID, l2 eth.BlockID, err error) + // L1AtSafeHead returns the earliest L1 block at which the given L2 block became safe. + L1AtSafeHead(ctx context.Context, l2 eth.BlockID) (eth.BlockID, error) + CurrentL1(ctx context.Context) (eth.BlockRef, error) + VerifiedAt(ctx context.Context, ts uint64) (l2, l1 eth.BlockID, err error) + OptimisticAt(ctx context.Context, ts uint64) (l2, l1 eth.BlockID, err error) + OutputRootAtL2BlockNumber(ctx context.Context, l2BlockNum uint64) (eth.Bytes32, error) + // OptimisticOutputAtTimestamp returns the full Output at the optimistic L2 block for the given timestamp. + OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputResponse, error) } type virtualNodeFactory func(cfg *opnodecfg.Config, log gethlog.Logger, initOverrides *rollupNode.InitializationOverrides, appVersion string) virtual_node.VirtualNode @@ -33,6 +48,7 @@ type simpleChainContainer struct { vn virtual_node.VirtualNode vncfg *opnodecfg.Config cfg config.CLIConfig + engine engine_controller.EngineController pause atomic.Bool stop atomic.Bool stopped chan struct{} @@ -43,9 +59,13 @@ type simpleChainContainer struct { setHandler func(chainID string, h http.Handler) // Set the RPC handler on the router for the chain setMetricsHandler func(chainID string, h http.Handler) // Set the metrics handler on the router for the chain appVersion string - virtualNodeFactory virtualNodeFactory // Factory function to create virtual node (for testing) + virtualNodeFactory virtualNodeFactory // Factory function to create virtual node (for testing) + rollupClient *sources.RollupClient // In-proc rollup RPC client bound to rpcHandler } +// Interface conformance assertions +var _ ChainContainer = (*simpleChainContainer)(nil) + func NewChainContainer( chainID eth.ChainID, vncfg *opnodecfg.Config, @@ -71,6 +91,24 @@ func NewChainContainer( } vncfg.SafeDBPath = c.subPath("safe_db") vncfg.RPC = cfg.RPCConfig + // Attach in-proc rollup client if an initial handler is provided + if c.rpcHandler != nil { + if err := c.attachInProcRollupClient(); err != nil { + log.Warn("failed to attach in-proc rollup client (initial)", "err", err) + } + } + // Initialize engine controller (separate connection, not an op-node override) with a short setup timeout + if vncfg.L2 != nil { + setupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + // Provide contextual logger to engine controller + engLog := log.New("chain_id", chainID.String(), "component", "engine_controller") + if eng, err := engine_controller.NewEngineControllerFromConfig(setupCtx, engLog, vncfg); err != nil { + log.Error("failed to setup engine controller", "err", err) + } else { + c.engine = eng + } + } return c } @@ -96,6 +134,10 @@ func (c *simpleChainContainer) Start(ctx context.Context) error { } c.initOverload.RPCHandler = h c.rpcHandler = h + // attach in-proc rollup client for this handler + if err := c.attachInProcRollupClient(); err != nil { + c.log.Warn("failed to attach in-proc rollup client", "err", err) + } // Disable per-VN metrics server and provide metrics registry hook c.vncfg.Metrics.Enabled = false @@ -152,12 +194,22 @@ func (c *simpleChainContainer) Stop(ctx context.Context) error { stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() + // Close in-proc rollup RPC resources + if c.rollupClient != nil { + c.rollupClient.Close() + } + if c.vn != nil { if err := c.vn.Stop(stopCtx); err != nil { c.log.Error("error stopping virtual node", "error", err) } } + // Close engine controller RPC resources + if c.engine != nil { + _ = c.engine.Close() + } + select { case <-c.stopped: return nil @@ -175,3 +227,123 @@ func (c *simpleChainContainer) Resume(ctx context.Context) error { c.pause.Store(false) return nil } + +// SafeBlockAtTimestamp returns the highest SAFE L2 block with timestamp <= ts using the L2 client. +func (c *simpleChainContainer) SafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) { + if c.engine == nil { + return eth.L2BlockRef{}, engine_controller.ErrNoEngineClient + } + return c.engine.SafeBlockAtTimestamp(ctx, ts) +} + +// OutputRootAtL2BlockNumber computes the L2 output root for the specified L2 block number. +func (c *simpleChainContainer) OutputRootAtL2BlockNumber(ctx context.Context, l2BlockNum uint64) (eth.Bytes32, error) { + if c.engine == nil { + return eth.Bytes32{}, engine_controller.ErrNoEngineClient + } + out, err := c.engine.OutputV0AtBlockNumber(ctx, l2BlockNum) + if err != nil { + return eth.Bytes32{}, err + } + return eth.OutputRoot(out), nil +} + +// SafeHeadAtL1 queries the embedded op-node RPC handler for the SafeDB mapping at/preceding the given L1 block number. +func (c *simpleChainContainer) SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (eth.BlockID, eth.BlockID, error) { + if c.vn == nil { + return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("virtual node not initialized") + } + return c.vn.SafeHeadAtL1(ctx, l1BlockNum) +} + +// L1AtSafeHead delegates to the virtual node to resolve the earliest L1 at which the L2 became safe. +func (c *simpleChainContainer) L1AtSafeHead(ctx context.Context, l2 eth.BlockID) (eth.BlockID, error) { + if c.vn == nil { + return eth.BlockID{}, fmt.Errorf("virtual node not initialized") + } + return c.vn.L1AtSafeHead(ctx, l2) +} + +// CurrentL1 returns the most recent processed L1 block reference based on the derivation pipeline sync status. +func (c *simpleChainContainer) CurrentL1(ctx context.Context) (eth.BlockRef, error) { + if c.vn == nil { + if c.log != nil { + c.log.Warn("CurrentL1: virtual node not initialized") + } + return eth.BlockRef{}, nil + } + return c.vn.CurrentL1(ctx) +} + +// VerifiedAt returns the verified L2 and L1 blocks for the given L2 timestamp. +func (c *simpleChainContainer) VerifiedAt(ctx context.Context, ts uint64) (l2, l1 eth.BlockID, err error) { + l2Block, err := c.SafeBlockAtTimestamp(ctx, ts) + if err != nil { + c.log.Error("error determining l2 block at given timestamp", "error", err) + return eth.BlockID{}, eth.BlockID{}, err + } + l1Block, err := c.L1AtSafeHead(ctx, l2Block.ID()) + if err != nil { + c.log.Error("error determining l1 block number at which l2 block became safe", "error", err) + return eth.BlockID{}, eth.BlockID{}, err + } + + // if there were Verification Activities, we would check if the data could be *verified* at this L1, or would use its L1 block number + // but there are currently no verification activities, so we just return the l2 and l1 blocks + return l2Block.ID(), l1Block, nil +} + +// OptimisticAt returns the optimistic (pre-verified) L2 and L1 blocks for the given L2 timestamp. +func (c *simpleChainContainer) OptimisticAt(ctx context.Context, ts uint64) (l2, l1 eth.BlockID, err error) { + l2Block, err := c.SafeBlockAtTimestamp(ctx, ts) + if err != nil { + c.log.Error("error determining l2 block at given timestamp", "error", err) + return eth.BlockID{}, eth.BlockID{}, err + } + l1Block, err := c.L1AtSafeHead(ctx, l2Block.ID()) + if err != nil { + c.log.Error("error determining l1 block number at which l2 block became safe", "error", err) + return eth.BlockID{}, eth.BlockID{}, err + } + + // if there were Verification Activities, we could check if there was a pre-verified block which was added to the denylist + // but there are currently no verification activities, so we just return the l2 and l1 blocks + return l2Block.ID(), l1Block, nil +} + +// OptimisticOutputAtTimestamp returns the full Output for the optimistic L2 block at the given timestamp. +// For now this simply calls the op-node's normal OutputAtBlock for the block number computed from the timestamp. +func (c *simpleChainContainer) OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputResponse, error) { + if c.rollupClient == nil { + return nil, fmt.Errorf("rollup client not initialized") + } + // Determine the optimistic L2 block at timestamp (currently same as safe block at ts) + l2Block, err := c.SafeBlockAtTimestamp(ctx, ts) + if err != nil { + return nil, fmt.Errorf("failed to resolve L2 block at timestamp: %w", err) + } + // Call the standard OutputAtBlock RPC + out, err := c.rollupClient.OutputAtBlock(ctx, l2Block.Number) + if err != nil { + return nil, fmt.Errorf("failed to get output at block %d: %w", l2Block.Number, err) + } + return out, nil +} + +// attachInProcRollupClient creates a new in-proc rollup RPC client bound to the current rpcHandler. +// It will close any existing client before replacing it. +func (c *simpleChainContainer) attachInProcRollupClient() error { + if c.rpcHandler == nil { + return fmt.Errorf("rpc handler not initialized") + } + inproc, err := c.rpcHandler.DialInProc() + if err != nil { + return err + } + // Close previous rollup client if present + if c.rollupClient != nil { + c.rollupClient.Close() + } + c.rollupClient = sources.NewRollupClient(client.NewBaseRPCClient(inproc)) + return nil +} diff --git a/op-supernode/supernode/chain_container/chain_container_test.go b/op-supernode/supernode/chain_container/chain_container_test.go index ba7da6006ce..193610d7385 100644 --- a/op-supernode/supernode/chain_container/chain_container_test.go +++ b/op-supernode/supernode/chain_container/chain_container_test.go @@ -31,6 +31,14 @@ type mockVirtualNode struct { stopFunc func(ctx context.Context) error blockOnStart bool startSignal chan struct{} + // latest safe mock behavior + latestSafe eth.BlockID + latestErr error + + // safe head mapping mock behavior + safeHeadL1 eth.BlockID + safeHeadL2 eth.BlockID + safeHeadErr error } func newMockVirtualNode() *mockVirtualNode { @@ -73,6 +81,33 @@ func (m *mockVirtualNode) Stop(ctx context.Context) error { return m.stopErr } +// SafeTimestamp implements virtual_node.VirtualNode SafeTimestamp +func (m *mockVirtualNode) LatestSafe(ctx context.Context) (eth.BlockID, error) { + return m.latestSafe, m.latestErr +} + +// SafeHeadAtL1 implements virtual_node.VirtualNode SafeHeadAtL1 +func (m *mockVirtualNode) SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (eth.BlockID, eth.BlockID, error) { + return m.safeHeadL1, m.safeHeadL2, m.safeHeadErr +} + +// L1AtSafeHead implements virtual_node.VirtualNode L1AtSafeHead +func (m *mockVirtualNode) L1AtSafeHead(ctx context.Context, target eth.BlockID) (eth.BlockID, error) { + return m.safeHeadL1, m.safeHeadErr +} + +// LastL1 implements virtual_node.VirtualNode LastL1 +func (m *mockVirtualNode) LastL1(ctx context.Context) (eth.BlockID, error) { + return m.safeHeadL1, m.safeHeadErr +} + +// CurrentL1 implements virtual_node.VirtualNode CurrentL1 +func (m *mockVirtualNode) CurrentL1(ctx context.Context) (eth.BlockRef, error) { + return eth.BlockRef{Hash: m.safeHeadL1.Hash, Number: m.safeHeadL1.Number}, m.safeHeadErr +} + +// SafeDB is not required by VirtualNode in these tests + // Test helpers func createTestVNConfig() *opnodecfg.Config { return &opnodecfg.Config{ @@ -595,3 +630,5 @@ func TestChainContainer_VirtualNodeIntegration(t *testing.T) { }, 1*time.Second, 10*time.Millisecond) }) } + +// Output root helper tests removed with simplified interface diff --git a/op-supernode/supernode/chain_container/engine_controller/engine_controller.go b/op-supernode/supernode/chain_container/engine_controller/engine_controller.go index 2dc77e163c1..1fbe41706b2 100644 --- a/op-supernode/supernode/chain_container/engine_controller/engine_controller.go +++ b/op-supernode/supernode/chain_container/engine_controller/engine_controller.go @@ -1,11 +1,132 @@ package engine_controller +import ( + "context" + "errors" + + opnodecfg "github.com/ethereum-optimism/optimism/op-node/config" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/sources" + gethlog "github.com/ethereum/go-ethereum/log" +) + +// EngineController abstracts access to the L2 execution layer type EngineController interface { + // SafeBlockAtTimestamp returns the L2 block ref for the block at or before the given timestamp, + // clamped to the current SAFE head. + SafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) + // OutputV0AtBlockNumber returns the output preimage for the given L2 block number. + OutputV0AtBlockNumber(ctx context.Context, num uint64) (*eth.OutputV0, error) + // Close releases any underlying RPC resources. + Close() error +} + +// l2Provider captures the subset of the engine client we rely on. +type l2Provider interface { + L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) + L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error) + OutputV0AtBlockNumber(ctx context.Context, blockNum uint64) (*eth.OutputV0, error) + PayloadByNumber(ctx context.Context, number uint64) (*eth.ExecutionPayloadEnvelope, error) + Close() } type simpleEngineController struct { + l2 l2Provider + rollup *rollup.Config + log gethlog.Logger +} + +// NewEngineControllerWithL2 wraps an existing L2 provider. +func NewEngineControllerWithL2(l2 l2Provider) EngineController { + return &simpleEngineController{l2: l2, log: gethlog.New()} +} + +// NewEngineControllerFromConfig builds an engine client from the op-node L2 endpoint config. +// This creates a separate connection (not passed as an override to op-node). +func NewEngineControllerFromConfig(ctx context.Context, log gethlog.Logger, vncfg *opnodecfg.Config) (EngineController, error) { + rpc, engCfg, err := vncfg.L2.Setup(ctx, log, &vncfg.Rollup, &opmetrics.NoopRPCMetrics{}) + if err != nil { + return nil, err + } + eng, err := sources.NewEngineClient(rpc, log, nil, engCfg) + if err != nil { + return nil, err + } + return &simpleEngineController{l2: eng, rollup: &vncfg.Rollup, log: log}, nil +} + +var ( + ErrNoEngineClient = errors.New("engine client not initialized") + ErrNoRollupConfig = errors.New("rollup config not available") + ErrNotFound = errors.New("not found") +) + +func (e *simpleEngineController) SafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) { + if e.l2 == nil { + return eth.L2BlockRef{}, ErrNoEngineClient + } + if e.rollup == nil { + return eth.L2BlockRef{}, ErrNoRollupConfig + } + // Compute the target block directly from rollup config + num, err := e.rollup.TargetBlockNumber(ts) + if err != nil { + return eth.L2BlockRef{}, err + } + safeHead, err := e.l2.L2BlockRefByLabel(ctx, eth.Safe) + if err != nil { + return eth.L2BlockRef{}, err + } + if num > safeHead.Number { + e.log.Warn("engine_controller: target block number exceeds safe head", "targetBlockNumber", num, "safeHead", safeHead.Number) + return eth.L2BlockRef{}, ErrNotFound + } + e.log.Debug("engine_controller: computed safe block number from timestamp", + "timestamp", ts, "targetBlockNumber", num, "safeHead", safeHead.Number, "safeHeadErr", err) + return e.l2.L2BlockRefByNumber(ctx, num) } -func NewEngineController() EngineController { - return &simpleEngineController{} +func (e *simpleEngineController) OutputV0AtBlockNumber(ctx context.Context, num uint64) (*eth.OutputV0, error) { + if e.l2 == nil { + return nil, ErrNoEngineClient + } + // Prefer payload WithdrawalsRoot to avoid eth_getProof requirement on compatible nodes + env, err := e.l2.PayloadByNumber(ctx, num) + if e.log != nil { + if err != nil { + e.log.Debug("engine_controller: payload fetch failed, will try fallback if needed", "blockNumber", num, "err", err) + } else if env == nil || env.ExecutionPayload == nil { + e.log.Debug("engine_controller: payload missing, will try fallback", "blockNumber", num) + } else if env.ExecutionPayload.WithdrawalsRoot == nil { + e.log.Debug("engine_controller: payload has no withdrawals root (pre-Isthmus?), will try fallback", "blockNumber", num) + } else { + e.log.Debug("engine_controller: payload contains withdrawals root; using payload-based OutputV0", "blockNumber", num) + } + } + if err == nil && env != nil && env.ExecutionPayload != nil && env.ExecutionPayload.WithdrawalsRoot != nil { + p := env.ExecutionPayload + out := ð.OutputV0{ + StateRoot: p.StateRoot, + MessagePasserStorageRoot: eth.Bytes32(*p.WithdrawalsRoot), + BlockHash: p.BlockHash, + } + return out, nil + } + // Fallback to proof-based method if payload does not include WithdrawalsRoot + if e.log != nil { + e.log.Debug("engine_controller: falling back to proof-based OutputV0", "blockNumber", num) + } + return e.l2.OutputV0AtBlockNumber(ctx, num) } + +func (e *simpleEngineController) Close() error { + if e.l2 != nil { + e.l2.Close() + } + return nil +} + +// Interface conformance assertion +var _ EngineController = (*simpleEngineController)(nil) diff --git a/op-supernode/supernode/chain_container/engine_controller/engine_controller_test.go b/op-supernode/supernode/chain_container/engine_controller/engine_controller_test.go new file mode 100644 index 00000000000..6545b15f7ef --- /dev/null +++ b/op-supernode/supernode/chain_container/engine_controller/engine_controller_test.go @@ -0,0 +1,109 @@ +package engine_controller + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +// unified mock covers both payload/output paths and SafeBlockAtTimestamp path + +func TestOutputV0AtBlockNumber_UsesPayloadWhenAvailable(t *testing.T) { + t.Parallel() + l2 := &mockL2{ + ref: eth.L2BlockRef{Number: 100, Time: 123}, + payload: ð.ExecutionPayloadEnvelope{ExecutionPayload: ð.ExecutionPayload{ + StateRoot: eth.Bytes32{0xaa}, + WithdrawalsRoot: func() *common.Hash { h := common.Hash{}; h[0] = 0xbb; return &h }(), + BlockHash: func() common.Hash { h := common.Hash{}; h[0] = 0xcc; return h }(), + }}, + } + ec := &simpleEngineController{l2: l2, rollup: &rollup.Config{}, log: gethlog.New()} + out, err := ec.OutputV0AtBlockNumber(context.Background(), 100) + require.NoError(t, err) + require.NotNil(t, out) + require.Equal(t, 1, l2.payloadCalls) + require.Equal(t, 0, l2.outputCalls) // no fallback +} + +func TestOutputV0AtBlockNumber_FallsBackWithoutWithdrawalsRoot(t *testing.T) { + t.Parallel() + l2 := &mockL2{ + ref: eth.L2BlockRef{Number: 100, Time: 123}, + // payload without withdrawals root forces fallback + payload: ð.ExecutionPayloadEnvelope{ExecutionPayload: ð.ExecutionPayload{}}, + output: ð.OutputV0{StateRoot: eth.Bytes32{0x01}, MessagePasserStorageRoot: eth.Bytes32{0x02}, BlockHash: func() common.Hash { var h common.Hash; h[0] = 0x03; return h }()}, + } + ec := &simpleEngineController{l2: l2, rollup: &rollup.Config{}, log: gethlog.New()} + out, err := ec.OutputV0AtBlockNumber(context.Background(), 100) + require.NoError(t, err) + require.NotNil(t, out) + require.Equal(t, 1, l2.payloadCalls) + require.Equal(t, 1, l2.outputCalls) +} + +type mockL2 struct { + // Block ref path + lastNum uint64 + ref eth.L2BlockRef + refErr error + + // Output/payload path + payload *eth.ExecutionPayloadEnvelope + payloadErr error + output *eth.OutputV0 + outputErr error + payloadCalls int + outputCalls int +} + +func (m *mockL2) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) { + return eth.L2BlockRef{Number: 999}, nil +} +func (m *mockL2) L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2BlockRef, error) { + m.lastNum = num + return m.ref, m.refErr +} +func (m *mockL2) OutputV0AtBlockNumber(ctx context.Context, blockNum uint64) (*eth.OutputV0, error) { + m.outputCalls++ + return m.output, m.outputErr +} +func (m *mockL2) PayloadByNumber(ctx context.Context, number uint64) (*eth.ExecutionPayloadEnvelope, error) { + m.payloadCalls++ + return m.payload, m.payloadErr +} +func (m *mockL2) Close() { +} + +func TestEngineController_TargetBlockNumber(t *testing.T) { + t.Parallel() + rcfg := &rollup.Config{Genesis: rollup.Genesis{L2: eth.BlockID{Number: 0}, L2Time: 1_000}, BlockTime: 2, L2ChainID: big.NewInt(420)} + m := &mockL2{ref: eth.L2BlockRef{Number: 0, Time: 0}} + ec := &simpleEngineController{l2: m, rollup: rcfg, log: gethlog.New()} + + // ts = genesis + 2*3 => block #3, with safe head above target + numRef, err := ec.SafeBlockAtTimestamp(context.Background(), 1_000+2*3) + require.NoError(t, err) + require.Equal(t, uint64(3), m.lastNum) + require.Equal(t, m.ref, numRef) + // ts = genesis + 2*1000 => block #1000, with safe head now below target + _, err = ec.SafeBlockAtTimestamp(context.Background(), 1_000+2*1000) + require.ErrorIs(t, err, ErrNotFound) +} + +func TestEngineController_SentinelErrors(t *testing.T) { + t.Parallel() + ec := &simpleEngineController{l2: nil, rollup: nil} + _, err := ec.SafeBlockAtTimestamp(context.Background(), 0) + require.ErrorIs(t, err, ErrNoEngineClient) + + ec = &simpleEngineController{l2: &mockL2{}, rollup: nil} + _, err = ec.SafeBlockAtTimestamp(context.Background(), 0) + require.ErrorIs(t, err, ErrNoRollupConfig) +} diff --git a/op-supernode/supernode/chain_container/virtual_node/virtual_node.go b/op-supernode/supernode/chain_container/virtual_node/virtual_node.go index b9797c4e030..af0cd88e637 100644 --- a/op-supernode/supernode/chain_container/virtual_node/virtual_node.go +++ b/op-supernode/supernode/chain_container/virtual_node/virtual_node.go @@ -3,11 +3,13 @@ package virtual_node import ( "context" "errors" + "math" "sync" opnodecfg "github.com/ethereum-optimism/optimism/op-node/config" opmetrics "github.com/ethereum-optimism/optimism/op-node/metrics" rollupNode "github.com/ethereum-optimism/optimism/op-node/node" + "github.com/ethereum-optimism/optimism/op-service/eth" gethlog "github.com/ethereum/go-ethereum/log" "github.com/google/uuid" ) @@ -31,11 +33,18 @@ var ( type VirtualNode interface { Start(ctx context.Context) error Stop(ctx context.Context) error + + SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (eth.BlockID, eth.BlockID, error) + // L1AtSafeHead returns the earliest L1 block at which the given L2 block became safe. + L1AtSafeHead(ctx context.Context, target eth.BlockID) (eth.BlockID, error) + CurrentL1(ctx context.Context) (eth.BlockRef, error) } type innerNode interface { Start(ctx context.Context) error Stop(ctx context.Context) error + SafeDB() rollupNode.SafeDBReader + SyncStatus() *eth.SyncStatus } type innerNodeFactory func(ctx context.Context, cfg *opnodecfg.Config, log gethlog.Logger, appVersion string, m *opmetrics.Metrics, initOverload *rollupNode.InitializationOverrides) (innerNode, error) @@ -179,3 +188,92 @@ func (v *simpleVirtualNode) State() VNState { defer v.mu.Unlock() return v.state } + +// SafeHeadAtL1 returns the recorded mapping of L1 block -> L2 safe head at or before the given L1 block number. +func (v *simpleVirtualNode) SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (eth.BlockID, eth.BlockID, error) { + v.mu.Lock() + inner := v.inner + v.mu.Unlock() + if inner == nil { + return eth.BlockID{}, eth.BlockID{}, ErrVirtualNodeNotRunning + } + db := inner.SafeDB() + if db == nil { + return eth.BlockID{}, eth.BlockID{}, ErrVirtualNodeNotRunning + } + return db.SafeHeadAtL1(ctx, l1BlockNum) +} + +var ErrL1AtSafeHeadNotFound = errors.New("l1 at safe head not found") + +// L1AtSafeHead finds the earliest L1 block at which the provided L2 block became safe, +// using the monotonicity of SafeDB (L2 safe head number is non-decreasing over L1). +func (v *simpleVirtualNode) L1AtSafeHead(ctx context.Context, target eth.BlockID) (eth.BlockID, error) { + v.mu.Lock() + inner := v.inner + v.mu.Unlock() + if inner == nil { + return eth.BlockID{}, ErrVirtualNodeNotRunning + } + db := inner.SafeDB() + if db == nil { + return eth.BlockID{}, ErrVirtualNodeNotRunning + } + // Get the latest entry to start the walkback + latestL1, latestL2, err := db.SafeHeadAtL1(ctx, math.MaxUint64-1) + if err != nil { + v.log.Debug("L1AtSafeHead: latest lookup failed", "err", err) + return eth.BlockID{}, err + } + v.log.Debug("L1AtSafeHead: latest bounds", "latest_l1", latestL1.Number, "latest_l2_num", latestL2.Number, "latest_l2_hash", latestL2.Hash) + if latestL2.Number < target.Number { + v.log.Debug("L1AtSafeHead: target beyond latest", "latest_l2", latestL2.Number) + return eth.BlockID{}, ErrL1AtSafeHeadNotFound + } + // Walk back until the cursor would drop below the target + cursor := latestL1 + genesisL1 := v.cfg.Rollup.Genesis.L1.Number + for { + if cursor.Number <= 0 || cursor.Number <= genesisL1 { + // if we made it all the way back to genesis, it is likely the SafeDB is not stable enough for use + // safer to simply return an error for now. + v.log.Warn("L1AtSafeHead: reached genesis bound", "genesis_l1", genesisL1, "earliest_l1", cursor.Number) + return eth.BlockID{}, ErrL1AtSafeHeadNotFound + } + prev := cursor.Number - 1 + v.log.Debug("L1AtSafeHead: checking previous l1 block", "l1_num", prev) + l1Prev, l2Prev, err := db.SafeHeadAtL1(ctx, prev) + if err != nil { + v.log.Debug("L1AtSafeHead: walkback lookup failed, stopping", "probe_l1", prev, "err", err) + break + } + v.log.Debug("L1AtSafeHead: walkback result", "l1_prev", l1Prev.Number, "l2_prev_num", l2Prev.Number, "l2_prev_hash", l2Prev.Hash) + if l2Prev.Number >= target.Number { + // Still meets or exceeds target; continue walking back + cursor = l1Prev + continue + } + // Dropped below target; current cursor is the first that meets/exceeds + break + } + v.log.Debug("L1AtSafeHead: result", "l1", cursor) + return cursor, nil +} + +// CurrentL1 returns the current processed L1 block based on derivation pipeline sync status. +func (v *simpleVirtualNode) CurrentL1(ctx context.Context) (eth.BlockRef, error) { + v.mu.Lock() + inner := v.inner + v.mu.Unlock() + if inner == nil { + return eth.BlockRef{}, ErrVirtualNodeNotRunning + } + st := inner.SyncStatus() + // Map L1 block ref into generic block ref + return eth.BlockRef{ + Hash: st.CurrentL1.Hash, + Number: st.CurrentL1.Number, + ParentHash: st.CurrentL1.ParentHash, + Time: st.CurrentL1.Time, + }, nil +} diff --git a/op-supernode/supernode/chain_container/virtual_node/virtual_node_test.go b/op-supernode/supernode/chain_container/virtual_node/virtual_node_test.go index 5b6e89feec8..1e57d197f9e 100644 --- a/op-supernode/supernode/chain_container/virtual_node/virtual_node_test.go +++ b/op-supernode/supernode/chain_container/virtual_node/virtual_node_test.go @@ -12,6 +12,7 @@ import ( opmetrics "github.com/ethereum-optimism/optimism/op-node/metrics" rollupNode "github.com/ethereum-optimism/optimism/op-node/node" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" gethlog "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -24,6 +25,9 @@ type mockInnerNode struct { stopErr error startFunc func(ctx context.Context) started bool + safeTs uint64 + haveSafe bool + db rollupNode.SafeDBReader } func newMockInnerNode() *mockInnerNode { @@ -51,6 +55,16 @@ func (m *mockInnerNode) Stop(ctx context.Context) error { return m.stopErr } +// SafeL2Timestamp implements the innerNode interface method used by VirtualNode for safety checks +func (m *mockInnerNode) SafeL2Timestamp() (uint64, bool) { + return m.safeTs, m.haveSafe +} + +// SafeDB implements innerNode interface method used by VirtualNode +func (m *mockInnerNode) SafeDB() rollupNode.SafeDBReader { return m.db } + +func (m *mockInnerNode) SyncStatus() *eth.SyncStatus { return ð.SyncStatus{} } + // Test helpers func createTestConfig() *opnodecfg.Config { return &opnodecfg.Config{ diff --git a/op-supernode/supernode/supernode.go b/op-supernode/supernode/supernode.go index ab2f4a3f9a6..6fb50868282 100644 --- a/op-supernode/supernode/supernode.go +++ b/op-supernode/supernode/supernode.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-supernode/supernode/activity" "github.com/ethereum-optimism/optimism/op-supernode/supernode/activity/heartbeat" + "github.com/ethereum-optimism/optimism/op-supernode/supernode/activity/superroot" cc "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container" "github.com/ethereum-optimism/optimism/op-supernode/supernode/resources" gethlog "github.com/ethereum/go-ethereum/log" @@ -84,6 +85,7 @@ func New(ctx context.Context, log gethlog.Logger, version string, requestStop co // Initialize activities s.activities = []activity.Activity{ heartbeat.New(log.New("activity", "heartbeat"), 10*time.Second), + superroot.New(log.New("activity", "superroot"), s.chains), } addr := net.JoinHostPort(cfg.RPCConfig.ListenAddr, strconv.Itoa(cfg.RPCConfig.ListenPort)) s.httpServer = httputil.NewHTTPServer(addr, s.rpcRouter) diff --git a/op-test-sequencer/sequencer/backend/work/committers/standardcommitter/committer.go b/op-test-sequencer/sequencer/backend/work/committers/standardcommitter/committer.go index 317e854b963..10663488f49 100644 --- a/op-test-sequencer/sequencer/backend/work/committers/standardcommitter/committer.go +++ b/op-test-sequencer/sequencer/backend/work/committers/standardcommitter/committer.go @@ -49,6 +49,8 @@ func (n *Committer) Commit(ctx context.Context, block work.SignedBlock) error { err := n.api.CommitBlock(ctx, bl) if err != nil { n.log.Error("Failed to publish block", "block", block, "err", err) + return err } - return err + n.log.Info("Committed block to op-stack", "block", bl.ID()) + return nil } diff --git a/op-test-sequencer/sequencer/backend/work/publishers/standardpublisher/publisher.go b/op-test-sequencer/sequencer/backend/work/publishers/standardpublisher/publisher.go index a26ff2f6260..dfedd9225b3 100644 --- a/op-test-sequencer/sequencer/backend/work/publishers/standardpublisher/publisher.go +++ b/op-test-sequencer/sequencer/backend/work/publishers/standardpublisher/publisher.go @@ -47,6 +47,8 @@ func (n *Publisher) Publish(ctx context.Context, block work.SignedBlock) error { err := n.api.PublishBlock(ctx, bl) if err != nil { n.log.Error("Failed to publish block", "block", block, "err", err) + return err } - return err + n.log.Info("Published block to op-stack", "block", bl.ID()) + return nil } diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol index 03d11b5304a..fb1351e1ff1 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol @@ -36,7 +36,7 @@ interface IOPContractsManagerContainer { address storageSetterImpl; } - error OPContractsManagerContractsContainer_DevFeatureInProd(); + error OPContractsManagerContainer_DevFeatureInProd(); function blueprints() external view returns (Blueprints memory); function implementations() external view returns (Implementations memory); diff --git a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol index 48fc1aefa97..4e2c609400a 100644 --- a/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol +++ b/packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerV2.sol @@ -80,6 +80,7 @@ interface IOPContractsManagerV2 { uint256 l2ChainId; IResourceMetering.ResourceConfig resourceConfig; DisputeGameConfig[] disputeGameConfigs; + bool useCustomGasToken; } struct ExtraInstruction { @@ -135,9 +136,7 @@ interface IOPContractsManagerV2 { function version() external view returns (string memory); /// @notice Upgrades Superchain-wide contracts. - function upgradeSuperchain(SuperchainUpgradeInput memory _inp) - external - returns (SuperchainContracts memory); + function upgradeSuperchain(SuperchainUpgradeInput memory _inp) external returns (SuperchainContracts memory); /// @notice Deploys and wires a complete OP Chain per the provided configuration. function deploy(FullConfig memory _cfg) external returns (ChainContracts memory); diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 434b768f8e4..c47751e7e45 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -286,6 +286,10 @@ lint-forge-tests-check: build lint-forge-tests-check-no-build lint-check: forge fmt --check +# Updates the selectors for the contracts +update-selectors: + forge selectors up --all + # Checks for unused imports in Solidity contracts. Does not build contracts. unused-imports-check-no-build: go run ./scripts/checks/unused-imports @@ -293,6 +297,13 @@ unused-imports-check-no-build: # Checks for unused imports in Solidity contracts. unused-imports-check: build unused-imports-check-no-build +# Checks that contracts use strict pragma versions. Does not build contracts. +strict-pragma-check-no-build: + go run ./scripts/checks/strict-pragma + +# Checks that contracts use strict pragma versions. +strict-pragma-check: build strict-pragma-check-no-build + # Checks that the semver of contracts are valid. Does not build contracts. valid-semver-check-no-build: go run ./scripts/checks/valid-semver-check/main.go @@ -332,6 +343,7 @@ check: lint-check \ snapshots-check-no-build \ unused-imports-check-no-build \ + strict-pragma-check-no-build \ valid-semver-check-no-build \ semver-diff-check-no-build \ validate-deploy-configs \ @@ -380,6 +392,7 @@ pre-pr *ARGS: just build-source just check + # Restore build artifacts after running checks. if [ -d "$TEMP_BUILD_DIR" ]; then cp -r "$TEMP_BUILD_DIR/artifacts" ./ diff --git a/packages/contracts-bedrock/scripts/checks/strict-pragma/main.go b/packages/contracts-bedrock/scripts/checks/strict-pragma/main.go new file mode 100644 index 00000000000..6b272cb5065 --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/strict-pragma/main.go @@ -0,0 +1,184 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "regexp" + "strings" + + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/scripts/checks/common" +) + +// Patterns to detect contract types and pragma +var ( + // Matches "pragma solidity X.Y.Z;" (strict) vs "pragma solidity ^X.Y.Z;" or ">=X.Y.Z" (non-strict) + pragmaPattern = regexp.MustCompile(`pragma\s+solidity\s+([^;]+);`) + + // Matches "contract Name" but not "abstract contract Name" + // Uses \s* to allow indentation at start of line + contractPattern = regexp.MustCompile(`(?m)^\s*contract\s+\w+`) + + // Matches "abstract contract Name" + abstractPattern = regexp.MustCompile(`(?m)^\s*abstract\s+contract\s+\w+`) + + // Matches "library Name" + libraryPattern = regexp.MustCompile(`(?m)^\s*library\s+\w+`) + + // Matches "interface Name" + interfacePattern = regexp.MustCompile(`(?m)^\s*interface\s+\w+`) +) + +// Files that are grandfathered in (already have non-strict pragma) +// These should be fixed over time, but we don't want to block CI on them +var excludedFiles = []string{ + "src/integration/EventLogger.sol", + "src/integration/GameHelper.sol", + "src/libraries/TransientContext.sol", + "src/periphery/AssetReceiver.sol", + "src/periphery/Transactor.sol", + "src/periphery/monitoring/DisputeMonitorHelper.sol", + "src/universal/SafeSend.sol", +} + +func main() { + if _, err := common.ProcessFilesGlob( + []string{"src/**/*.sol"}, + excludedFiles, + processFile, + ); err != nil { + fmt.Printf("error: %v\n", err) + os.Exit(1) + } +} + +func processFile(filePath string) (*common.Void, []error) { + content, err := os.ReadFile(filePath) + if err != nil { + return nil, []error{fmt.Errorf("failed to read file: %w", err)} + } + + contentStr := string(content) + + // Check if file contains a concrete contract (not abstract, not library, not interface) + if !hasConcreteContract(contentStr) { + return nil, nil + } + + // Check if pragma is strict + pragma := extractPragma(contentStr) + if pragma == "" { + return nil, []error{fmt.Errorf("no pragma found")} + } + + if !isStrictPragma(pragma) { + return nil, []error{fmt.Errorf("non-strict pragma '%s' - contracts must use exact version (e.g., '0.8.15' not '^0.8.15')", pragma)} + } + + return nil, nil +} + +// hasConcreteContract returns true if the file contains at least one concrete contract +// (not abstract, not library, not interface) +func hasConcreteContract(content string) bool { + // Remove comments to avoid false positives + content = removeComments(content) + + // Check for concrete contract definition + hasContract := contractPattern.MatchString(content) + if !hasContract { + return false + } + + // Make sure it's not just abstract contracts, libraries, or interfaces + // by checking if we have a "contract X" that isn't preceded by "abstract" + lines := strings.Split(content, "\n") + for _, line := range lines { + trimmed := strings.TrimSpace(line) + // Skip if it's an abstract contract, library, or interface + if abstractPattern.MatchString(trimmed) || + libraryPattern.MatchString(trimmed) || + interfacePattern.MatchString(trimmed) { + continue + } + // Check for concrete contract + if contractPattern.MatchString(trimmed) { + return true + } + } + + return false +} + +// extractPragma extracts the pragma version string from the content +func extractPragma(content string) string { + matches := pragmaPattern.FindStringSubmatch(content) + if len(matches) > 1 { + return strings.TrimSpace(matches[1]) + } + return "" +} + +// isStrictPragma returns true if the pragma is a strict version (no ^ or >= or other operators) +func isStrictPragma(pragma string) bool { + // Strict pragma should be just a version number like "0.8.15" + // Non-strict examples: "^0.8.0", ">=0.8.0", ">=0.8.0 <0.9.0", "0.8.x" + + // Check for common non-strict indicators + nonStrictIndicators := []string{"^", ">=", "<=", ">", "<", "~", "x", "X", "*", " "} + for _, indicator := range nonStrictIndicators { + if strings.Contains(pragma, indicator) { + return false + } + } + + // Should match a simple version pattern like "0.8.15" + strictPattern := regexp.MustCompile(`^\d+\.\d+\.\d+$`) + return strictPattern.MatchString(pragma) +} + +// removeComments removes single-line and multi-line comments from Solidity code +func removeComments(content string) string { + var result strings.Builder + scanner := bufio.NewScanner(strings.NewReader(content)) + inMultiLineComment := false + + for scanner.Scan() { + line := scanner.Text() + + // Handle multi-line comments + if inMultiLineComment { + if idx := strings.Index(line, "*/"); idx != -1 { + line = line[idx+2:] + inMultiLineComment = false + } else { + continue + } + } + + // Remove multi-line comment starts + for { + startIdx := strings.Index(line, "/*") + if startIdx == -1 { + break + } + endIdx := strings.Index(line[startIdx:], "*/") + if endIdx == -1 { + line = line[:startIdx] + inMultiLineComment = true + break + } + line = line[:startIdx] + line[startIdx+endIdx+2:] + } + + // Remove single-line comments + if idx := strings.Index(line, "//"); idx != -1 { + line = line[:idx] + } + + result.WriteString(line) + result.WriteString("\n") + } + + return result.String() +} diff --git a/packages/contracts-bedrock/scripts/checks/strict-pragma/main_test.go b/packages/contracts-bedrock/scripts/checks/strict-pragma/main_test.go new file mode 100644 index 00000000000..0426dbaf2de --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/strict-pragma/main_test.go @@ -0,0 +1,288 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_hasConcreteContract(t *testing.T) { + tests := []struct { + name string + content string + expected bool + }{ + { + name: "concrete contract", + content: ` + pragma solidity 0.8.15; + contract MyContract { + } + `, + expected: true, + }, + { + name: "abstract contract only", + content: ` + pragma solidity 0.8.15; + abstract contract MyContract { + } + `, + expected: false, + }, + { + name: "library only", + content: ` + pragma solidity 0.8.15; + library MyLibrary { + } + `, + expected: false, + }, + { + name: "interface only", + content: ` + pragma solidity 0.8.15; + interface IMyInterface { + } + `, + expected: false, + }, + { + name: "abstract and concrete contract", + content: ` + pragma solidity 0.8.15; + abstract contract Base { + } + contract MyContract is Base { + } + `, + expected: true, + }, + { + name: "library and concrete contract", + content: ` + pragma solidity 0.8.15; + library MyLibrary { + } + contract MyContract { + } + `, + expected: true, + }, + { + name: "contract in comment", + content: ` + pragma solidity 0.8.15; + // contract NotReal { + // } + library MyLibrary { + } + `, + expected: false, + }, + { + name: "contract in multiline comment", + content: ` + pragma solidity 0.8.15; + /* + contract NotReal { + } + */ + library MyLibrary { + } + `, + expected: false, + }, + { + name: "empty content", + content: "", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := hasConcreteContract(tt.content) + require.Equal(t, tt.expected, result) + }) + } +} + +func Test_extractPragma(t *testing.T) { + tests := []struct { + name string + content string + expected string + }{ + { + name: "strict pragma", + content: ` + pragma solidity 0.8.15; + contract MyContract {} + `, + expected: "0.8.15", + }, + { + name: "caret pragma", + content: ` + pragma solidity ^0.8.0; + contract MyContract {} + `, + expected: "^0.8.0", + }, + { + name: "range pragma", + content: ` + pragma solidity >=0.8.0 <0.9.0; + contract MyContract {} + `, + expected: ">=0.8.0 <0.9.0", + }, + { + name: "greater than pragma", + content: ` + pragma solidity >=0.8.0; + contract MyContract {} + `, + expected: ">=0.8.0", + }, + { + name: "no pragma", + content: "contract MyContract {}", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := extractPragma(tt.content) + require.Equal(t, tt.expected, result) + }) + } +} + +func Test_isStrictPragma(t *testing.T) { + tests := []struct { + name string + pragma string + expected bool + }{ + { + name: "strict version", + pragma: "0.8.15", + expected: true, + }, + { + name: "strict version with different numbers", + pragma: "0.8.28", + expected: true, + }, + { + name: "caret version", + pragma: "^0.8.0", + expected: false, + }, + { + name: "greater than or equal", + pragma: ">=0.8.0", + expected: false, + }, + { + name: "less than or equal", + pragma: "<=0.9.0", + expected: false, + }, + { + name: "range", + pragma: ">=0.8.0 <0.9.0", + expected: false, + }, + { + name: "tilde version", + pragma: "~0.8.0", + expected: false, + }, + { + name: "wildcard x", + pragma: "0.8.x", + expected: false, + }, + { + name: "wildcard X", + pragma: "0.8.X", + expected: false, + }, + { + name: "wildcard star", + pragma: "0.8.*", + expected: false, + }, + { + name: "empty pragma", + pragma: "", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isStrictPragma(tt.pragma) + require.Equal(t, tt.expected, result) + }) + } +} + +func Test_removeComments(t *testing.T) { + tests := []struct { + name string + content string + shouldNotContain string + shouldContain string + }{ + { + name: "single line comment", + content: `contract MyContract { + // this is a comment + uint256 value; + }`, + shouldNotContain: "this is a comment", + shouldContain: "uint256 value", + }, + { + name: "multi line comment", + content: `contract MyContract { + /* this is + a multi-line + comment */ + uint256 value; + }`, + shouldNotContain: "multi-line", + shouldContain: "uint256 value", + }, + { + name: "inline comment", + content: `contract MyContract { + uint256 value; // inline comment + }`, + shouldNotContain: "inline comment", + shouldContain: "uint256 value", + }, + { + name: "no comments", + content: "contract MyContract {}", + shouldNotContain: "", + shouldContain: "contract MyContract", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := removeComments(tt.content) + if tt.shouldNotContain != "" { + require.NotContains(t, result, tt.shouldNotContain) + } + require.Contains(t, result, tt.shouldContain) + }) + } +} diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 9e743b2fa4f..abe36f4cef9 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -542,7 +542,8 @@ contract Deploy is Deployer { gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), l2ChainId: cfg.l2ChainID(), resourceConfig: Constants.DEFAULT_RESOURCE_CONFIG(), - disputeGameConfigs: disputeGameConfigs + disputeGameConfigs: disputeGameConfigs, + useCustomGasToken: cfg.useCustomGasToken() }); } } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json index 29b2d118a3d..f81f6cda750 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContainer.json @@ -368,7 +368,7 @@ }, { "inputs": [], - "name": "OPContractsManagerContractsContainer_DevFeatureInProd", + "name": "OPContractsManagerContainer_DevFeatureInProd", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json index 1f10dbcd2b8..ac8633e9465 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerV2.json @@ -227,6 +227,11 @@ "internalType": "struct OPContractsManagerV2.DisputeGameConfig[]", "name": "disputeGameConfigs", "type": "tuple[]" + }, + { + "internalType": "bool", + "name": "useCustomGasToken", + "type": "bool" } ], "internalType": "struct OPContractsManagerV2.FullConfig", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 638beb96a01..600bd47b88a 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -52,8 +52,8 @@ "sourceCodeHash": "0xc2b530bf529ac23d1ba1adf67eedcc5d95e25c2919e1d97f4f2bba4823303b17" }, "src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": { - "initCodeHash": "0x0db0819c7dcefafc5a0780b3a85c0b2dded2413cb5ee12306eecd6c81d91a404", - "sourceCodeHash": "0x41a838105c41756eccab3a2ef2e89ebd1ec4e04aabde1597ca847878cb87fe4f" + "initCodeHash": "0x631a5214207f818ef0d9a7547f237a4ede31d7c5a6fe986661489918c0cd4284", + "sourceCodeHash": "0xdb58f72f482407684b1b6fcd2bec54b8c389f73582991480681fa21ee8185e26" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0x838bbd7f381e84e21887f72bd1da605bfc4588b3c39aed96cbce67c09335b3ee", diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol index fc4d18eb151..7775cc485ee 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerContainer.sol @@ -63,7 +63,7 @@ contract OPContractsManagerContainer { bytes32 public immutable devFeatureBitmap; /// @notice Thrown when a development feature is enabled in production. - error OPContractsManagerContractsContainer_DevFeatureInProd(); + error OPContractsManagerContainer_DevFeatureInProd(); /// @param _blueprints The blueprint contract addresses. /// @param _implementations The implementation contract addresses. @@ -75,7 +75,7 @@ contract OPContractsManagerContainer { // Development features MUST NOT be enabled on Mainnet. if (block.chainid == 1 && !_isTestingEnvironment() && uint256(_devFeatureBitmap) != 0) { - revert OPContractsManagerContractsContainer_DevFeatureInProd(); + revert OPContractsManagerContainer_DevFeatureInProd(); } } diff --git a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol index f5bc13c83c2..e7a6ab907f8 100644 --- a/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol +++ b/packages/contracts-bedrock/src/L1/opcm/OPContractsManagerV2.sol @@ -121,6 +121,8 @@ contract OPContractsManagerV2 is ISemver { IResourceMetering.ResourceConfig resourceConfig; // Dispute game configuration. DisputeGameConfig[] disputeGameConfigs; + // CGT + bool useCustomGasToken; } /// @notice Partial input required for an upgrade. @@ -180,8 +182,8 @@ contract OPContractsManagerV2 is ISemver { IOPContractsManagerStandardValidator public immutable standardValidator; /// @notice The version of the OPCM contract. - /// @custom:semver 6.1.0 - string public constant version = "6.1.0"; + /// @custom:semver 6.2.0 + string public constant version = "6.2.0"; /// @notice Special constant key for the PermittedProxyDeployment instruction. string internal constant PERMITTED_PROXY_DEPLOYMENT_KEY = "PermittedProxyDeployment"; @@ -606,7 +608,16 @@ contract OPContractsManagerV2 is ISemver { ), (GameType) ), - disputeGameConfigs: _upgradeInput.disputeGameConfigs + disputeGameConfigs: _upgradeInput.disputeGameConfigs, + useCustomGasToken: abi.decode( + _loadBytes( + address(_chainContracts.systemConfig), + _chainContracts.systemConfig.isCustomGasToken.selector, + "overrides.cfg.useCustomGasToken", + _upgradeInput.extraInstructions + ), + (bool) + ) }); } @@ -810,6 +821,11 @@ contract OPContractsManagerV2 is ISemver { ); } + // If the custom gas token feature was requested, enable it in the SystemConfig. + if (_cfg.useCustomGasToken) { + _cts.systemConfig.setFeature(Features.CUSTOM_GAS_TOKEN, true); + } + // If critical transfer is allowed, tranfer ownership of the DisputeGameFactory and // ProxyAdmin to the PAO. During deployments, this means transferring ownership from the // OPCM contract to the target PAO. During upgrades, this would theoretically mean diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerContainer.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerContainer.t.sol new file mode 100644 index 00000000000..8f42a642360 --- /dev/null +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerContainer.t.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { Test } from "forge-std/Test.sol"; + +// Contracts +import { OPContractsManagerContainer } from "src/L1/opcm/OPContractsManagerContainer.sol"; + +// Libraries +import { Constants } from "src/libraries/Constants.sol"; + +/// @title OPContractsManagerContainer_TestInit +/// @notice Shared setup for OPContractsManagerContainer tests. +contract OPContractsManagerContainer_TestInit is Test { + OPContractsManagerContainer.Blueprints internal blueprints; + OPContractsManagerContainer.Implementations internal implementations; + + function setUp() public virtual { + blueprints = OPContractsManagerContainer.Blueprints({ + addressManager: makeAddr("addressManager"), + proxy: makeAddr("proxy"), + proxyAdmin: makeAddr("proxyAdmin"), + l1ChugSplashProxy: makeAddr("l1ChugSplashProxy"), + resolvedDelegateProxy: makeAddr("resolvedDelegateProxy"), + permissionedDisputeGame1: makeAddr("permissionedDisputeGame1"), + permissionedDisputeGame2: makeAddr("permissionedDisputeGame2"), + permissionlessDisputeGame1: makeAddr("permissionlessDisputeGame1"), + permissionlessDisputeGame2: makeAddr("permissionlessDisputeGame2") + }); + + implementations = OPContractsManagerContainer.Implementations({ + superchainConfigImpl: makeAddr("superchainConfigImpl"), + protocolVersionsImpl: makeAddr("protocolVersionsImpl"), + l1ERC721BridgeImpl: makeAddr("l1ERC721BridgeImpl"), + optimismPortalImpl: makeAddr("optimismPortalImpl"), + optimismPortalInteropImpl: makeAddr("optimismPortalInteropImpl"), + ethLockboxImpl: makeAddr("ethLockboxImpl"), + systemConfigImpl: makeAddr("systemConfigImpl"), + optimismMintableERC20FactoryImpl: makeAddr("optimismMintableERC20FactoryImpl"), + l1CrossDomainMessengerImpl: makeAddr("l1CrossDomainMessengerImpl"), + l1StandardBridgeImpl: makeAddr("l1StandardBridgeImpl"), + disputeGameFactoryImpl: makeAddr("disputeGameFactoryImpl"), + anchorStateRegistryImpl: makeAddr("anchorStateRegistryImpl"), + delayedWETHImpl: makeAddr("delayedWETHImpl"), + mipsImpl: makeAddr("mipsImpl"), + faultDisputeGameV2Impl: makeAddr("faultDisputeGameV2Impl"), + permissionedDisputeGameV2Impl: makeAddr("permissionedDisputeGameV2Impl"), + superFaultDisputeGameImpl: makeAddr("superFaultDisputeGameImpl"), + superPermissionedDisputeGameImpl: makeAddr("superPermissionedDisputeGameImpl"), + storageSetterImpl: makeAddr("storageSetterImpl") + }); + } + + /// @notice Deploys a new OPContractsManagerContainer with the given dev feature bitmap. + /// @param _devFeatureBitmap The dev feature bitmap to use. + /// @return The deployed OPContractsManagerContainer. + function _deploy(bytes32 _devFeatureBitmap) internal returns (OPContractsManagerContainer) { + return new OPContractsManagerContainer(blueprints, implementations, _devFeatureBitmap); + } +} + +/// @title OPContractsManagerContainer_Constructor_Test +/// @notice Tests the constructor of OPContractsManagerContainer. +contract OPContractsManagerContainer_Constructor_Test is OPContractsManagerContainer_TestInit { + /// @notice Tests that the constructor succeeds with any dev bitmap when in a test environment. + /// @param _chainId The chain ID to use. + /// @param _devFeatureBitmap The dev feature bitmap to use. + function testFuzz_constructor_devBitmapInTestEnv_succeeds(uint64 _chainId, bytes32 _devFeatureBitmap) public { + // Etch code into the magic testing address so we're recognized as a test env. + vm.etch(Constants.TESTING_ENVIRONMENT_ADDRESS, hex"01"); + + // Set chain ID. + vm.chainId(_chainId); + + OPContractsManagerContainer container = _deploy(_devFeatureBitmap); + + assertEq(container.devFeatureBitmap(), _devFeatureBitmap); + } + + /// @notice Tests that the constructor reverts when dev features are enabled on mainnet without + /// test env. + /// @param _devFeatureBitmap The dev feature bitmap to use. + function testFuzz_constructor_devBitmapOnMainnet_reverts(bytes32 _devFeatureBitmap) public { + // Ensure at least one dev feature is enabled. + _devFeatureBitmap = bytes32(bound(uint256(_devFeatureBitmap), 1, type(uint256).max)); + + // Clear the magic testing address so we're recognized as production. + vm.etch(Constants.TESTING_ENVIRONMENT_ADDRESS, hex""); + + // Set chain ID to mainnet. + vm.chainId(1); + + vm.expectRevert(OPContractsManagerContainer.OPContractsManagerContainer_DevFeatureInProd.selector); + _deploy(_devFeatureBitmap); + } + + /// @notice Tests that the constructor succeeds on mainnet with a zero dev bitmap. + function test_constructor_zeroBitmapOnMainnet_succeeds() public { + // Clear the magic testing address. + vm.etch(Constants.TESTING_ENVIRONMENT_ADDRESS, hex""); + + // Set chain ID to mainnet. + vm.chainId(1); + + OPContractsManagerContainer container = _deploy(bytes32(0)); + + assertEq(container.devFeatureBitmap(), bytes32(0)); + } +} + +/// @title OPContractsManagerContainer_Blueprints_Test +/// @notice Tests the blueprints() getter. +contract OPContractsManagerContainer_Blueprints_Test is OPContractsManagerContainer_TestInit { + /// @notice Tests that blueprints() returns the struct provided at construction. + function test_blueprints_succeeds() public { + OPContractsManagerContainer container = _deploy(bytes32(0)); + + assertEq(abi.encode(container.blueprints()), abi.encode(blueprints)); + } +} + +/// @title OPContractsManagerContainer_Implementations_Test +/// @notice Tests the implementations() getter. +contract OPContractsManagerContainer_Implementations_Test is OPContractsManagerContainer_TestInit { + /// @notice Tests that implementations() returns the struct provided at construction. + function test_implementations_succeeds() public { + OPContractsManagerContainer container = _deploy(bytes32(0)); + + assertEq(abi.encode(container.implementations()), abi.encode(implementations)); + } +} + +/// @title OPContractsManagerContainer_IsDevFeatureEnabled_Test +/// @notice Tests the isDevFeatureEnabled() function. +contract OPContractsManagerContainer_IsDevFeatureEnabled_Test is OPContractsManagerContainer_TestInit { + /// @notice Tests that isDevFeatureEnabled returns true when the feature bit is set. + /// @param _bitIndex The bit index to test. + function testFuzz_isDevFeatureEnabled_bitSet_succeeds(uint8 _bitIndex) public { + bytes32 bitmap = bytes32(uint256(1) << _bitIndex); + bytes32 feature = bytes32(uint256(1) << _bitIndex); + + OPContractsManagerContainer container = _deploy(bitmap); + + assertTrue(container.isDevFeatureEnabled(feature)); + assertFalse(container.isDevFeatureEnabled(bytes32(0))); + } + + /// @notice Tests that isDevFeatureEnabled returns false when the feature bit is not set. + /// @param _bitIndex The bit index to test. + function testFuzz_isDevFeatureEnabled_bitNotSet_succeeds(uint8 _bitIndex) public { + // Create a bitmap with all bits set except the one we're testing. + bytes32 bitmap = bytes32(type(uint256).max ^ (uint256(1) << _bitIndex)); + bytes32 feature = bytes32(uint256(1) << _bitIndex); + + OPContractsManagerContainer container = _deploy(bitmap); + + assertFalse(container.isDevFeatureEnabled(feature)); + } + + /// @notice Tests that isDevFeatureEnabled returns false when the bitmap is zero. + /// @param _feature The feature to check. + function testFuzz_isDevFeatureEnabled_zeroBitmap_succeeds(bytes32 _feature) public { + OPContractsManagerContainer container = _deploy(bytes32(0)); + + assertFalse(container.isDevFeatureEnabled(_feature)); + } + + /// @notice Tests that isDevFeatureEnabled returns true for multiple features set at once. + function test_isDevFeatureEnabled_multipleBitsSet_succeeds() public { + uint256 numFeatures = vm.randomUint(1, 16); + uint256 bitmap; + uint8[] memory bitIndices = new uint8[](numFeatures); + + // Set random bits in the bitmap. + for (uint256 i = 0; i < numFeatures; i++) { + uint8 bitIndex = uint8(vm.randomUint(0, 255)); + bitIndices[i] = bitIndex; + bitmap |= uint256(1) << bitIndex; + } + + OPContractsManagerContainer container = _deploy(bytes32(bitmap)); + + // Verify each feature is enabled. + for (uint256 i = 0; i < numFeatures; i++) { + bytes32 feature = bytes32(uint256(1) << bitIndices[i]); + assertTrue(container.isDevFeatureEnabled(feature)); + } + } +} + +/// @title OPContractsManagerContainer_DevFeatureBitmap_Test +/// @notice Tests the devFeatureBitmap() getter. +contract OPContractsManagerContainer_DevFeatureBitmap_Test is OPContractsManagerContainer_TestInit { + /// @notice Tests that devFeatureBitmap() returns the value provided at construction. + /// @param _devFeatureBitmap The dev feature bitmap to use. + function testFuzz_devFeatureBitmap_succeeds(bytes32 _devFeatureBitmap) public { + OPContractsManagerContainer container = _deploy(_devFeatureBitmap); + + assertEq(container.devFeatureBitmap(), _devFeatureBitmap); + } +} diff --git a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol index 6f69658758a..314edd4beba 100644 --- a/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol +++ b/packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol @@ -10,7 +10,7 @@ import { DisputeGames } from "test/setup/DisputeGames.sol"; import { Config } from "scripts/libraries/Config.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Claim } from "src/dispute/lib/LibUDT.sol"; -import { GameTypes } from "src/dispute/lib/Types.sol"; +import { GameType, GameTypes } from "src/dispute/lib/Types.sol"; import { DevFeatures } from "src/libraries/DevFeatures.sol"; // Interfaces @@ -119,10 +119,12 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { /// @param _opcm The OPCM contract to reference for shared components. /// @param _delegateCaller The address of the delegate caller to use for superchain upgrade. /// @param _revertBytes The bytes of the revert to expect. + /// @param _expectedValidatorErrors The StandardValidator errors to expect. function _runOpcmV2UpgradeAndChecks( IOPContractsManagerV2 _opcm, address _delegateCaller, - bytes memory _revertBytes + bytes memory _revertBytes, + string memory _expectedValidatorErrors ) internal { @@ -199,6 +201,19 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { // try to apply to this function call instead. IOPContractsManagerStandardValidator validator = _opcm.standardValidator(); + // Expect validator errors if the user provides them. We always expect the L1PAOMultisig + // and Challenger overrides so we don't need to repeat them here. + if (bytes(_expectedValidatorErrors).length > 0) { + vm.expectRevert( + bytes( + string.concat( + "OPContractsManagerStandardValidator: OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER,", + _expectedValidatorErrors + ) + ) + ); + } + // Run the StandardValidator checks. if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { validator.validateWithOverrides( @@ -247,14 +262,28 @@ contract OPContractsManagerV2_Upgrade_TestInit is CommonTest, DisputeGames { /// @notice Executes the current V2 upgrade and checks the results. /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. function runCurrentUpgradeV2(address _delegateCaller) public { - _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, bytes("")); + _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, bytes(""), ""); } /// @notice Executes the current V2 upgrade and expects reverts. /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. /// @param _revertBytes The bytes of the revert to expect. function runCurrentUpgradeV2(address _delegateCaller, bytes memory _revertBytes) public { - _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, _revertBytes); + _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, _revertBytes, ""); + } + + /// @notice Executes the current V2 upgrade and expects reverts. + /// @param _delegateCaller The address of the delegate caller to use for the superchain upgrade. + /// @param _revertBytes The bytes of the revert to expect. + /// @param _expectedValidatorErrors The StandardValidator errors to expect. + function runCurrentUpgradeV2( + address _delegateCaller, + bytes memory _revertBytes, + string memory _expectedValidatorErrors + ) + public + { + _runOpcmV2UpgradeAndChecks(opcmV2, _delegateCaller, _revertBytes, _expectedValidatorErrors); } } @@ -443,6 +472,102 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI ) ); } + + /// @notice Tests that repeatedly upgrading can enable a previously disabled game type. + function test_upgrade_enableGameType_succeeds() public { + uint256 originalBond = disputeGameFactory.initBonds(GameTypes.CANNON); + + // First, disable Cannon and clear its bond so the factory entry is removed. + v2UpgradeInput.disputeGameConfigs[0].enabled = false; + v2UpgradeInput.disputeGameConfigs[0].initBond = 0; + runCurrentUpgradeV2(chainPAO, hex"", "PLDG-10"); + assertEq(address(disputeGameFactory.gameImpls(GameTypes.CANNON)), address(0), "game impl not cleared"); + + // Re-enable Cannon and restore its bond so that it is re-installed. + v2UpgradeInput.disputeGameConfigs[0].enabled = true; + v2UpgradeInput.disputeGameConfigs[0].initBond = originalBond; + runCurrentUpgradeV2(chainPAO); + assertEq( + address(disputeGameFactory.gameImpls(GameTypes.CANNON)), + opcmV2.implementations().faultDisputeGameV2Impl, + "game impl not restored" + ); + assertEq(disputeGameFactory.initBonds(GameTypes.CANNON), originalBond, "init bond not restored"); + } + + /// @notice Tests that disabling a game type removes it from the factory. + function test_upgrade_disableGameType_succeeds() public { + // Establish the baseline where Cannon is enabled. + runCurrentUpgradeV2(chainPAO); + assertEq( + address(disputeGameFactory.gameImpls(GameTypes.CANNON)), + opcmV2.implementations().faultDisputeGameV2Impl, + "initial game impl mismatch" + ); + + // Disable Cannon and zero its bond, then ensure it is removed. + v2UpgradeInput.disputeGameConfigs[0].enabled = false; + v2UpgradeInput.disputeGameConfigs[0].initBond = 0; + runCurrentUpgradeV2(chainPAO, hex"", "PLDG-10"); + assertEq(address(disputeGameFactory.gameImpls(GameTypes.CANNON)), address(0), "game impl not cleared"); + assertEq(disputeGameFactory.initBonds(GameTypes.CANNON), 0, "init bond not cleared"); + assertEq(disputeGameFactory.gameArgs(GameTypes.CANNON), bytes(""), "game args not cleared"); + } + + /// @notice Tests that the upgrade flow can update the Cannon and Permissioned prestate. + function test_upgrade_updatePrestate_succeeds() public { + skipIfDevFeatureDisabled(DevFeatures.OPCM_V2); + + // Run baseline upgrade and capture the current prestates. + runCurrentUpgradeV2(chainPAO); + assertEq( + _gameArgsAbsolutePrestate(GameTypes.CANNON), + Claim.unwrap(cannonPrestate), + "baseline cannon prestate mismatch" + ); + assertEq( + _gameArgsAbsolutePrestate(GameTypes.PERMISSIONED_CANNON), + Claim.unwrap(cannonPrestate), + "baseline permissioned prestate mismatch" + ); + + // Prepare new prestates. + Claim newPrestate = Claim.wrap(bytes32(keccak256("new cannon prestate"))); + cannonPrestate = newPrestate; + + // Update the dispute game configs to point at the new prestates. + v2UpgradeInput.disputeGameConfigs[0].gameArgs = + abi.encode(IOPContractsManagerV2.FaultDisputeGameConfig({ absolutePrestate: newPrestate })); + v2UpgradeInput.disputeGameConfigs[1].gameArgs = abi.encode( + IOPContractsManagerV2.PermissionedDisputeGameConfig({ + absolutePrestate: newPrestate, + proposer: permissionedGameProposer(disputeGameFactory), + challenger: permissionedGameChallenger(disputeGameFactory) + }) + ); + + // Run the upgrade again and ensure prestates updated. + runCurrentUpgradeV2(chainPAO); + assertEq(_gameArgsAbsolutePrestate(GameTypes.CANNON), Claim.unwrap(newPrestate), "cannon prestate not updated"); + assertEq( + _gameArgsAbsolutePrestate(GameTypes.PERMISSIONED_CANNON), + Claim.unwrap(newPrestate), + "permissioned prestate not updated" + ); + } + + /// @notice Extracts the absolute prestate embedded in a dispute game config. + /// @param _gameType Game type to inspect. + /// @return prestate_ The absolute prestate stored in the factory's game args. + function _gameArgsAbsolutePrestate(GameType _gameType) internal view returns (bytes32 prestate_) { + bytes memory args = disputeGameFactory.gameArgs(_gameType); + if (args.length == 0) { + return bytes32(0); + } + assembly { + prestate_ := mload(add(args, 0x20)) + } + } } /// @title OPContractsManagerV2_UpgradeSuperchain_Test