diff --git a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif new file mode 100644 index 00000000000..40c79bd91f6 Binary files /dev/null and b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif differ diff --git a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif index 4da21bc863b..4882aabdfde 100644 Binary files a/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif and b/docs/assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif differ diff --git a/docs/getting-started/local-setup/setting-up-a-local-chain.md b/docs/getting-started/local-setup/setting-up-a-local-chain.md index 23b4e110820..30360333208 100644 --- a/docs/getting-started/local-setup/setting-up-a-local-chain.md +++ b/docs/getting-started/local-setup/setting-up-a-local-chain.md @@ -13,13 +13,13 @@ Additionally, you will see the different options you can use to make your Gno in - [`gnoland` installed](local-setup.md#3-installing-other-gno-tools). -## Starting a node with a default configuration +## Starting a local node (lazy init) You can start a Gno blockchain node with the default configuration by navigating to the `gno.land` sub-folder and running the following command: ```bash -gnoland start +gnoland start --lazy ``` The command will trigger a chain initialization process (if you haven't run the node before), and start the Gno node, @@ -27,6 +27,17 @@ which is ready to accept transactions and interact with other Gno nodes. ![gnoland start](../../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start.gif) +:::info Lazy init + +Starting a Gno blockchain node using just the `gnoland start --lazy` command implies a few things: + +- the default configuration will be used, and generated on disk in the `gnoland-data` directory +- random secrets data will be generated (node private keys, networking keys...) +- an entirely new `genesis.json` will be used, and generated on disk in the `../gnoland-data` directory. The genesis + will have a single validator, whose public key is derived from the previously generated node secrets + +::: + To view the command defaults, simply run the `help` command: ```bash @@ -37,10 +48,9 @@ Let's break down the most important default settings: - `chainid` - the ID of the Gno chain. This is used for Gno clients, and distinguishing the chain from other Gno chains (ex. through IBC) -- `config` - the custom node configuration file - for more details on utilizing this file - `genesis-balances-file` - the initial premine balances file, which contains initial native currency allocations for - the chain. By default, the genesis balances file is located in `gno.land/genesis/genesis_balances.txt`, this is also the + the chain. By default, the genesis balances file is located in `gno.land/genesis/genesis_balances.txt`, this is also + the reason why we need to navigate to the `gno.land` sub-folder to run the command with default settings - `data-dir` - the working directory for the node configuration and node data (state DB) @@ -48,15 +58,230 @@ Let's break down the most important default settings: As mentioned, the working directory for the node is located in `data-dir`. To reset the chain, you need to delete this directory and start the node up again. If you are using the default node configuration, you can run -`make fclean` from the `gno.land` sub-folder to delete the `tempdir` working directory. +`make fclean` from the `gno.land` sub-folder to delete the `gnoland-data` working directory. + +::: + +## Starting a local node (manual configuration) + +Manually configuring and starting the Gno blockchain node is a bit more involved than simply initializing it "lazily", +and involves the following steps: + +- generating the node secrets, and configuration +- generating the `genesis.json`, and populating it +- starting the node with the generated data + +### 1. Generate the node directory (secrets + config) + +You can generate the default node directory secrets using the following command: + +```shell +gnoland secrets init +``` + +And generate the default node config using the following command: + +```shell +gnoland config init +``` + +This will initialize the following directory structure: + +```shell +. +└── gnoland-data/ + ├── secrets/ + │ ├── priv_validator_state.json + │ ├── node_key.json + │ └── priv_validator_key.json + └── config/ + └── config.toml +``` + +A couple of things to note: + +- `gnoland config init` initializes a default configuration +- `gnoland secrets init` initializes new node secrets (validator key, node p2p key) + +Essentially, `gnoland start --lazy` is simply a combination of `gnoland secrets generate` and `gnoland config generate`, +with the default options enabled. + +#### Changing the node configuration + +To change the configuration params, such as for example the node's listen address, you can utilize the following +command: + +```shell +gnoland config set rpc.laddr tcp://0.0.0.0:26657 +``` + +This will update the RPC listen address to `0.0.0.0:26657`. You can verify the configuration was updated by running: + +```go +gnoland config get rpc.laddr +``` + +### 2. Generate the `genesis.json` + +:::info Where's the `genesis.json`? + +In this example, we are starting a completely new network. In case you are connecting to an existing network, you don't +need to regenerate the `genesis.json`, but simply fetch it from publicly available resources of the Gno chain you're +trying to connect to. ::: -## Changing the chain ID +Generating an empty `genesis.json` is relatively straightforward: + +```shell +gnoland genesis generate +``` + +The resulting `genesis.json` is empty: + +```json +{ + "genesis_time": "2024-05-08T10:25:09Z", + "chain_id": "dev", + "consensus_params": { + "Block": { + "MaxTxBytes": "1000000", + "MaxDataBytes": "2000000", + "MaxBlockBytes": "0", + "MaxGas": "10000000", + "TimeIotaMS": "100" + }, + "Validator": { + "PubKeyTypeURLs": [ + "/tm.PubKeyEd25519" + ] + } + }, + "app_hash": null +} +``` + +This will generate a `genesis.json` in the calling directory, by default. To check all configurable options when +generating the `genesis.json`, you can run the command using the `--help` flag: -:::info Changing the Gno chain ID has several implications +```shell +gnoland genesis generate --help -- It affects how the Gno node communicates with other Gno nodes / chains +USAGE + generate [flags] + +Generates a node's genesis.json based on specified parameters + +FLAGS + -block-max-data-bytes 2000000 the max size of the block data + -block-max-gas 10000000 the max gas limit for the block + -block-max-tx-bytes 1000000 the max size of the block transaction + -block-time-iota 100 the block time iota (in ms) + -chain-id dev the ID of the chain + -genesis-time 1715163944 the genesis creation time. Defaults to current time + -output-path ./genesis.json the output path for the genesis.json +``` + +### 3. Add the initial validator set + +A new Gno chain cannot advance without an active validator set. +Since this example follows starting a completely new Gno chain, you need to add at least one validator to the validator +set. + +Luckily, we've generated the node secrets in step #1 -- we will utilize the generated node key, so the process we start +locally will be the validator node for the new Gno network. + +To display the generated node key data, run the following command: + +```shell +gnoland secrets get ValidatorPrivateKey +``` + +This will display the information we need for updating the `genesis.json`: + +```shell +[Validator Key Info] + +Address: g10e3smsmusjn00n7j75fk9u4zta8djrlglcv6af +Public Key: gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqhjhrqd7xlhda7spfdtx6lrcjxlk67av46w7eng9z4e2ch478fsk4xmq3j +``` + +Updating the `genesis.json` is relatively simple, running the following command will add the generated node info to the +validator set: + +```shell +gnoland genesis validator add \ +--address g10e3smsmusjn00n7j75fk9u4zta8djrlglcv6af \ +--pub-key gpub1pggj7ard9eg82cjtv4u52epjx56nzwgjyg9zqhjhrqd7xlhda7spfdtx6lrcjxlk67av46w7eng9z4e2ch478fsk4xmq3j \ +--name Cuttlas +``` + +We can verify that the new validator was indeed added to the validator set: + +```json +{ + "genesis_time": "2024-05-08T10:25:09Z", + "chain_id": "dev", + "consensus_params": { + "Block": { + "MaxTxBytes": "1000000", + "MaxDataBytes": "2000000", + "MaxBlockBytes": "0", + "MaxGas": "10000000", + "TimeIotaMS": "100" + }, + "Validator": { + "PubKeyTypeURLs": [ + "/tm.PubKeyEd25519" + ] + } + }, + "validators": [ + { + "address": "g1lz2ez3ceeds9f6jllwy7u0hvkphuuv0plcc8pp", + "pub_key": { + "@type": "/tm.PubKeyEd25519", + "value": "AvaVf/cH84urHNuS1lo3DYmtEErxkTLRsrcr71QoAr4=" + }, + "power": "1", + "name": "Cuttlas" + } + ], + "app_hash": null +} +``` + +### 4. Starting the chain + +We have completed the main aspects of setting up a node: + +- generated the node directory (secrets and configuration) ✅ +- generated a `genesis.json` ✅ +- added an initial validator set to the `genesis.json` ✅ + +Now, we can go ahead and start the Gno chain for the first time, by running: + +```shell +gnoland start \ +--genesis ./genesis.json \ +--data-dir ./gnoland-data +``` + +That's it! 🎉 + +Your new Gno node (chain) should be up and running: + +![gnoland start](../../assets/getting-started/local-setup/setting-up-a-local-chain/gnoland-start-manual.gif) + +## Chain runtime options + +### Changing the chain ID + +:::info Changing the Gno chain ID + +Below are some implications to consider when changing the chain ID: + +- it affects how the Gno node communicates with other Gno nodes / chains - Gno clients that communicate through JSON-RPC need to match this value It's important to configure your node properly before launching it in a distributed network. @@ -109,7 +334,7 @@ have no effect. ::: -## Changing the node configuration +### Changing the node configuration You can specify a node configuration file using the `--config` flag. @@ -117,7 +342,7 @@ You can specify a node configuration file using the `--config` flag. gnoland start --config config.toml ``` -## Changing the premine list +### Changing the premine list You do not need to use the `gno.land/genesis/genesis_balances.txt` file as the source of truth for initial network funds. @@ -140,3 +365,11 @@ Following this pattern, potential entries into the genesis balances file would l g1qpymzwx4l4cy6cerdyajp9ksvjsf20rk5y9rtt=10000000000ugnot g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq=10000000000ugnot ``` + +:::info Genesis generation + +Genesis block generation happens only once during the lifetime of a Gno chain. +This means that if you specify a balances file using `gnoland start`, and the chain has already started (advanced from +block 0), the specified balance sheet will not be applied. + +::: \ No newline at end of file diff --git a/docs/gno-tooling/cli/gnoland.md b/docs/gno-tooling/cli/gnoland.md index 9bd722b0f2c..3d4ba9eac00 100644 --- a/docs/gno-tooling/cli/gnoland.md +++ b/docs/gno-tooling/cli/gnoland.md @@ -4,28 +4,72 @@ 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 +## `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 +``` + +### Example usage + +#### Generating fresh secrets / config + +To initialize the node secrets and configuration to `./example-node-data`, run the following command: + +```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 ``` -### **Sub Commands** -| Command | Description | -| --------- | ----------------- | -| `start` | Run the full node | +#### 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. -### **Options** +:::warning Back up any secrets -| 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. | +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 `/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 +``` diff --git a/gno.land/cmd/gnoland/config.go b/gno.land/cmd/gnoland/config.go index 22d23e3ecb1..8e11e57fd5f 100644 --- a/gno.land/cmd/gnoland/config.go +++ b/gno.land/cmd/gnoland/config.go @@ -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" ) @@ -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 diff --git a/gno.land/cmd/gnoland/config_init.go b/gno.land/cmd/gnoland/config_init.go index be7902b48cd..238db6bd348 100644 --- a/gno.land/cmd/gnoland/config_init.go +++ b/gno.land/cmd/gnoland/config_init.go @@ -3,17 +3,27 @@ package main 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{ @@ -32,15 +42,36 @@ func newConfigInitCmd(io commands.IO) *commands.Command { 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) + } + // Save the config to the path if err := config.WriteConfigFile(cfg.configPath, c); err != nil { return fmt.Errorf("unable to initialize config, %w", err) diff --git a/gno.land/cmd/gnoland/config_init_test.go b/gno.land/cmd/gnoland/config_init_test.go index c576aec1641..03a6d40a239 100644 --- a/gno.land/cmd/gnoland/config_init_test.go +++ b/gno.land/cmd/gnoland/config_init_test.go @@ -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) + }) } diff --git a/gno.land/cmd/gnoland/genesis_validator.go b/gno.land/cmd/gnoland/genesis_validator.go index cf619ac40dc..91d3e4af7dd 100644 --- a/gno.land/cmd/gnoland/genesis_validator.go +++ b/gno.land/cmd/gnoland/genesis_validator.go @@ -44,6 +44,6 @@ func (c *validatorCfg) RegisterFlags(fs *flag.FlagSet) { &c.address, "address", "", - "the output path for the genesis.json", + "the gno bech32 address of the validator", ) } diff --git a/gno.land/cmd/gnoland/root.go b/gno.land/cmd/gnoland/root.go index d670151b358..8df716b1fed 100644 --- a/gno.land/cmd/gnoland/root.go +++ b/gno.land/cmd/gnoland/root.go @@ -33,9 +33,9 @@ func newRootCmd(io commands.IO) *commands.Command { cmd.AddSubCommands( newStartCmd(io), + newGenesisCmd(io), newSecretsCmd(io), newConfigCmd(io), - newGenesisCmd(io), ) return cmd diff --git a/gno.land/cmd/gnoland/secrets.go b/gno.land/cmd/gnoland/secrets.go index 36113a3e207..fda2a664c28 100644 --- a/gno.land/cmd/gnoland/secrets.go +++ b/gno.land/cmd/gnoland/secrets.go @@ -3,7 +3,9 @@ package main import ( "errors" "flag" + "path/filepath" + "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -13,7 +15,6 @@ var ( ) const ( - defaultSecretsDir = "./secrets" defaultValidatorKeyName = "priv_validator_key.json" defaultNodeKeyName = "node_key.json" defaultValidatorStateName = "priv_validator_state.json" @@ -58,7 +59,16 @@ func (c *commonAllCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.dataDir, "data-dir", - defaultSecretsDir, + constructSecretsPath(defaultNodeDir), "the secrets output directory", ) } + +// constructSecretsPath constructs the default secrets path, using +// the given node directory +func constructSecretsPath(nodeDir string) string { + return filepath.Join( + nodeDir, + config.DefaultSecretsDir, + ) +} diff --git a/gno.land/cmd/gnoland/secrets_init.go b/gno.land/cmd/gnoland/secrets_init.go index 55be73c22fc..85181d4735c 100644 --- a/gno.land/cmd/gnoland/secrets_init.go +++ b/gno.land/cmd/gnoland/secrets_init.go @@ -88,55 +88,54 @@ func execSecretsInit(cfg *secretsInitCfg, args []string, io commands.IO) error { switch key { case validatorPrivateKeyKey: if osm.FileExists(validatorKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled + return fmt.Errorf("unable to overwrite validator key, %w", errOverwriteNotEnabled) } // Initialize and save the validator's private key return initAndSaveValidatorKey(validatorKeyPath, io) case nodeKeyKey: if osm.FileExists(nodeKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled + return fmt.Errorf("unable to overwrite the node' p2p key, %w", errOverwriteNotEnabled) } // Initialize and save the node's p2p key return initAndSaveNodeKey(nodeKeyPath, io) case validatorStateKey: if osm.FileExists(validatorStatePath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled + return fmt.Errorf("unable to overwrite validator last sign state, %w", errOverwriteNotEnabled) } // Initialize and save the validator's last sign state return initAndSaveValidatorState(validatorStatePath, io) default: - // Check if the validator key should be overwritten - if osm.FileExists(validatorKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled - } - - // Check if the validator state should be overwritten - if osm.FileExists(validatorStatePath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled - } - - // Check if the node key should be overwritten - if osm.FileExists(nodeKeyPath) && !cfg.forceOverwrite { - return errOverwriteNotEnabled - } - // No key provided, initialize everything - // Initialize and save the validator's private key - if err := initAndSaveValidatorKey(validatorKeyPath, io); err != nil { - return err - } - - // Initialize and save the validator's last sign state - if err := initAndSaveValidatorState(validatorStatePath, io); err != nil { - return err - } + return errors.Join( + overwriteGuard(validatorKeyPath, initAndSaveValidatorKey, cfg.forceOverwrite, io), + overwriteGuard(validatorStatePath, initAndSaveValidatorState, cfg.forceOverwrite, io), + overwriteGuard(nodeKeyPath, initAndSaveNodeKey, cfg.forceOverwrite, io), + ) + } +} - // Initialize and save the node's p2p key - return initAndSaveNodeKey(nodeKeyPath, io) +// overwriteGuard guards against unwanted secret overwrites, +// and executes the secret initialization if the secret is not present +func overwriteGuard( + path string, + initFn func(string, commands.IO) error, + overwriteEnabled bool, + io commands.IO, +) error { + // Check if the secret already exists + if osm.FileExists(path) && !overwriteEnabled { + return fmt.Errorf( + "unable to overwrite secret at %q, %w", + path, + errOverwriteNotEnabled, + ) } + + // Secret doesn't exist, initialize it + return initFn(path, io) } // initAndSaveValidatorKey generates a validator private key and saves it to the given path diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 5ccf4b3a01b..282681edd63 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -5,8 +5,12 @@ import ( "errors" "flag" "fmt" + "io" + "os" + "os/signal" "path/filepath" "strings" + "syscall" "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" @@ -16,18 +20,20 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/bft/privval" - "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/file" - "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/null" - eventstorecfg "github.com/gnolang/gno/tm2/pkg/bft/state/eventstore/types" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/telemetry" + "go.uber.org/zap" "go.uber.org/zap/zapcore" ) +const defaultNodeDir = "gnoland-data" + +var errMissingGenesis = errors.New("missing genesis.json") + var startGraphic = strings.ReplaceAll(` __ __ ___ ____ ___ / /__ ____ ___/ / @@ -37,21 +43,17 @@ var startGraphic = strings.ReplaceAll(` `, "'", "`") type startCfg struct { - gnoRootDir string - skipFailingGenesisTxs bool - skipStart bool - genesisBalancesFile string - genesisTxsFile string + gnoRootDir string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + skipFailingGenesisTxs bool // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisBalancesFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisTxsFile string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 + genesisRemote string // TODO: remove as part of https://github.com/gnolang/gno/issues/1952 genesisFile string chainID string - genesisRemote string dataDir string genesisMaxVMCycles int64 config string - - txEventStoreType string - txEventStorePath string - nodeConfigPath string + lazyInit bool logLevel string logFormat string @@ -64,11 +66,12 @@ func newStartCmd(io commands.IO) *commands.Command { commands.Metadata{ Name: "start", ShortUsage: "start [flags]", - ShortHelp: "run the full node", + ShortHelp: "starts the Gnoland blockchain node", + LongHelp: "Starts the Gnoland blockchain node, with accompanying setup", }, cfg, - func(_ context.Context, _ []string) error { - return execStart(cfg, io) + func(ctx context.Context, _ []string) error { + return execStart(ctx, cfg, io) }, ) } @@ -85,13 +88,6 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "don't panic when replaying invalid genesis txs", ) - fs.BoolVar( - &c.skipStart, - "skip-start", - false, - "quit after initialization, don't start the node", - ) - fs.StringVar( &c.genesisBalancesFile, "genesis-balances-file", @@ -130,7 +126,7 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar( &c.dataDir, "data-dir", - "gnoland-data", + defaultNodeDir, "the path to the node's data directory", ) @@ -155,36 +151,6 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { "the flag config file (optional)", ) - fs.StringVar( - &c.nodeConfigPath, - "config-path", - "", - "the node TOML config file path (optional)", - ) - - fs.StringVar( - &c.txEventStoreType, - "tx-event-store-type", - null.EventStoreType, - fmt.Sprintf( - "type of transaction event store [%s]", - strings.Join( - []string{ - null.EventStoreType, - file.EventStoreType, - }, - ", ", - ), - ), - ) - - fs.StringVar( - &c.txEventStorePath, - "tx-event-store-path", - "", - fmt.Sprintf("path for the file tx event store (required if event store is '%s')", file.EventStoreType), - ) - fs.StringVar( &c.logLevel, "log-level", @@ -198,9 +164,16 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { log.ConsoleFormat.String(), "log format for the gnoland node", ) + + fs.BoolVar( + &c.lazyInit, + "lazy", + false, + "flag indicating if lazy init is enabled. Generates the node secrets, configuration, and genesis.json", + ) } -func execStart(c *startCfg, io commands.IO) error { +func execStart(ctx context.Context, c *startCfg, io commands.IO) error { // Get the absolute path to the node's data directory nodeDir, err := filepath.Abs(c.dataDir) if err != nil { @@ -213,105 +186,188 @@ func execStart(c *startCfg, io commands.IO) error { return fmt.Errorf("unable to get absolute path for the genesis.json, %w", err) } - var ( - cfg *config.Config - loadCfgErr error - ) - - // Set the node configuration - if c.nodeConfigPath != "" { - // Load the node configuration - // from the specified path - cfg, loadCfgErr = config.LoadConfigFile(c.nodeConfigPath) - } else { - // Load the default node configuration - cfg, loadCfgErr = config.LoadOrMakeConfigWithOptions(nodeDir) - } - - if loadCfgErr != nil { - return fmt.Errorf("unable to load node configuration, %w", loadCfgErr) - } - - // Initialize the log level - logLevel, err := zapcore.ParseLevel(c.logLevel) + // Initialize the logger + zapLogger, err := initializeLogger(io.Out(), c.logLevel, c.logFormat) if err != nil { - return fmt.Errorf("unable to parse log level, %w", err) + return fmt.Errorf("unable to initialize zap logger, %w", err) } - // Initialize the log format - logFormat := log.Format(strings.ToLower(c.logFormat)) - - // Initialize the zap logger - zapLogger := log.GetZapLoggerFn(logFormat)(io.Out(), logLevel) + defer func() { + // Sync the logger before exiting + _ = zapLogger.Sync() + }() // Wrap the zap logger logger := log.ZapLoggerToSlog(zapLogger) - // Initialize the telemetry - if err := telemetry.Init(*cfg.Telemetry); err != nil { - return fmt.Errorf("unable to initialize telemetry, %w", err) + if c.lazyInit { + if err := lazyInitNodeDir(io, nodeDir); err != nil { + return fmt.Errorf("unable to lazy-init the node directory, %w", err) + } + } + + // Load the configuration + cfg, err := config.LoadConfig(nodeDir) + if err != nil { + return fmt.Errorf("unable to load config, %w", err) } - // Write genesis file if missing. - // NOTE: this will be dropped in a PR that resolves issue #1886: - // https://github.com/gnolang/gno/issues/1886 + // Check if the genesis.json exists if !osm.FileExists(genesisPath) { - // Create priv validator first. - // Need it to generate genesis.json - newPrivValKey := cfg.PrivValidatorKeyFile() - newPrivValState := cfg.PrivValidatorStateFile() - priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) - pk := priv.GetPubKey() - - // Generate genesis.json file - if err := generateGenesisFile(genesisPath, pk, c); err != nil { - return fmt.Errorf("unable to generate genesis file: %w", err) + if !c.lazyInit { + return errMissingGenesis + } + + // Load the private validator secrets + privateKey := privval.LoadFilePV( + cfg.PrivValidatorKeyFile(), + cfg.PrivValidatorStateFile(), + ) + + // Init a new genesis.json + if err := lazyInitGenesis(io, c, genesisPath, privateKey.GetPubKey()); err != nil { + return fmt.Errorf("unable to initialize genesis.json, %w", err) } } - // Initialize the indexer config - txEventStoreCfg, err := getTxEventStoreConfig(c) - if err != nil { - return fmt.Errorf("unable to parse indexer config, %w", err) + // Initialize telemetry + if err := telemetry.Init(*cfg.Telemetry); err != nil { + return fmt.Errorf("unable to initialize telemetry, %w", err) } - cfg.TxEventStore = txEventStoreCfg - // Create application and node. - gnoApp, err := gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) + // Create application and node + cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) if err != nil { - return fmt.Errorf("error in creating new app: %w", err) + return fmt.Errorf("unable to create the Gnoland app, %w", err) } - cfg.LocalApp = gnoApp - if logFormat != log.JSONFormat { + // Print the starting graphic + if c.logFormat != string(log.JSONFormat) { io.Println(startGraphic) } + // Create a default node, with the given setup gnoNode, err := node.DefaultNewNode(cfg, genesisPath, logger) if err != nil { - return fmt.Errorf("error in creating node: %w", err) + return fmt.Errorf("unable to create the Gnoland node, %w", err) + } + + // Start the node (async) + if err := gnoNode.Start(); err != nil { + return fmt.Errorf("unable to start the Gnoland node, %w", err) } - if c.skipStart { - io.ErrPrintln("'--skip-start' is set. Exiting.") + // Set up the wait context + nodeCtx, _ := signal.NotifyContext( + ctx, + os.Interrupt, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + + // Wait for the exit signal + <-nodeCtx.Done() + + if !gnoNode.IsRunning() { return nil } - if err := gnoNode.Start(); err != nil { - return fmt.Errorf("error in start node: %w", err) + // Gracefully stop the gno node + if err := gnoNode.Stop(); err != nil { + return fmt.Errorf("unable to gracefully stop the Gnoland node, %w", err) } - osm.TrapSignal(func() { - if gnoNode.IsRunning() { - _ = gnoNode.Stop() + return nil +} + +// lazyInitNodeDir initializes new secrets, and a default configuration +// in the given node directory, if not present +func lazyInitNodeDir(io commands.IO, nodeDir string) error { + var ( + configPath = constructConfigPath(nodeDir) + secretsPath = constructSecretsPath(nodeDir) + ) + + // Check if the configuration already exists + if !osm.FileExists(configPath) { + // Create the gnoland config options + cfg := &configInitCfg{ + configCfg: configCfg{ + configPath: constructConfigPath(nodeDir), + }, } - // Sync the logger before exiting - _ = zapLogger.Sync() - }) + // Run gnoland config init + if err := execConfigInit(cfg, io); err != nil { + return fmt.Errorf("unable to initialize config, %w", err) + } + + io.Printfln("WARN: Initialized default node config at %q", filepath.Dir(cfg.configPath)) + io.Println() + } + + // Create the gnoland secrets options + secrets := &secretsInitCfg{ + commonAllCfg: commonAllCfg{ + dataDir: secretsPath, + }, + forceOverwrite: false, // existing secrets shouldn't be pruned + } - // Run forever - select {} + // Run gnoland secrets init + err := execSecretsInit(secrets, []string{}, io) + if err == nil { + io.Printfln("WARN: Initialized default node secrets at %q", secrets.dataDir) + + return nil + } + + // Check if the error is valid + if errors.Is(err, errOverwriteNotEnabled) { + // No new secrets were generated + return nil + } + + return fmt.Errorf("unable to initialize secrets, %w", err) +} + +// lazyInitGenesis a new genesis.json file, with a signle validator +func lazyInitGenesis( + io commands.IO, + c *startCfg, + genesisPath string, + publicKey crypto.PubKey, +) error { + // Check if the genesis.json is present + if osm.FileExists(genesisPath) { + return nil + } + + // Generate the new genesis.json file + if err := generateGenesisFile(genesisPath, publicKey, c); err != nil { + return fmt.Errorf("unable to generate genesis file, %w", err) + } + + io.Printfln("WARN: Initialized genesis.json at %q", genesisPath) + + return nil +} + +// initializeLogger initializes the zap logger using the given format and log level, +// outputting to the given IO +func initializeLogger(io io.WriteCloser, logLevel, logFormat string) (*zap.Logger, error) { + // Initialize the log level + level, err := zapcore.ParseLevel(logLevel) + if err != nil { + return nil, fmt.Errorf("unable to parse log level, %w", err) + } + + // Initialize the log format + format := log.Format(strings.ToLower(logFormat)) + + // Initialize the zap logger + return log.GetZapLoggerFn(format)(io, level), nil } func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) error { @@ -373,27 +429,3 @@ func generateGenesisFile(genesisFile string, pk crypto.PubKey, c *startCfg) erro return nil } - -// getTxEventStoreConfig constructs an event store config from provided user options -func getTxEventStoreConfig(c *startCfg) (*eventstorecfg.Config, error) { - var cfg *eventstorecfg.Config - - switch c.txEventStoreType { - case file.EventStoreType: - if c.txEventStorePath == "" { - return nil, errors.New("unspecified file transaction indexer path") - } - - // Fill out the configuration - cfg = &eventstorecfg.Config{ - EventStoreType: file.EventStoreType, - Params: map[string]any{ - file.Path: c.txEventStorePath, - }, - } - default: - cfg = eventstorecfg.DefaultEventStoreConfig() - } - - return cfg, nil -} diff --git a/gno.land/cmd/gnoland/start_test.go b/gno.land/cmd/gnoland/start_test.go index cdec6de0f99..2f620fcd360 100644 --- a/gno.land/cmd/gnoland/start_test.go +++ b/gno.land/cmd/gnoland/start_test.go @@ -4,38 +4,110 @@ import ( "bytes" "context" "path/filepath" + "strings" "testing" "time" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) -func TestStartInitialize(t *testing.T) { - t.Parallel() +// retryUntilTimeout runs the callback until the timeout is exceeded, or +// the callback returns a flag indicating completion +func retryUntilTimeout(ctx context.Context, cb func() bool) error { + ch := make(chan error, 1) + + go func() { + defer close(ch) + + for { + select { + case <-ctx.Done(): + ch <- ctx.Err() + + return + default: + retry := cb() + if !retry { + ch <- nil + return + } + } + + time.Sleep(500 * time.Millisecond) + } + }() + + return <-ch +} + +// prepareNodeRPC sets the RPC listen address for the node to be an arbitrary +// free address. Setting the listen port to a free port on the machine avoids +// node collisions between different testing suites +func prepareNodeRPC(t *testing.T, nodeDir string) { + t.Helper() + + path := constructConfigPath(nodeDir) + args := []string{ + "config", + "init", + "--config-path", + path, + } + + // Prepare the IO + mockOut := new(bytes.Buffer) + mockErr := new(bytes.Buffer) + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(mockOut)) + io.SetErr(commands.WriteNopCloser(mockErr)) + + // Prepare the cmd context + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() - // NOTE: cannot be txtar tests as they use their own parsing for the - // "gnoland" command line. See pkg/integration. + // Run config init + require.NoError(t, newRootCmd(io).ParseAndRun(ctx, args)) + + args = []string{ + "config", + "set", + "--config-path", + path, + "rpc.laddr", + "tcp://0.0.0.0:0", + } + + // Run config set + require.NoError(t, newRootCmd(io).ParseAndRun(ctx, args)) +} + +func TestStart_Lazy(t *testing.T) { + t.Parallel() var ( nodeDir = t.TempDir() genesisFile = filepath.Join(nodeDir, "test_genesis.json") - - args = []string{ - "start", - "--skip-start", - "--skip-failing-genesis-txs", - - // These two flags are tested together as they would otherwise - // pollute this directory (cmd/gnoland) if not set. - "--data-dir", - nodeDir, - "--genesis", - genesisFile, - } ) + // Prepare the config + prepareNodeRPC(t, nodeDir) + + args := []string{ + "start", + "--lazy", + "--skip-failing-genesis-txs", + + // These two flags are tested together as they would otherwise + // pollute this directory (cmd/gnoland) if not set. + "--data-dir", + nodeDir, + "--genesis", + genesisFile, + } + // Prepare the IO mockOut := new(bytes.Buffer) mockErr := new(bytes.Buffer) @@ -44,13 +116,49 @@ func TestStartInitialize(t *testing.T) { io.SetErr(commands.WriteNopCloser(mockErr)) // Create and run the command - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) defer cancelFn() - cmd := newRootCmd(io) - require.NoError(t, cmd.ParseAndRun(ctx, args)) + // Set up the command ctx + g, gCtx := errgroup.WithContext(ctx) + + // Start the node + g.Go(func() error { + return newRootCmd(io).ParseAndRun(gCtx, args) + }) + + // Set up the retry ctx + retryCtx, retryCtxCancelFn := context.WithTimeout(ctx, 5*time.Second) + defer retryCtxCancelFn() + + // This is a very janky way to verify the node has started. + // The alternative is to poll the node's RPC endpoints, but for some reason + // this introduces a lot of flakyness to the testing suite -- shocking! + // In an effort to keep this simple, and avoid randomly failing tests, + // we query the CLI output of the command + require.NoError(t, retryUntilTimeout(retryCtx, func() bool { + return !strings.Contains(mockOut.String(), startGraphic) + })) + + cancelFn() // stop the node + require.NoError(t, g.Wait()) - // Make sure the directory is created - assert.DirExists(t, nodeDir) + // Make sure the genesis is generated assert.FileExists(t, genesisFile) + + // Make sure the config is generated (default) + assert.FileExists(t, constructConfigPath(nodeDir)) + + // Make sure the secrets are generated + var ( + secretsPath = constructSecretsPath(nodeDir) + validatorKeyPath = filepath.Join(secretsPath, defaultValidatorKeyName) + validatorStatePath = filepath.Join(secretsPath, defaultValidatorStateName) + nodeKeyPath = filepath.Join(secretsPath, defaultNodeKeyName) + ) + + assert.DirExists(t, secretsPath) + assert.FileExists(t, validatorKeyPath) + assert.FileExists(t, validatorStatePath) + assert.FileExists(t, nodeKeyPath) } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index cd0c946f907..7669d5cce95 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -157,31 +157,34 @@ func PanicOnFailingTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { // InitChainer returns a function that can initialize the chain with genesis. func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, resHandler GenesisTxHandler) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - // Get genesis state. - genState := req.AppState.(GnoGenesisState) - // Parse and set genesis state balances. - for _, bal := range genState.Balances { - acc := acctKpr.NewAccountWithAddress(ctx, bal.Address) - acctKpr.SetAccount(ctx, acc) - err := bankKpr.SetCoins(ctx, bal.Address, bal.Amount) - if err != nil { - panic(err) + if req.AppState != nil { + // Get genesis state + genState := req.AppState.(GnoGenesisState) + + // Parse and set genesis state balances + for _, bal := range genState.Balances { + acc := acctKpr.NewAccountWithAddress(ctx, bal.Address) + acctKpr.SetAccount(ctx, acc) + err := bankKpr.SetCoins(ctx, bal.Address, bal.Amount) + if err != nil { + panic(err) + } } - } - // Run genesis txs. - for _, tx := range genState.Txs { - res := baseApp.Deliver(tx) - if res.IsErr() { - ctx.Logger().Error( - "Unable to deliver genesis tx", - "log", res.Log, - "error", res.Error, - "gas-used", res.GasUsed, - ) + // Run genesis txs + for _, tx := range genState.Txs { + res := baseApp.Deliver(tx) + if res.IsErr() { + ctx.Logger().Error( + "Unable to deliver genesis tx", + "log", res.Log, + "error", res.Error, + "gas-used", res.GasUsed, + ) + } + + resHandler(ctx, tx, res) } - - resHandler(ctx, tx, res) } // Done! diff --git a/go.mod b/go.mod index b5ef5021fac..76c42f0419c 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/mod v0.16.0 golang.org/x/net v0.23.0 + golang.org/x/sync v0.6.0 golang.org/x/term v0.18.0 golang.org/x/tools v0.19.0 google.golang.org/protobuf v1.33.0 diff --git a/misc/loop/scripts/start.sh b/misc/loop/scripts/start.sh index 978fbd4683f..76869ccb4bd 100755 --- a/misc/loop/scripts/start.sh +++ b/misc/loop/scripts/start.sh @@ -14,16 +14,23 @@ PERSISTENT_PEERS=${PERSISTENT_PEERS:-""} echo "" >> /gnoroot/gno.land/genesis/genesis_txs.jsonl cat ${GENESIS_BACKUP_FILE} >> /gnoroot/gno.land/genesis/genesis_txs.jsonl -/gnoland start \ - --chainid="${CHAIN_ID}" \ - --skip-start=true \ - --skip-failing-genesis-txs - -sed -i "s#^moniker = \".*\"#moniker = \"${MONIKER}\"#" ./gnoland-data/config/config.toml -sed -i "s#laddr = \".*:26656\"#laddr = \"${P2P_LADDR}\"#" ./gnoland-data/config/config.toml -sed -i "s#laddr = \".*:26657\"#laddr = \"${RPC_LADDR}\"#" ./gnoland-data/config/config.toml - -sed -i "s#seeds = \".*\"#seeds = \"${SEEDS}\"#" ./gnoland-data/config/config.toml -sed -i "s#persistent_peers = \".*\"#persistent_peers = \"${PERSISTENT_PEERS}\"#" ./gnoland-data/config/config.toml - -exec /gnoland start --skip-failing-genesis-txs +# Initialize the secrets +gnoland secrets init + +# Initialize the configuration +gnoland config init + +# Set the config values +gnoland config set moniker "${MONIKER}" +gnoland config set rpc.laddr "${RPC_LADDR}" +gnoland config set p2p.laddr "${P2P_LADDR}" +gnoland config set p2p.seeds "${SEEDS}" +gnoland config set p2p.persistent_peers "${PERSISTENT_PEERS}" + +# Running a lazy init will generate a fresh genesis.json, with +# the previously generated secrets. We do this to avoid CLI magic from config +# reading and piping to the gnoland genesis commands +exec gnoland start \ + --chainid="${CHAIN_ID}" \ + --lazy \ + --skip-failing-genesis-txs diff --git a/tm2/pkg/bft/config/config.go b/tm2/pkg/bft/config/config.go index 875eddcb7f1..21e9c2fe646 100644 --- a/tm2/pkg/bft/config/config.go +++ b/tm2/pkg/bft/config/config.go @@ -71,6 +71,36 @@ func DefaultConfig() *Config { type Option func(cfg *Config) +// LoadConfig loads the node configuration from disk +func LoadConfig(root string) (*Config, error) { + // Initialize the config as default + var ( + cfg = DefaultConfig() + configPath = filepath.Join(root, defaultConfigPath) + ) + + if !osm.FileExists(configPath) { + return nil, fmt.Errorf("config file at %q does not exist", configPath) + } + + // Load the configuration + loadedCfg, loadErr := LoadConfigFile(configPath) + if loadErr != nil { + return nil, loadErr + } + + // Merge the loaded config with the default values. + // This is done in case the loaded config is missing values + if err := mergo.Merge(loadedCfg, cfg); err != nil { + return nil, err + } + + // Set the root directory + loadedCfg.SetRootDir(root) + + return loadedCfg, nil +} + // LoadOrMakeConfigWithOptions loads the configuration located in the given // root directory, at [defaultConfigFilePath]. // @@ -165,11 +195,11 @@ func (cfg *Config) EnsureDirs() error { return fmt.Errorf("no root directory, %w", err) } - if err := osm.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultConfigDir), DefaultDirPerm); err != nil { return fmt.Errorf("no config directory, %w", err) } - if err := osm.EnsureDir(filepath.Join(rootDir, defaultSecretsDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultSecretsDir), DefaultDirPerm); err != nil { return fmt.Errorf("no secrets directory, %w", err) } @@ -205,18 +235,18 @@ func (cfg *Config) ValidateBasic() error { var ( DefaultDBDir = "db" - defaultConfigDir = "config" - defaultSecretsDir = "secrets" + DefaultConfigDir = "config" + DefaultSecretsDir = "secrets" - defaultConfigFileName = "config.toml" + DefaultConfigFileName = "config.toml" defaultNodeKeyName = "node_key.json" defaultPrivValKeyName = "priv_validator_key.json" defaultPrivValStateName = "priv_validator_state.json" - defaultConfigPath = filepath.Join(defaultConfigDir, defaultConfigFileName) - defaultPrivValKeyPath = filepath.Join(defaultSecretsDir, defaultPrivValKeyName) - defaultPrivValStatePath = filepath.Join(defaultSecretsDir, defaultPrivValStateName) - defaultNodeKeyPath = filepath.Join(defaultSecretsDir, defaultNodeKeyName) + defaultConfigPath = filepath.Join(DefaultConfigDir, DefaultConfigFileName) + defaultPrivValKeyPath = filepath.Join(DefaultSecretsDir, defaultPrivValKeyName) + defaultPrivValStatePath = filepath.Join(DefaultSecretsDir, defaultPrivValStateName) + defaultNodeKeyPath = filepath.Join(DefaultSecretsDir, defaultNodeKeyName) ) // BaseConfig defines the base configuration for a Tendermint node. diff --git a/tm2/pkg/bft/config/toml.go b/tm2/pkg/bft/config/toml.go index 5d8589394a0..023f9d9e625 100644 --- a/tm2/pkg/bft/config/toml.go +++ b/tm2/pkg/bft/config/toml.go @@ -69,10 +69,10 @@ func ResetTestRoot(testName string) (*Config, string) { } // ensure config and data subdirs are created - if err := osm.EnsureDir(filepath.Join(rootDir, defaultConfigDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultConfigDir), DefaultDirPerm); err != nil { panic(err) } - if err := osm.EnsureDir(filepath.Join(rootDir, defaultSecretsDir), DefaultDirPerm); err != nil { + if err := osm.EnsureDir(filepath.Join(rootDir, DefaultSecretsDir), DefaultDirPerm); err != nil { panic(err) } if err := osm.EnsureDir(filepath.Join(rootDir, DefaultDBDir), DefaultDirPerm); err != nil {