diff --git a/app/key/gen.go b/app/key/gen.go index 395a59ea8..aeb1add5a 100644 --- a/app/key/gen.go +++ b/app/key/gen.go @@ -28,6 +28,9 @@ func GenCmd() *cobra.Command { Long: "The `gen` command generates a private key for use by validators.", Example: genExample, Args: cobra.RangeArgs(0, 1), + // Override the root command's PersistentPreRunE, so that we don't + // try to read the config from a ~/.kwild directory + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil }, RunE: func(cmd *cobra.Command, args []string) error { keyType := crypto.KeyTypeSecp256k1 // default with 0 args if len(args) > 0 { diff --git a/app/key/info.go b/app/key/info.go index 411272451..a2fee1907 100644 --- a/app/key/info.go +++ b/app/key/info.go @@ -33,6 +33,9 @@ func InfoCmd() *cobra.Command { Long: infoLong, Example: infoExample, Args: cobra.MaximumNArgs(1), + // Override the root command's PersistentPreRunE, so that we don't + // try to read the config from a ~/.kwild directory + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil }, RunE: func(cmd *cobra.Command, args []string) error { // if len(args) == 1, then the private key is passed as a hex string // otherwise, it is passed as a file path diff --git a/app/node/start.go b/app/node/start.go index b84df7db7..ff2a4a395 100644 --- a/app/node/start.go +++ b/app/node/start.go @@ -16,7 +16,7 @@ func StartCmd() *cobra.Command { var dbOwner string cmd := &cobra.Command{ Use: "start", - Short: "Start the node (default command if none given)", + Short: "Start the node", Long: "The `start` command starts the Kwil DB blockchain node.", DisableAutoGenTag: true, SilenceUsage: true, diff --git a/app/root.go b/app/root.go index eedee7f8d..0b21adced 100644 --- a/app/root.go +++ b/app/root.go @@ -61,18 +61,23 @@ func RootCmd() *cobra.Command { display.BindOutputFormatFlag(cmd) // --output/-o // There is a virtual "node" command grouping, but no actual "node" command yet. - cmd.AddCommand(node.StartCmd()) - cmd.AddCommand(node.PrintConfigCmd()) + cmd.AddCommand(node.StartCmd()) // needs merged config + cmd.AddCommand(node.PrintConfigCmd()) // needs merged config + // This group of command uses the merged config for fallback admin listen + // addr if the --rpcserver flag is not set. cmd.AddCommand(rpc.NewAdminCmd()) cmd.AddCommand(validator.NewValidatorsCmd()) cmd.AddCommand(params.NewConsensusCmd()) - cmd.AddCommand(setup.SetupCmd()) cmd.AddCommand(whitelist.WhitelistCmd()) + cmd.AddCommand(block.NewBlockExecCmd()) + cmd.AddCommand(migration.NewMigrationCmd()) + + cmd.AddCommand(setup.SetupCmd()) // only kinda needs merged config for `setup reset` + cmd.AddCommand(key.KeyCmd()) cmd.AddCommand(snapshot.NewSnapshotCmd()) - cmd.AddCommand(migration.NewMigrationCmd()) - cmd.AddCommand(block.NewBlockExecCmd()) + cmd.AddCommand(seed.SeedCmd()) cmd.AddCommand(utils.NewCmdUtils()) cmd.AddCommand(verCmd.NewVersionCmd()) diff --git a/app/seed/seed.go b/app/seed/seed.go index edb6953a7..e62eb9f9b 100644 --- a/app/seed/seed.go +++ b/app/seed/seed.go @@ -25,6 +25,7 @@ func SeedCmd() *cobra.Command { Short: "Run a network seeder", Long: "The `seed` command starts a peer seeder process to crawl and bootstrap the network. This does not use the kwild node config. It will bind to TCP port 6609, and store config and data in the specified directory.", Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { logger := log.New(log.WithWriter(os.Stdout), log.WithFormat(log.FormatUnstructured), log.WithName("SEEDER")) diff --git a/app/setup/genesis-hash.go b/app/setup/genesis-hash.go index 626f895c0..dc1b04cb0 100644 --- a/app/setup/genesis-hash.go +++ b/app/setup/genesis-hash.go @@ -11,12 +11,12 @@ import ( "os" "path/filepath" + "github.com/kwilteam/kwil-db/app/node/conf" "github.com/kwilteam/kwil-db/app/shared/bind" "github.com/kwilteam/kwil-db/app/shared/display" "github.com/kwilteam/kwil-db/app/snapshot" "github.com/kwilteam/kwil-db/config" "github.com/kwilteam/kwil-db/node" - "github.com/kwilteam/kwil-db/node/pg" "github.com/spf13/cobra" ) @@ -41,7 +41,7 @@ kwild setup genesis-hash --snapshot "/path/to/snapshot.sql.gz" --genesis "~/.kwi ) func GenesisHashCmd() *cobra.Command { - var genesisFile, rootDir, snapshotFile string + var genesisFile, snapshotFile string cmd := &cobra.Command{ Use: "genesis-hash", @@ -49,8 +49,11 @@ func GenesisHashCmd() *cobra.Command { Long: genesisHashLong, Example: genesisHashExample, Args: cobra.NoArgs, + // Override the root's PersistentPreRunE to bind only the config file, + // not the full node flag set. + PersistentPreRunE: bind.ChainPreRuns(conf.PreRunBindConfigFileStrict[config.Config]), // but not the flags RunE: func(cmd *cobra.Command, args []string) error { - if cmd.Flags().Changed("snapshot") && cmd.Flags().Changed("root-dir") { + if cmd.Flags().Changed("snapshot") && cmd.Flags().Changed(bind.RootFlagName) { return display.PrintErr(cmd, errors.New("cannot use both --snapshot and --root-dir")) } @@ -61,24 +64,11 @@ func GenesisHashCmd() *cobra.Command { if err != nil { return display.PrintErr(cmd, err) } - } else { - var pgConf *pg.ConnConfig - var err error - if rootDir != "" { - rootDir, err = node.ExpandPath(rootDir) - if err != nil { - return display.PrintErr(cmd, err) - } - - pgConf, err = loadPGConfigFromTOML(cmd, rootDir) - if err != nil { - return display.PrintErr(cmd, err) - } - } else { - pgConf, err = bind.GetPostgresFlags(cmd) - if err != nil { - return display.PrintErr(cmd, err) - } + } else { // create a snapshot first + dbCfg := conf.ActiveConfig().DB + pgConf, err := bind.GetPostgresFlags(cmd, &dbCfg) + if err != nil { + return display.PrintErr(cmd, fmt.Errorf("failed to get postgres flags: %v", err)) } dir, err := tmpKwilAdminSnapshotDir() @@ -86,18 +76,18 @@ func GenesisHashCmd() *cobra.Command { return display.PrintErr(cmd, err) } - // ensure the temp admin snapshots directory exists - err = ensureTmpKwilAdminDir(dir) + // clean up any previous temp admin snapshots + err = cleanupTmpKwilAdminDir(dir) if err != nil { return display.PrintErr(cmd, err) } - defer cleanupTmpKwilAdminDir(dir) // clean up temp admin snapshots directory on exit after app hash computation - // clean up any previous temp admin snapshots - err = cleanupTmpKwilAdminDir(dir) + // ensure the temp admin snapshots directory exists + err = ensureTmpKwilAdminDir(dir) if err != nil { return display.PrintErr(cmd, err) } + defer cleanupTmpKwilAdminDir(dir) // clean up temp admin snapshots directory on exit after app hash computation _, _, genCfg, err := snapshot.PGDump(cmd.Context(), pgConf.DBName, pgConf.User, pgConf.Pass, pgConf.Host, pgConf.Port, dir) if err != nil { @@ -107,15 +97,22 @@ func GenesisHashCmd() *cobra.Command { appHash = genCfg.StateHash } - return writeAndReturnGenesisHash(cmd, genesisFile, appHash) + if genesisFile != "" { + err := writeAndReturnGenesisHash(genesisFile, appHash) + if err != nil { + return display.PrintErr(cmd, err) + } + } + return display.PrintCmd(cmd, &genesisHashRes{ + Hash: base64.StdEncoding.EncodeToString(appHash), + }) }, } cmd.Flags().StringVarP(&genesisFile, "genesis", "g", "", "optional path to the genesis file to patch with the computed app hash") - cmd.Flags().StringVarP(&rootDir, "root-dir", "r", "", "optional path to the root directory of the kwild node from which the genesis hash will be computed") cmd.Flags().StringVarP(&snapshotFile, "snapshot", "s", "", "optional path to the snapshot file to use for the genesis hash computation") - bind.BindPostgresFlags(cmd) + bind.BindPostgresFlags(cmd, &conf.ActiveConfig().DB) return cmd } @@ -152,56 +149,20 @@ func appHashFromSnapshotFile(filePath string) ([]byte, error) { return hash.Sum(nil), nil } -// loadPGConfigFromTOML loads the postgres connection configuration from the toml file -// and merges it with the flags set in the command. -func loadPGConfigFromTOML(cmd *cobra.Command, rootDir string) (*pg.ConnConfig, error) { - tomlFile := config.ConfigFilePath(rootDir) - - // Check if the config file exists - if _, err := os.Stat(tomlFile); os.IsNotExist(err) { - return bind.GetPostgresFlags(cmd) // return the flags if the config file does not exist - } - - cfg, err := config.LoadConfig(tomlFile) +func writeAndReturnGenesisHash(genesisFile string, appHash []byte) error { + genesisFile, err := node.ExpandPath(genesisFile) if err != nil { - return nil, fmt.Errorf("failed to load config from toml file: %w", err) + return err } - conf := &pg.ConnConfig{ - Host: cfg.DB.Host, - Port: cfg.DB.Port, - User: cfg.DB.User, - Pass: cfg.DB.Pass, - DBName: cfg.DB.DBName, + cfg, err := config.LoadGenesisConfig(genesisFile) + if err != nil { + return err } - // merge the flags with the config file - return bind.MergePostgresFlags(conf, cmd) -} - -func writeAndReturnGenesisHash(cmd *cobra.Command, genesisFile string, appHash []byte) error { - if genesisFile != "" { - genesisFile, err := node.ExpandPath(genesisFile) - if err != nil { - return display.PrintErr(cmd, err) - } - - cfg, err := config.LoadGenesisConfig(genesisFile) - if err != nil { - return display.PrintErr(cmd, err) - } - - cfg.StateHash = appHash + cfg.StateHash = appHash - err = cfg.SaveAs(genesisFile) - if err != nil { - return display.PrintErr(cmd, err) - } - } - - return display.PrintCmd(cmd, &genesisHashRes{ - Hash: base64.StdEncoding.EncodeToString(appHash), - }) + return cfg.SaveAs(genesisFile) } type genesisHashRes struct { @@ -234,14 +195,11 @@ func tmpKwilAdminSnapshotDir() (string, error) { // ensureTmpKwilAdminDir ensures that the temporary directory for kwil-admin snapshots exists. func ensureTmpKwilAdminDir(dir string) error { - if _, err := os.Stat(dir); os.IsNotExist(err) { + _, err := os.Stat(dir) + if os.IsNotExist(err) { err = os.Mkdir(dir, 0755) - if err != nil { - return err - } } - - return nil + return err } // cleanupTmpKwilAdminDir removes the temporary directory for kwil-admin snapshots. diff --git a/app/setup/genesis.go b/app/setup/genesis.go index 33d027f41..faa4b2409 100644 --- a/app/setup/genesis.go +++ b/app/setup/genesis.go @@ -61,6 +61,9 @@ func GenesisCmd() *cobra.Command { DisableDefaultCmd: true, }, Args: cobra.NoArgs, + // Override the root command's PersistentPreRunE, so that we don't + // try to read the config from a ~/.kwild directory. + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil }, RunE: func(cmd *cobra.Command, args []string) error { outDir, err := node.ExpandPath(output) if err != nil { diff --git a/app/setup/init.go b/app/setup/init.go index 160a70920..125243254 100644 --- a/app/setup/init.go +++ b/app/setup/init.go @@ -57,6 +57,10 @@ func InitCmd() *cobra.Command { DisableDefaultCmd: true, }, Args: cobra.NoArgs, + // Override parent persistent prerun so we don't try to read an existing + // config file; only the default+flags+env for generating a new root. + PersistentPreRunE: bind.ChainPreRuns(bind.MaybeEnableCLIDebug, + conf.PreRunBindFlags, conf.PreRunBindEnvMatching, conf.PreRunPrintEffectiveConfig), RunE: func(cmd *cobra.Command, args []string) error { genSnapshotFlag := cmd.Flag("genesis-snapshot").Changed genStateFlag := cmd.Flag("genesis-state").Changed @@ -75,6 +79,12 @@ func InitCmd() *cobra.Command { return err } + // Ensure the output directory does not already exist... + if _, err := os.Stat(outDir); err == nil { + return display.PrintErr(cmd, fmt.Errorf("output directory %s already exists", outDir)) + } + + // create the output directory if err := os.MkdirAll(outDir, nodeDirPerm); err != nil { return err } diff --git a/app/setup/reset.go b/app/setup/reset.go index 1c2482dcf..00ff0dbec 100644 --- a/app/setup/reset.go +++ b/app/setup/reset.go @@ -6,7 +6,11 @@ import ( "os" "path/filepath" + "github.com/kwilteam/kwil-db/app/custom" + "github.com/kwilteam/kwil-db/app/node/conf" "github.com/kwilteam/kwil-db/app/shared/bind" + "github.com/kwilteam/kwil-db/app/shared/display" + "github.com/kwilteam/kwil-db/config" "github.com/kwilteam/kwil-db/node" "github.com/kwilteam/kwil-db/node/pg" @@ -31,6 +35,9 @@ func ResetCmd() *cobra.Command { Long: resetLong, Example: resetExample, Args: cobra.NoArgs, + // Override the root's PersistentPreRunE to bind only the config file, + // not the full node flag set. + PersistentPreRunE: bind.ChainPreRuns(conf.PreRunBindConfigFileStrict[config.Config]), // but not the flags RunE: func(cmd *cobra.Command, args []string) error { rootDir, err := bind.RootDir(cmd) if err != nil { @@ -45,9 +52,10 @@ func ResetCmd() *cobra.Command { return fmt.Errorf("root directory %s does not exist", rootDir) } - pgConf, err := loadPGConfigFromTOML(cmd, rootDir) + dbCfg := conf.ActiveConfig().DB + pgConf, err := bind.GetPostgresFlags(cmd, &dbCfg) if err != nil { - return err + return display.PrintErr(cmd, fmt.Errorf("failed to get postgres flags: %v", err)) } err = resetPGState(cmd.Context(), pgConf) @@ -96,7 +104,7 @@ func ResetCmd() *cobra.Command { } cmd.Flags().BoolVar(&all, "all", false, "reset all data, if this is not set, only the app state will be reset") - bind.BindPostgresFlags(cmd) + bind.BindPostgresFlags(cmd, &custom.DefaultConfig().DB) return cmd } diff --git a/app/setup/testnet.go b/app/setup/testnet.go index 85da5eb58..ad9e5f5a4 100644 --- a/app/setup/testnet.go +++ b/app/setup/testnet.go @@ -33,10 +33,14 @@ func TestnetCmd() *cobra.Command { var outDir, dbOwner string cmd := &cobra.Command{ + Use: "testnet", Short: "Generate configuration for a new test network with multiple nodes", Long: "The `testnet` command generates a configuration for a new test network with multiple nodes. " + "For a configuration set that can be run on the same host, use the `--unique-ports` flag.", + // Override the root command's PersistentPreRunE, so that we don't + // try to read the config from a ~/.kwild directory + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return nil }, RunE: func(cmd *cobra.Command, args []string) error { return GenerateTestnetConfigs(&TestnetConfig{ RootDir: outDir, diff --git a/app/shared/bind/bind.go b/app/shared/bind/bind.go index b8dadf7a8..dc13bb250 100644 --- a/app/shared/bind/bind.go +++ b/app/shared/bind/bind.go @@ -13,6 +13,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" + "github.com/kwilteam/kwil-db/config" "github.com/kwilteam/kwil-db/core/log" ktypes "github.com/kwilteam/kwil-db/core/types" "github.com/kwilteam/kwil-db/node/pg" @@ -312,18 +313,27 @@ func mergeFunc(src, dest map[string]interface{}, keyFn func(s string) string) er return nil } -// bindPostgresFlags binds flags to connect to a postgres database. -func BindPostgresFlags(cmd *cobra.Command) { - cmd.Flags().String("dbname", "kwild", "Name of the database in the PostgreSQL server") - cmd.Flags().String("user", "postgres", "User with administrative privileges on the database") - cmd.Flags().String("password", "", "Password for the database user") - cmd.Flags().String("host", "localhost", "Host of the database") - cmd.Flags().String("port", "5432", "Port of the database") +// BindPostgresFlags binds top-level flags to connect to a postgres database. +// The provided config is used as defaults for the flags. +func BindPostgresFlags(cmd *cobra.Command, dbCfg *config.DBConfig) { + cmd.Flags().String("dbname", dbCfg.DBName, "Name of the database in the PostgreSQL server") + cmd.Flags().String("user", dbCfg.User, "User with administrative privileges on the database") + cmd.Flags().String("password", dbCfg.Pass, "Password for the database user") + cmd.Flags().String("host", dbCfg.Host, "Host of the database") + cmd.Flags().String("port", dbCfg.Port, "Port of the database") } -// getPostgresFlags returns the postgres flags from the given command. -func GetPostgresFlags(cmd *cobra.Command) (*pg.ConnConfig, error) { - return MergePostgresFlags(defaultPostgresConnConfig(), cmd) +// GetPostgresFlags returns the postgres flags from the given command, using the +// given fallback config, which may be a combination of defaults and values from +// an existing config file. +func GetPostgresFlags(cmd *cobra.Command, dbCfg *config.DBConfig) (*pg.ConnConfig, error) { + return MergePostgresFlags(&pg.ConnConfig{ + DBName: dbCfg.DBName, + User: dbCfg.User, + Pass: dbCfg.Pass, + Host: dbCfg.Host, + Port: dbCfg.Port, + }, cmd) } // MergePostgresFlags merges the given connection config with the flags from the given command. @@ -367,13 +377,3 @@ func MergePostgresFlags(conf *pg.ConnConfig, cmd *cobra.Command) (*pg.ConnConfig return conf, nil } - -// DefaultPostgresConnConfig returns a default connection config for a postgres database. -func defaultPostgresConnConfig() *pg.ConnConfig { - return &pg.ConnConfig{ - DBName: "kwild", - User: "postgres", - Host: "localhost", - Port: "5432", - } -} diff --git a/app/shared/display/format.go b/app/shared/display/format.go index c38703b5c..6c094d99e 100644 --- a/app/shared/display/format.go +++ b/app/shared/display/format.go @@ -183,7 +183,11 @@ func PrintCmd(cmd *cobra.Command, msg MsgFormatter) error { return prettyPrint(wrappedMsg, OutputFormat(format), cmd.OutOrStdout(), cmd.OutOrStderr()) } -// PrintErr prints the error according to the commands output format flag. +// PrintErr prints the error according to the commands output format flag. The +// returned error is nil if the message it was printed successfully. Thus, this +// function must ONLY be called from within a cobra.Command's RunE function or +// or returned directly by the RunE function, NOT used to direct application +// logic since the returned error no longer pertains to the initial error. func PrintErr(cmd *cobra.Command, err error) error { outputFormat, err2 := getOutputFormat(cmd) if err2 != nil { diff --git a/app/snapshot/create.go b/app/snapshot/create.go index 970598384..cb3d93d74 100644 --- a/app/snapshot/create.go +++ b/app/snapshot/create.go @@ -19,6 +19,8 @@ import ( "github.com/spf13/cobra" + "github.com/kwilteam/kwil-db/app/custom" + "github.com/kwilteam/kwil-db/app/node/conf" "github.com/kwilteam/kwil-db/app/shared/bind" "github.com/kwilteam/kwil-db/app/shared/display" "github.com/kwilteam/kwil-db/config" @@ -62,13 +64,19 @@ func createCmd() *cobra.Command { Long: createLongExplain, Example: createExample, Args: cobra.NoArgs, + // Override the root's PersistentPreRunE to bind only the config file, + // not the full node flag set. + PersistentPreRunE: bind.ChainPreRuns(conf.PreRunBindConfigFileStrict[config.Config]), // but not the flags RunE: func(cmd *cobra.Command, args []string) error { snapshotDir, err := node.ExpandPath(snapshotDir) if err != nil { return display.PrintErr(cmd, fmt.Errorf("failed to expand snapshot directory path: %v", err)) } - pgConf, err := bind.GetPostgresFlags(cmd) + // Get the pg.ConnConfig from the flags, using the active node + // config's DBConfig as defaults if the flags were not set. + dbCfg := conf.ActiveConfig().DB + pgConf, err := bind.GetPostgresFlags(cmd, &dbCfg) if err != nil { return display.PrintErr(cmd, fmt.Errorf("failed to get postgres flags: %v", err)) } @@ -83,7 +91,9 @@ func createCmd() *cobra.Command { }, } - bind.BindPostgresFlags(cmd) + // Bind the top level flags like --dbname, --user, --host, etc. using the + // defaults from the node's default config. + bind.BindPostgresFlags(cmd, &custom.DefaultConfig().DB) cmd.Flags().StringVar(&snapshotDir, "snapdir", "kwild-snaps", "Directory to store the snapshot and hash files") return cmd