From 489bf77eee05fa8e9d6c42896f8d272ba996da97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadej=20Jane=C5=BE?= Date: Fri, 18 Sep 2020 14:31:30 +0200 Subject: [PATCH 1/2] go/oasis-node/cmd/genesis: Add convert CLI command It can be used to convert a given genesis file to the canonical form. --- .changelog/3290.feature.md | 3 + go/oasis-node/cmd/genesis/genesis.go | 86 ++++++++++++++++--- .../scenario/e2e/genesis_file.go | 57 +++++++++--- 3 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 .changelog/3290.feature.md diff --git a/.changelog/3290.feature.md b/.changelog/3290.feature.md new file mode 100644 index 00000000000..24886c7915b --- /dev/null +++ b/.changelog/3290.feature.md @@ -0,0 +1,3 @@ +go/oasis-node: Add `oasis-node genesis convert` CLI command + +It can be used to convert a given genesis file to the canonical form. diff --git a/go/oasis-node/cmd/genesis/genesis.go b/go/oasis-node/cmd/genesis/genesis.go index 70ef1b6b157..cb84ec6cad3 100644 --- a/go/oasis-node/cmd/genesis/genesis.go +++ b/go/oasis-node/cmd/genesis/genesis.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io/ioutil" "math" "os" @@ -27,7 +28,7 @@ import ( tendermint "github.com/oasisprotocol/oasis-core/go/consensus/tendermint/api" epochtime "github.com/oasisprotocol/oasis-core/go/epochtime/api" genesis "github.com/oasisprotocol/oasis-core/go/genesis/api" - genesisFile "github.com/oasisprotocol/oasis-core/go/genesis/file" + genesisfile "github.com/oasisprotocol/oasis-core/go/genesis/file" keymanager "github.com/oasisprotocol/oasis-core/go/keymanager/api" cmdCommon "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common" "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/flags" @@ -39,6 +40,9 @@ import ( ) const ( + cfgGenesisCanonicalFile = "genesis.canonical_file" + cfgGenesisCanonicalFileShort = "C" + cfgEntity = "entity" cfgRuntime = "runtime" cfgNode = "node" @@ -102,9 +106,10 @@ const ( ) var ( - checkGenesisFlags = flag.NewFlagSet("", flag.ContinueOnError) - dumpGenesisFlags = flag.NewFlagSet("", flag.ContinueOnError) - initGenesisFlags = flag.NewFlagSet("", flag.ContinueOnError) + checkGenesisFlags = flag.NewFlagSet("", flag.ContinueOnError) + convertGenesisFlags = flag.NewFlagSet("", flag.ContinueOnError) + dumpGenesisFlags = flag.NewFlagSet("", flag.ContinueOnError) + initGenesisFlags = flag.NewFlagSet("", flag.ContinueOnError) genesisCmd = &cobra.Command{ Use: "genesis", @@ -129,6 +134,12 @@ var ( Run: doCheckGenesis, } + convertGenesisCmd = &cobra.Command{ + Use: "convert", + Short: "convert genesis file to canonical form", + Run: doConvertGenesis, + } + logger = logging.GetLogger("cmd/genesis") ) @@ -588,30 +599,72 @@ func doDumpGenesis(cmd *cobra.Command, args []string) { } } +func loadGenesisDocument(genFilePath string) (*genesis.Document, error) { + provider, err := genesisfile.NewFileProvider(genFilePath) + if err != nil { + return nil, fmt.Errorf("failed to open genesis file: %w", err) + } + doc, err := provider.GetGenesisDocument() + if err != nil { + return nil, fmt.Errorf("failed to get genesis document: %w", err) + } + return doc, nil +} + func doCheckGenesis(cmd *cobra.Command, args []string) { if err := cmdCommon.Init(); err != nil { cmdCommon.EarlyLogAndExit(err) } - filename := flags.GenesisFile() - provider, err := genesisFile.NewFileProvider(filename) + doc, err := loadGenesisDocument(flags.GenesisFile()) if err != nil { - logger.Error("failed to open genesis file", "err", err) + logger.Error("failed to load genesis document", "err", err) os.Exit(1) } - doc, err := provider.GetGenesisDocument() + + err = doc.SanityCheck() if err != nil { - logger.Error("failed to get genesis document", "err", err) + logger.Error("genesis document sanity check failed", "err", err) os.Exit(1) } +} - err = doc.SanityCheck() +func doConvertGenesis(cmd *cobra.Command, args []string) { + if err := cmdCommon.Init(); err != nil { + cmdCommon.EarlyLogAndExit(err) + } + + doc, err := loadGenesisDocument(flags.GenesisFile()) if err != nil { - logger.Error("genesis document sanity check failed", "err", err) + logger.Error("failed to load genesis document", "err", err) os.Exit(1) } - // TODO: Pretty-print contents of genesis document. + data, err := json.MarshalIndent(doc, "", " ") + if err != nil { + logger.Error("failed to marshal genesis document into JSON", + "err", err, + ) + os.Exit(1) + } + + w, shouldClose, err := cmdCommon.GetOutputWriter(cmd, cfgGenesisCanonicalFile) + if err != nil { + logger.Error("failed to get writer for canonical genesis file", + "err", err, + ) + os.Exit(1) + } + if shouldClose { + defer w.Close() + } + + if _, err = w.Write(data); err != nil { + logger.Error("failed to write marshalled document to the canonical genesis file", + "err", err, + ) + os.Exit(1) + } } // Register registers the genesis sub-command and all of it's children. @@ -620,11 +673,13 @@ func Register(parentCmd *cobra.Command) { dumpGenesisCmd.Flags().AddFlagSet(dumpGenesisFlags) dumpGenesisCmd.PersistentFlags().AddFlagSet(cmdGrpc.ClientFlags) checkGenesisCmd.Flags().AddFlagSet(checkGenesisFlags) + convertGenesisCmd.Flags().AddFlagSet(convertGenesisFlags) for _, v := range []*cobra.Command{ initGenesisCmd, dumpGenesisCmd, checkGenesisCmd, + convertGenesisCmd, } { genesisCmd.AddCommand(v) } @@ -636,6 +691,13 @@ func init() { _ = viper.BindPFlags(checkGenesisFlags) checkGenesisFlags.AddFlagSet(flags.GenesisFileFlags) + _ = viper.BindPFlags(convertGenesisFlags) + convertGenesisFlags.AddFlagSet(flags.GenesisFileFlags) + convertGenesisFlags.StringP( + cfgGenesisCanonicalFile, cfgGenesisCanonicalFileShort, + "", "path to where to output the canonical genesis file (default: stdout)", + ) + dumpGenesisFlags.Int64(cfgBlockHeight, consensus.HeightLatest, "block height at which to dump state") _ = viper.BindPFlags(dumpGenesisFlags) dumpGenesisFlags.AddFlagSet(flags.GenesisFileFlags) diff --git a/go/oasis-test-runner/scenario/e2e/genesis_file.go b/go/oasis-test-runner/scenario/e2e/genesis_file.go index c374fd0feab..f887fadb67c 100644 --- a/go/oasis-test-runner/scenario/e2e/genesis_file.go +++ b/go/oasis-test-runner/scenario/e2e/genesis_file.go @@ -47,52 +47,87 @@ func (s *genesisFileImpl) Fixture() (*oasis.NetworkFixture, error) { } func (s *genesisFileImpl) Run(childEnv *env.Env) error { + var err error + var out bytes.Buffer + // Manually provision genesis file. s.Logger.Info("manually provisioning genesis file before starting the network") - if err := s.Net.MakeGenesis(); err != nil { + if err = s.Net.MakeGenesis(); err != nil { return fmt.Errorf("e2e/genesis-file: failed to create genesis file") } // Set this genesis file in network's configuration. cfg := s.Net.Config() cfg.GenesisFile = s.Net.GenesisPath() - if err := checkGenesisFile(s.Net.GenesisPath()); err != nil { + // Checking if genesis check command works. + args := []string{ + "genesis", "check", + "--genesis.file", s.Net.GenesisPath(), + "--debug.dont_blame_oasis", + "--debug.allow_test_keys", + } + out, err = cli.RunSubCommandWithOutput(childEnv, s.Logger, "genesis-file", s.Net.Config().NodeBinary, args) + if err != nil { + return fmt.Errorf("e2e/genesis-file: failed to run genesis check: error: %w output: %s", err, out.String()) + } + s.Logger.Info("manually provisioned genesis file passed genesis check command") + + if err = checkGenesisFileCanonical(s.Net.GenesisPath()); err != nil { return fmt.Errorf("e2e/genesis-file: %w", err) } s.Logger.Info("manually provisioned genesis file equals canonical form") - if err := s.Net.Start(); err != nil { + if err = s.Net.Start(); err != nil { return fmt.Errorf("e2e/genesis-file: failed to start network: %w", err) } s.Logger.Info("waiting for network to come up") - if err := s.Net.Controller().WaitNodesRegistered(context.Background(), 1); err != nil { + if err = s.Net.Controller().WaitNodesRegistered(context.Background(), 1); err != nil { return fmt.Errorf("e2e/genesis-file: failed to wait for registered nodes: %w", err) } // Dump network state to a genesis file. s.Logger.Info("dumping network state to genesis file") - dumpPath := filepath.Join(childEnv.Dir(), "genesis_dump.json") - args := []string{ + args = []string{ "genesis", "dump", "--height", "0", "--genesis.file", dumpPath, "--address", "unix:" + s.Net.Validators()[0].SocketPath(), } - if err := cli.RunSubCommand(childEnv, s.Logger, "genesis-file", s.Net.Config().NodeBinary, args); err != nil { - return fmt.Errorf("e2e/genesis-file: failed to dump state: %w", err) + out, err = cli.RunSubCommandWithOutput(childEnv, s.Logger, "genesis-file", s.Net.Config().NodeBinary, args) + if err != nil { + return fmt.Errorf("e2e/genesis-file: failed to dump state: error: %w output: %s", err, out.String()) } - if err := checkGenesisFile(dumpPath); err != nil { + if err = checkGenesisFileCanonical(dumpPath); err != nil { return fmt.Errorf("e2e/genesis-file: %w", err) } s.Logger.Info("genesis file from dumped network state equals canonical form") + // Checking if genesis convert command returns canonical form. + convertPath := filepath.Join(childEnv.Dir(), "genesis_convert.json") + args = []string{ + "genesis", "convert", + "--genesis.file", dumpPath, + "--genesis.canonical_file", convertPath, + "--debug.dont_blame_oasis", + "--debug.allow_test_keys", + } + out, err = cli.RunSubCommandWithOutput(childEnv, s.Logger, "genesis-file", s.Net.Config().NodeBinary, args) + if err != nil { + return fmt.Errorf("e2e/genesis-file: failed to run genesis convert: error: %w output: %s", err, out.String()) + } + if err = checkGenesisFileCanonical(convertPath); err != nil { + return fmt.Errorf("e2e/genesis-file: %w", err) + } + s.Logger.Info("genesis file generated via convert command equals canonical form") + return nil } -// checkGenesisFile checks if the given genesis file equals the canonical form. -func checkGenesisFile(filePath string) error { +// checkGenesisFileCanonical checks if the given genesis file equals the +// canonical form. +func checkGenesisFileCanonical(filePath string) error { // Load genesis document from the genesis file. provider, err := genesisFile.NewFileProvider(filePath) if err != nil { From 43847a7ff20b828ffc7b2d9bcd1476e61cff146a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadej=20Jane=C5=BE?= Date: Fri, 18 Sep 2020 15:14:58 +0200 Subject: [PATCH 2/2] docs: Document oasis-node genesis CLI commands --- .changelog/3290.doc.md | 3 ++ docs/oasis-node/cli.md | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 .changelog/3290.doc.md diff --git a/.changelog/3290.doc.md b/.changelog/3290.doc.md new file mode 100644 index 00000000000..171daaccab8 --- /dev/null +++ b/.changelog/3290.doc.md @@ -0,0 +1,3 @@ +Document [`oasis-node genesis` CLI commands] + +[`oasis-node genesis` CLI commands]: docs/oasis-node/cli.md#genesis diff --git a/docs/oasis-node/cli.md b/docs/oasis-node/cli.md index 4aa85aa5432..8b2061f5721 100644 --- a/docs/oasis-node/cli.md +++ b/docs/oasis-node/cli.md @@ -155,6 +155,74 @@ node): } ``` +## `genesis` + +### `check` + +To check if a given [genesis file] is valid, run: + +```sh +oasis-node genesis check --genesis.file /path/to/genesis.json +``` + +### `convert` + +To convert a given [genesis file] to the [canonical form], run: + +```sh +oasis-node genesis convert --genesis.file /path/to/genesis.json \ + --genesis.canonical_file /path/to/canonical-genesis.json +``` + +{% hint style="info" %} +You can omit the `--genesis.canonical_file` flag and the canonical form will +be outputted to *stdout*. +{% endhint %} + +### `dump` + +To dump the state of the network at a specific block height, e.g. 717600, to a +[genesis file], run: + +```sh +oasis-node genesis dump \ + --address unix:/path/to/node/internal.sock \ + --genesis.file /path/to/genesis_dump.json \ + --height 717600 +``` + +{% hint style="warning" %} +You must only run the following command after the given block height has been +reached on the network. +{% endhint %} + +### `init` + +To initialize a new [genesis file] with the given chain id and [staking token +symbol], run: + +```sh +oasis-node genesis init --genesis.file /path/to/genesis.json \ + --chain.id "name-of-my-network" \ + --staking.token_symbol GOLD +``` + +{% hint style="info" %} +You can set a lot of parameters for the various [consensus layer services]. + +To see the full list, run: + +```sh +oasis-node genesis init --help +``` + +{% endhint %} + +[genesis file]: ../consensus/genesis.md#genesis-file +[canonical form]: ../consensus/genesis.md#canonical-form +[consensus layer services]: ../consensus/index.md +[staking token symbol]: ../consensus/staking.md#tokens-and-base-units + ## `stake` ### `account`