diff --git a/docs/docs/build/create-an-app-with-monomer.md b/docs/docs/build/create-an-app-with-monomer.md index e167addf..b825fd5f 100644 --- a/docs/docs/build/create-an-app-with-monomer.md +++ b/docs/docs/build/create-an-app-with-monomer.md @@ -45,10 +45,16 @@ If using a Go version `>=1.23.0`, run: go build -ldflags=-checklinkname=0 -o testappd ./cmd/testappd ```` -Now that our application is configured, we can start the Monomer application by running the following command. +Now that our application is configured, we can start the Monomer application in sequencer mode by running the following command. ```bash -./testappd monomer start --minimum-gas-prices 0.01wei --monomer.dev-start --api.enable +./testappd monomer start --minimum-gas-prices 0.01wei --monomer.sequencer --monomer.dev-start --api.enable ```` +To run the application in verifier mode, omit the `--monomer.sequencer` flag: + +```bash +./testappd monomer start --minimum-gas-prices 0.01wei --monomer.dev-start --api.enable +``` + Congratulations! You've successfully integrated Monomer into your Cosmos SDK application. diff --git a/docs/docs/build/interact.md b/docs/docs/build/interact.md index ef7891a1..93f4edf3 100644 --- a/docs/docs/build/interact.md +++ b/docs/docs/build/interact.md @@ -10,7 +10,7 @@ We will need to have an account on L1 with funds. To give yourself funds on the devnet at genesis, run the devnet start command specified in the last tutorial with the `--monomer.dev.l1-user-address` flag: ```bash -./testappd monomer start --minimum-gas-prices 0.01wei --monomer.dev-start --api.enable --monomer.dev.l1-user-address "
" +./testappd monomer start --minimum-gas-prices 0.01wei --monomer.sequencer --monomer.dev-start --api.enable --monomer.dev.l1-user-address "
" ``` ## Configuring L1 and L2 Wallets diff --git a/e2e/e2e.go b/e2e/e2e.go index eeee6f39..1e2641ee 100644 --- a/e2e/e2e.go +++ b/e2e/e2e.go @@ -71,6 +71,7 @@ func Run( "monomer", "start", "--minimum-gas-prices", "0.001wei", + "--monomer.sequencer", "--monomer.dev-start", )) appCmd.Dir = appDirPath diff --git a/integrations/integrations.go b/integrations/integrations.go index aaf89b5d..0a2bb7fe 100644 --- a/integrations/integrations.go +++ b/integrations/integrations.go @@ -54,6 +54,7 @@ import ( const ( flagEngineURL = "monomer.engine-url" + flagSequencerMode = "monomer.sequencer" flagDev = "monomer.dev-start" flagL1AllocsPath = "monomer.dev.l1-allocs" flagL1DeploymentsPath = "monomer.dev.l1-deployments" @@ -78,6 +79,7 @@ func AddMonomerCommand(rootCmd *cobra.Command, appCreator servertypes.AppCreator StartCommandHandler: startCommandHandler, AddFlags: func(cmd *cobra.Command) { cmd.Flags().String(flagEngineURL, "ws://127.0.0.1:9000", "url of Monomer's Engine API endpoint") + cmd.Flags().Bool(flagSequencerMode, false, "enable sequencer mode for the Monomer node") cmd.Flags().Bool(flagDev, false, "run the OP Stack devnet in-process for testing") cmd.Flags().String(flagL1URL, "ws://127.0.0.1:9001", "") cmd.Flags().String(flagOPNodeURL, "http://127.0.0.1:9002", "") @@ -302,6 +304,7 @@ func startOPDevnet( if err != nil { return fmt.Errorf("build op config: %v", err) } + opConfig.Node.Driver.SequencerEnabled = v.GetBool(flagSequencerMode) if err := opConfig.Run(ctx, env, logger); err != nil { return fmt.Errorf("run op: %v", err) } diff --git a/opdevnet/op.go b/opdevnet/op.go index 4b99d2c8..baa2570d 100644 --- a/opdevnet/op.go +++ b/opdevnet/op.go @@ -144,6 +144,11 @@ func (cfg *OPConfig) Run(ctx context.Context, env *environment.Env, logger log.L return opNode.Stop(context.Background()) }) + // Do not start the batcher and proposer if the node is running in verifier mode + if !cfg.Node.Driver.SequencerEnabled { + return nil + } + // Proposer proposerService, err := proposer.ProposerServiceFromCLIConfig(ctx, "v0.1", cfg.Proposer, newLogger(logger, "proposer")) if err != nil { diff --git a/opdevnet/opdevnet_test.go b/opdevnet/opdevnet_test.go index d30b7fbb..5206d1f5 100644 --- a/opdevnet/opdevnet_test.go +++ b/opdevnet/opdevnet_test.go @@ -78,14 +78,15 @@ func TestOPDevnet(t *testing.T) { l1Config.BlobsDirPath = t.TempDir() require.NoError(t, l1Config.Run(ctx, env, log.NewLogger(log.NewTerminalHandler(openLogFile(t, env, "l1"), false)))) l1GenesisBlock := l1Config.Genesis.ToBlock() - l1URL.IsReachable(ctx) + require.True(t, l1URL.IsReachable(ctx)) l2Allocs, err := opdevnet.DefaultL2Allocs() require.NoError(t, err) l2Genesis, err := genesis.BuildL2Genesis(deployConfig, l2Allocs, l1GenesisBlock) require.NoError(t, err) - jwtSecret := [32]byte{123} + + // Set up and run a sequencer node l2Node, l2Backend, err := geth.InitL2("l2", l2Genesis.Config.ChainID, l2Genesis, writeJWT(t, jwtSecret), func(_ *ethconfig.Config, nodeCfg *node.Config) error { nodeCfg.AuthAddr = l2EngineURL.Hostname() nodeCfg.AuthPort = int(l2EngineURL.PortU16()) @@ -98,7 +99,7 @@ func TestOPDevnet(t *testing.T) { defer func() { require.NoError(t, l2Node.Close()) }() - l2EngineURL.IsReachable(ctx) + require.True(t, l2EngineURL.IsReachable(ctx)) secrets, err := opdevnet.DefaultMnemonicConfig.Secrets() require.NoError(t, err) @@ -135,6 +136,56 @@ func TestOPDevnet(t *testing.T) { } time.Sleep(l2SlotDuration) } + + // Set up and run a verifier node + verifierL2EngineURL, err := e2eurl.ParseString("ws://127.0.0.1:8894") + require.NoError(t, err) + verifierOpNodeURL, err := e2eurl.ParseString("http://127.0.0.1:8895") + require.NoError(t, err) + verifierL2EthURL, err := e2eurl.ParseString("ws://127.0.0.1:8896") + require.NoError(t, err) + + verifierL2Node, verifierL2Backend, err := geth.InitL2("l2-verifier", l2Genesis.Config.ChainID, l2Genesis, writeJWT(t, jwtSecret), func(_ *ethconfig.Config, nodeCfg *node.Config) error { + nodeCfg.AuthAddr = verifierL2EngineURL.Hostname() + nodeCfg.AuthPort = int(verifierL2EngineURL.PortU16()) + nodeCfg.WSHost = verifierL2EthURL.Hostname() + nodeCfg.WSPort = int(verifierL2EthURL.PortU16()) + return nil + }) + require.NoError(t, err) + require.NoError(t, verifierL2Node.Start()) + defer func() { + require.NoError(t, verifierL2Node.Close()) + }() + require.True(t, verifierL2EngineURL.IsReachable(ctx)) + + verifierOpConfig, err := opdevnet.BuildOPConfig( + deployConfig, + secrets.Batcher, + secrets.Proposer, + l1GenesisBlock, + l1Deployments.L2OutputOracleProxy, + eth.HeaderBlockID(l2Genesis.ToBlock().Header()), + l1URL, + verifierOpNodeURL, + verifierL2EngineURL, + verifierL2EthURL, + jwtSecret, + ) + require.NoError(t, err) + verifierOpConfig.Node.Driver.SequencerEnabled = false + require.NoError(t, verifierOpConfig.Run(ctx, env, log.NewLogger(log.NewTerminalHandler(openLogFile(t, env, "op-verifier"), false)))) + + // Wait for the verifier node to sync to block 10 + for i := 0; i < 30; i++ { + if verifierL2Backend.BlockChain().CurrentHeader().Number.Uint64() >= 10 { + // Assert that the verifier and sequencer state roots at block 10 are equal + require.Equal(t, verifierL2Backend.BlockChain().GetHeaderByNumber(10).Root, l2Backend.BlockChain().GetHeaderByNumber(10).Root) + return + } + time.Sleep(time.Second * time.Duration(l1Config.BlockTime)) + } + t.Fatalf("verifier only synced to block %v", verifierL2Backend.BlockChain().CurrentHeader().Number) } // Copied and slightly modified from optimism/op-e2e.