diff --git a/cmd/cartesi-rollups-cli/root/deps/deps.go b/cmd/cartesi-rollups-cli/root/deps/deps.go new file mode 100644 index 000000000..94b0d92b5 --- /dev/null +++ b/cmd/cartesi-rollups-cli/root/deps/deps.go @@ -0,0 +1,65 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package deps + +import ( + "context" + "os/signal" + "syscall" + + "github.com/cartesi/rollups-node/internal/config" + "github.com/cartesi/rollups-node/internal/deps" + "github.com/spf13/cobra" +) + +var Cmd = &cobra.Command{ + Use: "run-deps", + Short: "Run node dependencies with Docker", + Example: examples, + Run: run, +} + +const examples = `# Run all deps: +cartesi-rollups-cli run-deps` + +var depsConfig = deps.NewDefaultDepsConfig() + +func init() { + Cmd.Flags().StringVar(&depsConfig.PostgresDockerImage, "postgres-docker-image", + deps.DefaultPostgresDockerImage, + "Postgress docker image name") + + Cmd.Flags().StringVar(&depsConfig.PostgresPort, "postgres-mapped-port", + deps.DefaultPostgresPort, + "Postgres local listening port number") + + Cmd.Flags().StringVar(&depsConfig.PostgresPassword, "postgres-password", + deps.DefaultPostgresPassword, + "Postgres password") + + Cmd.Flags().StringVar(&depsConfig.DevnetDockerImage, "devnet-docker-image", + deps.DefaultDevnetDockerImage, + "Devnet docker image name") + + Cmd.Flags().StringVar(&depsConfig.DevnetPort, "devnet-mapped-port", + deps.DefaultDevnetPort, + "devnet local listening port number") +} + +func run(cmd *cobra.Command, args []string) { + + ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + depsContainers, err := deps.Run(ctx, *depsConfig) + cobra.CheckErr(err) + + config.InfoLogger.Println("all deps are up") + + <-ctx.Done() + + err = deps.Terminate(context.Background(), depsContainers) + cobra.CheckErr(err) + +} diff --git a/cmd/cartesi-rollups-cli/root/root.go b/cmd/cartesi-rollups-cli/root/root.go index 2f0e99b06..20a7e9d61 100644 --- a/cmd/cartesi-rollups-cli/root/root.go +++ b/cmd/cartesi-rollups-cli/root/root.go @@ -4,6 +4,7 @@ package root import ( + "github.com/cartesi/rollups-node/cmd/cartesi-rollups-cli/root/deps" "github.com/cartesi/rollups-node/cmd/cartesi-rollups-cli/root/increasetime" "github.com/cartesi/rollups-node/cmd/cartesi-rollups-cli/root/inspect" "github.com/cartesi/rollups-node/cmd/cartesi-rollups-cli/root/read" @@ -27,5 +28,6 @@ func init() { Cmd.AddCommand(inspect.Cmd) Cmd.AddCommand(increasetime.Cmd) Cmd.AddCommand(validate.Cmd) + Cmd.AddCommand(deps.Cmd) Cmd.DisableAutoGenTag = true } diff --git a/docs/cartesi-rollups-cli.md b/docs/cartesi-rollups-cli.md index 840241743..04e88f286 100644 --- a/docs/cartesi-rollups-cli.md +++ b/docs/cartesi-rollups-cli.md @@ -18,6 +18,7 @@ Cartesi Rollups node. * [cartesi-rollups-cli increase-time](cartesi-rollups-cli_increase-time.md) - Increases evm time of the current machine * [cartesi-rollups-cli inspect](cartesi-rollups-cli_inspect.md) - Calls inspect API * [cartesi-rollups-cli read](cartesi-rollups-cli_read.md) - Read the node state from the GraphQL API +* [cartesi-rollups-cli run-deps](cartesi-rollups-cli_run-deps.md) - Run node dependencies with Docker * [cartesi-rollups-cli save-snapshot](cartesi-rollups-cli_save-snapshot.md) - Saves the testing Cartesi machine snapshot to the designated folder * [cartesi-rollups-cli send](cartesi-rollups-cli_send.md) - Send a rollups input to the Ethereum node * [cartesi-rollups-cli validate](cartesi-rollups-cli_validate.md) - Validates a notice diff --git a/docs/cartesi-rollups-cli_deps.md b/docs/cartesi-rollups-cli_deps.md new file mode 100644 index 000000000..c982723b1 --- /dev/null +++ b/docs/cartesi-rollups-cli_deps.md @@ -0,0 +1,15 @@ +## cartesi-rollups-cli deps + +Read the node state from the GraphQL API + +### Options + +``` + -h, --help help for deps +``` + +### SEE ALSO + +* [cartesi-rollups-cli](cartesi-rollups-cli.md) - Command line interface for Cartesi Rollups +* [cartesi-rollups-cli deps start](cartesi-rollups-cli_deps_start.md) - start all deps + diff --git a/docs/cartesi-rollups-cli_deps_start.md b/docs/cartesi-rollups-cli_deps_start.md new file mode 100644 index 000000000..a091a3e88 --- /dev/null +++ b/docs/cartesi-rollups-cli_deps_start.md @@ -0,0 +1,30 @@ +## cartesi-rollups-cli deps start + +start all deps + +``` +cartesi-rollups-cli deps start [flags] +``` + +### Examples + +``` +# Start all deps: +cartesi-rollups-cli deps start +``` + +### Options + +``` + --devnet-docker-image string Devnet docker image name + --devnet-mapped-port string devnet local listening port number + -h, --help help for start + --postgres-docker-image string Postgress docker image name + --postgres-mapped-port string Postgres local listening port number + --postgres-password string Postgres password +``` + +### SEE ALSO + +* [cartesi-rollups-cli deps](cartesi-rollups-cli_deps.md) - Read the node state from the GraphQL API + diff --git a/docs/cartesi-rollups-cli_run-deps.md b/docs/cartesi-rollups-cli_run-deps.md new file mode 100644 index 000000000..4003180b9 --- /dev/null +++ b/docs/cartesi-rollups-cli_run-deps.md @@ -0,0 +1,30 @@ +## cartesi-rollups-cli run-deps + +Run node dependencies with Docker + +``` +cartesi-rollups-cli run-deps [flags] +``` + +### Examples + +``` +# Run all deps: +cartesi-rollups-cli run-deps +``` + +### Options + +``` + --devnet-docker-image string Devnet docker image name (default "cartesi/rollups-devnet:devel") + --devnet-mapped-port string devnet local listening port number (default "8545") + -h, --help help for run-deps + --postgres-docker-image string Postgress docker image name (default "postgres:16-alpine") + --postgres-mapped-port string Postgres local listening port number (default "5432") + --postgres-password string Postgres password (default "password") +``` + +### SEE ALSO + +* [cartesi-rollups-cli](cartesi-rollups-cli.md) - Command line interface for Cartesi Rollups + diff --git a/internal/deps/deps.go b/internal/deps/deps.go new file mode 100644 index 000000000..74124fdce --- /dev/null +++ b/internal/deps/deps.go @@ -0,0 +1,152 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Package deps provides mechanisms to run Node dependencies using docker +package deps + +import ( + "context" + "strings" + "sync" + "time" + + "github.com/cartesi/rollups-node/internal/config" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +const ( + DefaultPostgresDockerImage = "postgres:16-alpine" + DefaultPostgresPort = "5432" + DefaultPostgresPassword = "password" + DefaultDevnetDockerImage = "cartesi/rollups-devnet:devel" + DefaultDevnetPort = "8545" + + numPostgresCheckReadyAttempts = 2 + pollInterval = 5 * time.Second +) + +// Struct to hold Node dependencies containers configurations +type DepsConfig struct { + PostgresDockerImage string + PostgresPort string + PostgresPassword string + DevnetDockerImage string + DevnetPort string +} + +// Builds a DepsConfig struct with default values +func NewDefaultDepsConfig() *DepsConfig { + return &DepsConfig{ + DefaultPostgresDockerImage, + DefaultPostgresPort, + DefaultPostgresPassword, + DefaultDevnetDockerImage, + DefaultDevnetPort, + } +} + +// Struct to represent the Node dependencies containers +type DepsContainers struct { + containers []testcontainers.Container + //Literal copies lock value from waitGroup as sync.WaitGroup contains sync.noCopy + waitGroup *sync.WaitGroup +} + +// A dummy Logging to write all Test Containers logs with DEBUG priority +type debugLogging struct{} + +func (debug debugLogging) Printf(format string, v ...interface{}) { + config.DebugLogger.Printf(format, v...) +} + +func createHook(containerName string, + waitGroup *sync.WaitGroup) []testcontainers.ContainerLifecycleHooks { + return []testcontainers.ContainerLifecycleHooks{ + { + PostTerminates: []testcontainers.ContainerHook{ + func(ctx context.Context, container testcontainers.Container) error { + waitGroup.Done() + return nil + }, + }, + }, + } + +} + +// Run starts the Node dependencies containers. +// The returned DepContainers struct can be used to gracefully +// terminate the containers using the Terminate method +func Run(ctx context.Context, depsConfig DepsConfig) (*DepsContainers, error) { + nolog := debugLogging{} + var waitGroup sync.WaitGroup + + // wait strategy copied from testcontainers docs + postgresWaitStrategy := wait.ForLog("database system is ready to accept connections"). + WithOccurrence(numPostgresCheckReadyAttempts). + WithPollInterval(pollInterval) + + postgresReq := testcontainers.ContainerRequest{ + Image: depsConfig.PostgresDockerImage, + ExposedPorts: []string{strings.Join([]string{ + depsConfig.PostgresPort, ":5432/tcp"}, "")}, + WaitingFor: postgresWaitStrategy, + Name: "rollups-node-dep-postgres", + Env: map[string]string{ + "POSTGRES_PASSWORD": depsConfig.PostgresPassword, + }, + LifecycleHooks: createHook("rollups-node-dep-postgres", &waitGroup), + } + + postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: postgresReq, + Started: true, + Logger: nolog, + }) + + if err != nil { + return nil, err + } + waitGroup.Add(1) + + devNetReq := testcontainers.ContainerRequest{ + Image: depsConfig.DevnetDockerImage, + ExposedPorts: []string{strings.Join([]string{depsConfig.DevnetPort, ":8545/tcp"}, "")}, + WaitingFor: wait.ForLog("Listening on 0.0.0.0:8545"), + Name: "rollups-node-dep-devnet", + Env: map[string]string{ + "ANVIL_IP_ADDR": "0.0.0.0", + }, + LifecycleHooks: createHook("rollups-node-dep-devnet", &waitGroup), + } + + devnet, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: devNetReq, + Started: true, + Logger: nolog, + }) + if err != nil { + return nil, err + } + + waitGroup.Add(1) + + containers := []testcontainers.Container{postgres, devnet} + + return &DepsContainers{containers, &waitGroup}, nil +} + +// Terminate terminates all dependencies containers. This method waits for all the containers +// to terminate or gives an error if it fails to terminate one of the containers +func Terminate(ctx context.Context, depContainers *DepsContainers) error { + + for _, depContainer := range depContainers.containers { + terr := depContainer.Terminate(ctx) + if terr != nil { + return terr + } + } + depContainers.waitGroup.Wait() + return nil +}