Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: overhaul lazy init in gnoland start #1985

Merged
merged 21 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 78 additions & 18 deletions docs/gno-tooling/cli/gnoland.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,88 @@ id: gno-tooling-gnoland

# gnoland

## Run a Gnoland Node
## Overview

Start a node on the Gnoland blockchain with the following command.
`gnoland` is the Gno.land blockchain client binary, which is capable of managing node working files, as well
as starting the blockchain client itself.

```bash
gnoland
```shell
gnoland --help

USAGE
<subcommand> [flags] [<arg>...]

starts the gnoland blockchain node.

SUBCOMMANDS
start run the full node
secrets gno secrets manipulation suite
config gno config manipulation suite
genesis gno genesis manipulation suite
init initializes the default node secrets / configuration
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
```

## `gnoland init`

`gnoland init` is supposed to initialize the node's working directory in the given path. The node's data directory is
comprised initially from the node's secrets and config (default values).

It is meant to be an initial step in starting the gno blockchain client, as the client itself cannot run without secrets
data like private keys, and a configuration. When the blockchain client is started, it will initialize on its own
relevant DB working directories inside the node directory.

```shell
gnoland init --help

USAGE
init [flags]

initializes the node directory containing the secrets and configuration files

FLAGS
-data-dir gnoland-data the path to the node's data directory
-force=false overwrite existing data, if any
```

### **Sub Commands**
| Command | Description |
| --------- | ----------------- |
| `start` | Run the full node |
### Example usage

#### Generating fresh secrets / config

### **Options**
To initialize the node secrets and configuration to `./example-node-data`, run the following command:

| Name | Type | Description |
|----------------------------| ------- | --------------------------------------------------------------------------------------- |
| `chainid` | String | The id of the chain (default: `dev`). |
| `genesis-balances-file` | String | The initial GNOT distribution file (default: `./gnoland/genesis/genesis_balances.txt`). |
| `genesis-remote` | String | Replacement '%%REMOTE%%' in genesis (default: `"localhost:26657"`). |
| `genesis-txs-file` | String | Initial txs to be executed (default: `"./gnoland/genesis/genesis_txs.jsonl"`). |
| `data-dir` | String | directory for config and data (default: `gnoland-data`). |
| `skip-failing-genesis-txs` | Boolean | Skips transactions that fail from the `genesis-txs-file` |
| `skip-start` | Boolean | Quits after initialization without starting the node. |
```shell
gnoland init --data-dir ./example-node-data
```

This will initialize the following directory structure:

```shell
.
└── example-node-data/
├── secrets/
│ ├── priv_validator_state.json
│ ├── node_key.json
│ └── priv_validator_key.json
└── config/
└── config.toml
```

#### Overwriting the secrets / config

In case there is an already existing node directory at the given path, you will need to provide an additional `--force`
flag to enable data overwrite.

:::warning Back up any secrets

Running `gnoland init` will generate completely new node secrets (validator private key, node p2p key), so make sure
you back up any existing secrets (located at `<node-dir>/secrets`) if you intend to overwrite them, in case you don't
want to lose them.

:::

Following up from the previous example where our desired node directory is `example-node-data` - to
initialize a completely new node data directory, with overwriting any existing data, run the following command:

```shell
gnoland init --data-dir ./example-node-data --force
```
14 changes: 13 additions & 1 deletion gno.land/cmd/gnoland/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package main
import (
"flag"
"fmt"
"path/filepath"
"reflect"
"strings"

"github.com/gnolang/gno/tm2/pkg/bft/config"
"github.com/gnolang/gno/tm2/pkg/commands"
)

Expand Down Expand Up @@ -39,11 +41,21 @@ func (c *configCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(
&c.configPath,
"config-path",
"./config.toml",
constructConfigPath(defaultNodeDir),
"the path for the config.toml",
)
}

// constructConfigPath constructs the default config path, using
// the given node directory
func constructConfigPath(nodeDir string) string {
return filepath.Join(
nodeDir,
config.DefaultConfigDir,
config.DefaultConfigFileName,
)
}

// getFieldAtPath fetches the given field from the given path
func getFieldAtPath(currentValue reflect.Value, path []string) (*reflect.Value, error) {
// Look at the current section, and figure out if
Expand Down
35 changes: 33 additions & 2 deletions gno.land/cmd/gnoland/config_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@
import (
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"

"github.com/gnolang/gno/tm2/pkg/bft/config"
"github.com/gnolang/gno/tm2/pkg/commands"
osm "github.com/gnolang/gno/tm2/pkg/os"
)

var errInvalidConfigOutputPath = errors.New("invalid config output path provided")

type configInitCfg struct {
configCfg

forceOverwrite bool
}

// newConfigInitCmd creates the config init command
func newConfigInitCmd(io commands.IO) *commands.Command {
cfg := &configCfg{}
cfg := &configInitCfg{}

cmd := commands.NewCommand(
commands.Metadata{
Expand All @@ -32,15 +42,36 @@
return cmd
}

func execConfigInit(cfg *configCfg, io commands.IO) error {
func (c *configInitCfg) RegisterFlags(fs *flag.FlagSet) {
c.configCfg.RegisterFlags(fs)

fs.BoolVar(
&c.forceOverwrite,
"force",
false,
"overwrite existing config.toml, if any",
)
}

func execConfigInit(cfg *configInitCfg, io commands.IO) error {
// Check the config output path
if cfg.configPath == "" {
return errInvalidConfigOutputPath
}

// Make sure overwriting the config is enabled
if osm.FileExists(cfg.configPath) && !cfg.forceOverwrite {
return errOverwriteNotEnabled
}

// Get the default config
c := config.DefaultConfig()

// Make sure the path is created
if err := os.MkdirAll(filepath.Dir(cfg.configPath), 0o755); err != nil {
return fmt.Errorf("unable to create config dir, %w", err)

Check warning on line 72 in gno.land/cmd/gnoland/config_init.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoland/config_init.go#L72

Added line #L72 was not covered by tests
}

// Save the config to the path
if err := config.WriteConfigFile(cfg.configPath, c); err != nil {
return fmt.Errorf("unable to initialize config, %w", err)
Expand Down
69 changes: 69 additions & 0 deletions gno.land/cmd/gnoland/config_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,73 @@ func TestConfig_Init(t *testing.T) {
assert.NoError(t, cfg.ValidateBasic())
assert.Equal(t, cfg, config.DefaultConfig())
})

t.Run("unable to overwrite config", func(t *testing.T) {
t.Parallel()

// Create a temporary directory
tempDir := t.TempDir()
path := filepath.Join(tempDir, "config.toml")

// Create the command
cmd := newRootCmd(commands.NewTestIO())
args := []string{
"config",
"init",
"--config-path",
path,
}

// Run the command
cmdErr := cmd.ParseAndRun(context.Background(), args)
require.NoError(t, cmdErr)

// Verify the config is valid
cfg, err := config.LoadConfigFile(path)
require.NoError(t, err)

assert.NoError(t, cfg.ValidateBasic())
assert.Equal(t, cfg, config.DefaultConfig())

// Try to initialize again, expecting failure
cmd = newRootCmd(commands.NewTestIO())

cmdErr = cmd.ParseAndRun(context.Background(), args)
assert.ErrorIs(t, cmdErr, errOverwriteNotEnabled)
})

t.Run("config overwritten", func(t *testing.T) {
t.Parallel()

// Create a temporary directory
tempDir := t.TempDir()
path := filepath.Join(tempDir, "config.toml")

// Create the command
cmd := newRootCmd(commands.NewTestIO())
args := []string{
"config",
"init",
"--force",
"--config-path",
path,
}

// Run the command
cmdErr := cmd.ParseAndRun(context.Background(), args)
require.NoError(t, cmdErr)

// Verify the config is valid
cfg, err := config.LoadConfigFile(path)
require.NoError(t, err)

assert.NoError(t, cfg.ValidateBasic())
assert.Equal(t, cfg, config.DefaultConfig())

// Try to initialize again, expecting success
cmd = newRootCmd(commands.NewTestIO())

cmdErr = cmd.ParseAndRun(context.Background(), args)
assert.NoError(t, cmdErr)
})
}
84 changes: 84 additions & 0 deletions gno.land/cmd/gnoland/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"context"
"flag"
"fmt"
"path/filepath"

"github.com/gnolang/gno/tm2/pkg/commands"
)

type initCfg struct {
dataDir string
forceOverwrite bool
}

// newInitCmd creates the gnoland init command
func newInitCmd(io commands.IO) *commands.Command {
cfg := &initCfg{}

return commands.NewCommand(
commands.Metadata{
Name: "init",
ShortUsage: "init [flags]",
ShortHelp: "initializes the default node secrets / configuration",
moul marked this conversation as resolved.
Show resolved Hide resolved
LongHelp: "initializes the node directory containing the secrets and configuration files",
},
cfg,
func(_ context.Context, _ []string) error {
return execInit(cfg, io)
},
)
}

func (c *initCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(
&c.dataDir,
"data-dir",
defaultNodeDir,
"the path to the node's data directory",
)

fs.BoolVar(
&c.forceOverwrite,
"force",
false,
"overwrite existing data, if any",
)
}

func execInit(cfg *initCfg, io commands.IO) error {
// Create the gnoland config options
config := &configInitCfg{
configCfg: configCfg{
configPath: constructConfigPath(cfg.dataDir),
},
forceOverwrite: cfg.forceOverwrite,
}

// Run gnoland config init
if err := execConfigInit(config, io); err != nil {
return fmt.Errorf("unable to initialize config, %w", err)
}

// Create the gnoland secrets options
secrets := &secretsInitCfg{
commonAllCfg: commonAllCfg{
dataDir: constructSecretsPath(cfg.dataDir),
},
forceOverwrite: cfg.forceOverwrite,
}

// Run gnoland secrets init
if err := execSecretsInit(secrets, []string{}, io); err != nil {
return fmt.Errorf("unable to initialize secrets, %w", err)

Check warning on line 75 in gno.land/cmd/gnoland/init.go

View check run for this annotation

Codecov / codecov/patch

gno.land/cmd/gnoland/init.go#L75

Added line #L75 was not covered by tests
}

io.Println()

io.Printfln("Successfully initialized default node config at %q", filepath.Dir(config.configPath))
io.Printfln("Successfully initialized default node secrets at %q", secrets.dataDir)

return nil
}
Loading
Loading