Skip to content

Commit

Permalink
feat: rollkit start running application logic of the rollup (rollkit#…
Browse files Browse the repository at this point in the history
…1680)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added functionality to intercept and run an entry point specified in a
TOML configuration file.
- Introduced a new command for initializing TOML files with specific
configurations.

- **Bug Fixes**
- Ensured proper handling of configuration read errors and empty entry
points during interception.

- **Documentation**
- Updated documentation to include new TOML command operations and
initialization steps.

- **Tests**
- Added tests for the intercept command, covering successful
interception, configuration read errors, and empty entry points.

- **Chores**
- Added a new dependency on `github.com/BurntSushi/toml` version
`v1.3.2`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Matthew Sevey <15232757+MSevey@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 13, 2024
1 parent 3368b73 commit c2bcb6c
Show file tree
Hide file tree
Showing 12 changed files with 715 additions and 10 deletions.
117 changes: 117 additions & 0 deletions cmd/rollkit/commands/intercept.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package commands

import (
"fmt"
"os"
"os/exec"
"path/filepath"

cometos "github.com/cometbft/cometbft/libs/os"
"github.com/spf13/cobra"

rollconf "github.com/rollkit/rollkit/config"
)

const rollupBinEntrypoint = "entrypoint"

var rollkitConfig rollconf.TomlConfig

// InterceptCommand intercepts the command and runs it against the `entrypoint`
// specified in the rollkit.toml configuration file.
func InterceptCommand(
rollkitCommand *cobra.Command,
readToml func() (rollconf.TomlConfig, error),
runEntrypoint func(*rollconf.TomlConfig, []string) error,
) (bool, error) {
// Grab flags and verify command
flags := []string{}
if len(os.Args) >= 2 {
flags = os.Args[1:]

// Handle specific cases first for help, version, and start
switch os.Args[1] {
case "help", "--help", "h", "-h",
"version", "--version", "v", "-v":
return false, nil
case "start":
goto readTOML
}

// Check if user attempted to run a rollkit command
for _, cmd := range rollkitCommand.Commands() {
if os.Args[1] == cmd.Use {
return false, nil
}
}
}

readTOML:
var err error
rollkitConfig, err = readToml()
if err != nil {
return false, err
}

// To avoid recursive calls, we check if the root directory is the rollkit repository itself
if filepath.Base(rollkitConfig.RootDir) == "rollkit" {
return false, nil
}

// After successfully reading the TOML file, we expect to be able to use the entrypoint
if rollkitConfig.Entrypoint == "" {
return true, fmt.Errorf("no entrypoint specified in %s", rollconf.RollkitToml)
}

return true, runEntrypoint(&rollkitConfig, flags)
}

// RunRollupEntrypoint runs the entrypoint specified in the rollkit.toml configuration file.
// If the entrypoint is not built, it will build it first. The entrypoint is built
// in the same directory as the rollkit.toml file. The entrypoint is run with the
// same flags as the original command, but with the `--home` flag set to the config
// directory of the chain specified in the rollkit.toml file. This is so the entrypoint,
// which is a separate binary of the rollup, can read the correct chain configuration files.
func RunRollupEntrypoint(rollkitConfig *rollconf.TomlConfig, args []string) error {
var entrypointSourceFile string
if !filepath.IsAbs(rollkitConfig.RootDir) {
entrypointSourceFile = filepath.Join(rollkitConfig.RootDir, rollkitConfig.Entrypoint)
} else {
entrypointSourceFile = rollkitConfig.Entrypoint
}

// The entrypoint binary file is always in the same directory as the rollkit.toml file.
entrypointBinaryFile := filepath.Join(rollkitConfig.RootDir, rollupBinEntrypoint)

if !cometos.FileExists(entrypointBinaryFile) {
if !cometos.FileExists(entrypointSourceFile) {
return fmt.Errorf("no entrypoint file: %s", entrypointSourceFile)
}

// try to build the entrypoint as a go binary
buildArgs := []string{"build", "-o", entrypointBinaryFile, entrypointSourceFile}
buildCmd := exec.Command("go", buildArgs...) //nolint:gosec
buildCmd.Stdout = os.Stdout
buildCmd.Stderr = os.Stderr
if err := buildCmd.Run(); err != nil {
return fmt.Errorf("failed to build entrypoint: %w", err)
}
}

var runArgs []string
runArgs = append(runArgs, args...)
if rollkitConfig.Chain.ConfigDir != "" {
// The entrypoint is a separate binary based on https://github.com/rollkit/cosmos-sdk, so
// we have to pass --home flag to the entrypoint to read the correct chain configuration files if specified.
runArgs = append(runArgs, "--home", rollkitConfig.Chain.ConfigDir)
}

entrypointCmd := exec.Command(entrypointBinaryFile, runArgs...) //nolint:gosec
entrypointCmd.Stdout = os.Stdout
entrypointCmd.Stderr = os.Stderr

if err := entrypointCmd.Run(); err != nil {
return fmt.Errorf("failed to run entrypoint: %w", err)
}

return nil
}
142 changes: 142 additions & 0 deletions cmd/rollkit/commands/intercept_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package commands

import (
"errors"
"os"
"testing"

rollconf "github.com/rollkit/rollkit/config"

"github.com/spf13/cobra"
)

func TestInterceptCommand(t *testing.T) {
tests := []struct {
name string
rollkitCommands []string
mockReadToml func() (rollconf.TomlConfig, error)
mockRunEntrypoint func(rollkitConfig *rollconf.TomlConfig, args []string) error
args []string
wantErr bool
wantExecuted bool
}{
{
name: "Successful intercept with entrypoint",
rollkitCommands: []string{"docs-gen", "toml"},
mockReadToml: func() (rollconf.TomlConfig, error) {
return rollconf.TomlConfig{
Entrypoint: "test-entrypoint",
Chain: rollconf.ChainTomlConfig{ConfigDir: "/test/config"},

RootDir: "/test/root",
}, nil
},
mockRunEntrypoint: func(config *rollconf.TomlConfig, flags []string) error {
return nil
},
args: []string{"rollkit", "start"},
wantErr: false,
wantExecuted: true,
},
{
name: "Configuration read error",
rollkitCommands: []string{"docs-gen", "toml"},
mockReadToml: func() (rollconf.TomlConfig, error) {
return rollconf.TomlConfig{}, errors.New("read error")
},
args: []string{"rollkit", "start"},
wantErr: true,
wantExecuted: false,
},
{
name: "Empty entrypoint",
rollkitCommands: []string{"docs-gen", "toml"},
mockReadToml: func() (rollconf.TomlConfig, error) {
return rollconf.TomlConfig{Entrypoint: ""}, nil
},
args: []string{"rollkit", "start"},
wantErr: true,
wantExecuted: true,
},
{
name: "Skip intercept, rollkit command",
rollkitCommands: []string{"docs-gen", "toml"},
mockReadToml: func() (rollconf.TomlConfig, error) {
return rollconf.TomlConfig{
Entrypoint: "test-entrypoint",
Chain: rollconf.ChainTomlConfig{ConfigDir: "/test/config"},

RootDir: "/test/root",
}, nil
},
mockRunEntrypoint: func(config *rollconf.TomlConfig, flags []string) error {
return nil
},
args: []string{"rollkit", "docs-gen"},
wantErr: false,
wantExecuted: false,
},
{
name: "Skip intercept, help command",
rollkitCommands: []string{"docs-gen", "toml"},
mockReadToml: func() (rollconf.TomlConfig, error) {
return rollconf.TomlConfig{
Entrypoint: "test-entrypoint",
Chain: rollconf.ChainTomlConfig{ConfigDir: "/test/config"},

RootDir: "/test/root",
}, nil
},
mockRunEntrypoint: func(config *rollconf.TomlConfig, flags []string) error {
return nil
},
args: []string{"rollkit", "-h"},
wantErr: false,
wantExecuted: false,
},
{
name: "Skip intercept, rollkit repository itself",
rollkitCommands: []string{"docs-gen", "toml"},
mockReadToml: func() (rollconf.TomlConfig, error) {
return rollconf.TomlConfig{
Entrypoint: "test-entrypoint",
Chain: rollconf.ChainTomlConfig{ConfigDir: "/test/config"},

RootDir: "/test/rollkit",
}, nil
},
mockRunEntrypoint: func(config *rollconf.TomlConfig, flags []string) error {
return nil
},
args: []string{"rollkit", "start"},
wantErr: false,
wantExecuted: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Args = tt.args

cmd := &cobra.Command{Use: "test"}
for _, c := range tt.rollkitCommands {
cmd.AddCommand(&cobra.Command{Use: c})
}

ok, err := InterceptCommand(
cmd,
tt.mockReadToml,
tt.mockRunEntrypoint,
)
if (err != nil) != tt.wantErr {
t.Errorf("InterceptCommand() error = %v, wantErr %v", err, tt.wantErr)
return
}
if ok != tt.wantExecuted {
t.Errorf("InterceptCommand() executed = %v, wantExecuted %v", ok, tt.wantExecuted)
return
}
})
}

}
19 changes: 9 additions & 10 deletions cmd/rollkit/commands/run_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ var (
// initialize the config with the cometBFT defaults
config = cometconf.DefaultConfig()

// initialize the rollkit configuration
rollkitConfig = rollconf.DefaultNodeConfig
// initialize the rollkit node configuration
nodeConfig = rollconf.DefaultNodeConfig

// initialize the logger with the cometBFT defaults
logger = cometlog.NewTMLogger(cometlog.NewSyncWriter(os.Stdout))
Expand All @@ -52,7 +52,6 @@ func NewRunNodeCmd() *cobra.Command {
Short: "Run the rollkit node",
// PersistentPreRunE is used to parse the config and initial the config files
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Parse the config
err := parseConfig(cmd)
if err != nil {
return err
Expand Down Expand Up @@ -105,8 +104,8 @@ func NewRunNodeCmd() *cobra.Command {
}

// get the node configuration
rollconf.GetNodeConfig(&rollkitConfig, config)
if err := rollconf.TranslateAddresses(&rollkitConfig); err != nil {
rollconf.GetNodeConfig(&nodeConfig, config)
if err := rollconf.TranslateAddresses(&nodeConfig); err != nil {
return err
}

Expand All @@ -115,7 +114,7 @@ func NewRunNodeCmd() *cobra.Command {

// handle lazy aggregator mode
if lazyAgg := cmd.Flags().Lookup("rollkit.lazy_aggregator"); lazyAgg.Changed {
rollkitConfig.LazyAggregator = lazyAgg.Value.String() == "true"
nodeConfig.LazyAggregator = lazyAgg.Value.String() == "true"
}

// use mock jsonrpc da server by default
Expand All @@ -131,10 +130,10 @@ func NewRunNodeCmd() *cobra.Command {
// create the rollkit node
rollnode, err := rollnode.NewNode(
context.Background(),
rollkitConfig,
nodeConfig,
p2pKey,
signingKey,
cometproxy.DefaultClientCreator(config.ProxyApp, config.ABCI, rollkitConfig.DBPath),
cometproxy.DefaultClientCreator(config.ProxyApp, config.ABCI, nodeConfig.DBPath),
genDoc,
metrics,
logger,
Expand Down Expand Up @@ -197,7 +196,7 @@ func NewRunNodeCmd() *cobra.Command {

// use aggregator by default
if !cmd.Flags().Lookup("rollkit.aggregator").Changed {
rollkitConfig.Aggregator = true
nodeConfig.Aggregator = true
}
return cmd
}
Expand All @@ -217,7 +216,7 @@ func addNodeFlags(cmd *cobra.Command) {

// startMockDAServJSONRPC starts a mock JSONRPC server
func startMockDAServJSONRPC(ctx context.Context) (*proxy.Server, error) {
addr, _ := url.Parse(rollkitConfig.DAAddress)
addr, _ := url.Parse(nodeConfig.DAAddress)
srv := proxy.NewServer(addr.Hostname(), addr.Port(), goDATest.NewDummyDA())
err := srv.Start(ctx)
if err != nil {
Expand Down
67 changes: 67 additions & 0 deletions cmd/rollkit/commands/toml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package commands

import (
"fmt"
"os"

rollconf "github.com/rollkit/rollkit/config"

"github.com/spf13/cobra"
)

// NewTomlCmd creates a new cobra command group for TOML file operations.
func NewTomlCmd() *cobra.Command {
TomlCmd := &cobra.Command{
Use: "toml",
Short: "TOML file operations",
Long: `This command group is used to interact with TOML files.`,
Example: ` rollkit toml init`,
}

TomlCmd.AddCommand(initCmd)

return TomlCmd
}

var initCmd = &cobra.Command{
Use: "init",
Short: fmt.Sprintf("Initialize a new %s file", rollconf.RollkitToml),
Long: fmt.Sprintf("This command initializes a new %s file in the current directory.", rollconf.RollkitToml),
Run: func(cmd *cobra.Command, args []string) {
if _, err := os.Stat(rollconf.RollkitToml); err == nil {
fmt.Printf("%s file already exists in the current directory.\n", rollconf.RollkitToml)
os.Exit(1)
}

// try find main.go file under the current directory
dirName, entrypoint := rollconf.FindEntrypoint()
if entrypoint == "" {
fmt.Println("Could not find a rollup main.go entrypoint under the current directory. Please put an entrypoint in the rollkit.toml file manually.")
} else {
fmt.Printf("Found rollup entrypoint: %s, adding to rollkit.toml\n", entrypoint)
}

// checking for default cosmos chain config directory
chainConfigDir, ok := rollconf.FindConfigDir(dirName)
if !ok {
fmt.Printf("Could not find rollup config under %s. Please put the chain.config_dir in the rollkit.toml file manually.\n", chainConfigDir)
} else {
fmt.Printf("Found rollup configuration under %s, adding to rollkit.toml\n", chainConfigDir)
}

config := rollconf.TomlConfig{
Entrypoint: entrypoint,
Chain: rollconf.ChainTomlConfig{
ConfigDir: chainConfigDir,
},
}

// marshal the config to a toml file in the current directory
if err := rollconf.WriteTomlConfig(config); err != nil {
fmt.Println("Error writing rollkit.toml file:", err)
os.Exit(1)
}

fmt.Printf("Initialized %s file in the current directory.\n", rollconf.RollkitToml)
},
}
Loading

0 comments on commit c2bcb6c

Please sign in to comment.