Skip to content

Commit

Permalink
go/oasis-node/cmd/genesis: Add check for canonical form to check cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
tjanez committed Sep 21, 2020
1 parent bb2b88f commit f4c8cef
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 38 deletions.
1 change: 1 addition & 0 deletions .changelog/3298.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/oasis-node: Make `oasis-node genesis check` command check if form canonical
39 changes: 38 additions & 1 deletion go/oasis-node/cmd/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
package genesis

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math"
"os"
"strconv"
"strings"
"time"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -99,6 +102,10 @@ const (
// Our 'entity' flag overlaps with the common flag 'entity'.
// We bind it to a separate Viper key to disambiguate at runtime.
viperEntity = "provision_entity"

// Check command.
// Number of lines to print if document not in canonical form.
checkNotCanonicalLines = 10
)

var (
Expand Down Expand Up @@ -611,7 +618,37 @@ func doCheckGenesis(cmd *cobra.Command, args []string) {
os.Exit(1)
}

// TODO: Pretty-print contents of genesis document.
// Load raw genesis file.
rawFile, err := ioutil.ReadFile(filename)
if err != nil {
logger.Error("failed to read genesis file:", "err", err)
os.Exit(1)
}
// Create a marshalled genesis document in the canonical form with 2 space indents.
rawCanonical, err := json.MarshalIndent(doc, "", " ")
if err != nil {
logger.Error("failed to marshal genesis document", "err", err)
os.Exit(1)
}
// Genesis file should equal the canonical form.
if !bytes.Equal(rawFile, rawCanonical) {
fileLines := strings.Split(string(rawFile), "\n")
if len(fileLines) > checkNotCanonicalLines {
fileLines = fileLines[:checkNotCanonicalLines]
}
canonicalLines := strings.Split(string(rawCanonical), "\n")
if len(canonicalLines) > checkNotCanonicalLines {
canonicalLines = canonicalLines[:checkNotCanonicalLines]
}
logger.Error("genesis document is not marshalled in the canonical form")
fmt.Fprintf(os.Stderr,
"Error: genesis document is not marshalled in the canonical form:\n"+
"\nActual marshalled genesis document (trimmed):\n%s\n\n... trimmed ...\n"+
"\nExpected marshalled genesis document (trimmed):\n%s\n\n... trimmed ...\n",
strings.Join(fileLines, "\n"), strings.Join(canonicalLines, "\n"),
)
os.Exit(1)
}
}

// Register registers the genesis sub-command and all of it's children.
Expand Down
77 changes: 40 additions & 37 deletions go/oasis-test-runner/scenario/e2e/genesis_file.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package e2e

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"strings"

genesisFile "github.com/oasisprotocol/oasis-core/go/genesis/file"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env"
Expand Down Expand Up @@ -56,10 +54,10 @@ func (s *genesisFileImpl) Run(childEnv *env.Env) error {
cfg := s.Net.Config()
cfg.GenesisFile = s.Net.GenesisPath()

if err := checkGenesisFile(s.Net.GenesisPath()); err != nil {
return fmt.Errorf("e2e/genesis-file: %w", err)
if err := s.runGenesisCheckCmd(childEnv, s.Net.GenesisPath()); err != nil {
return fmt.Errorf("e2e/genesis-file: running genesis check failed: %w", err)
}
s.Logger.Info("manually provisioned genesis file equals canonical form")
s.Logger.Info("manually provisioned genesis file passed genesis check command")

if err := s.Net.Start(); err != nil {
return fmt.Errorf("e2e/genesis-file: failed to start network: %w", err)
Expand All @@ -72,62 +70,67 @@ func (s *genesisFileImpl) Run(childEnv *env.Env) error {

// 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{
"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 {
return fmt.Errorf("e2e/genesis-file: %w", err)

if err := s.runGenesisCheckCmd(childEnv, dumpPath); err != nil {
return fmt.Errorf("e2e/genesis-file: running genesis check failed: %w", err)
}
s.Logger.Info("genesis file from dumped network state passed genesis check command")

uncanonicalPath := filepath.Join(childEnv.Dir(), "genesis_uncanonical.json")
if err := s.createUncanonicalGenesisFile(childEnv, uncanonicalPath); err != nil {
return fmt.Errorf("e2e/genesis-file: creating uncanonical genesis file failed: %w", err)
}
s.Logger.Info("genesis file from dumped network state equals canonical form")
if err := s.runGenesisCheckCmd(childEnv, uncanonicalPath); err == nil {
return fmt.Errorf("e2e/genesis-file: running genesis check for an uncanonical genesis file should fail")
}
s.Logger.Info("uncanonical genesis file didn't pass genesis check command")

return nil
}

func (s *genesisFileImpl) runGenesisCheckCmd(childEnv *env.Env, genesisFilePath string) error {
args := []string{
"genesis", "check",
"--genesis.file", genesisFilePath,
"--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("genesis check failed: error: %w output: %s", err, out.String())
}
return nil
}

// checkGenesisFile checks if the given genesis file equals the canonical form.
func checkGenesisFile(filePath string) error {
// Load genesis document from the genesis file.
provider, err := genesisFile.NewFileProvider(filePath)
func (s *genesisFileImpl) createUncanonicalGenesisFile(childEnv *env.Env, uncanonicalGenesisFilePath string) error {
provider, err := genesisFile.NewFileProvider(s.Net.GenesisPath())
if err != nil {
return fmt.Errorf("failed to open genesis file: %w", err)
}
doc, err := provider.GetGenesisDocument()
if err != nil {
return fmt.Errorf("failed to get genesis document: %w", err)
}
// Perform sanity checks on the loaded genesis document.
err = doc.SanityCheck()
if err != nil {
return fmt.Errorf("genesis document sanity check failed: %w", err)
}

// Load raw genesis file.
rawFile, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("failed to read genesis file: %w", err)
}
// Create a marshalled genesis document in the canonical form with 2 space indents.
rawCanonical, err := json.MarshalIndent(doc, "", " ")
// Create a marshalled genesis document in an uncanonical form (e.g with 4 space indents).
rawUncanonical, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal genesis document: %w", err)
}
// Genesis file should equal the canonical form.
if !bytes.Equal(rawFile, rawCanonical) {
fileLines := strings.Split(string(rawFile), "\n")
canonicalLines := strings.Split(string(rawCanonical), "\n")
return fmt.Errorf(
"genesis document is not marshalled to the canonical form:\n"+
"\nActual marshalled genesis document (trimmed):\n%s\n"+
"\nExpected marshalled genesis document (trimmed):\n%s\n",
strings.Join(fileLines[:10], "\n"), strings.Join(canonicalLines[:10], "\n"),
)
if err := ioutil.WriteFile(uncanonicalGenesisFilePath, rawUncanonical, 0o600); err != nil {
return fmt.Errorf("failed to write mashalled genesis document to file: %w", err)
}

return nil
}

0 comments on commit f4c8cef

Please sign in to comment.