diff --git a/cmd/gossamer/flags.go b/cmd/gossamer/flags.go index 8708433a73..fcc620fe1b 100644 --- a/cmd/gossamer/flags.go +++ b/cmd/gossamer/flags.go @@ -141,6 +141,10 @@ var ( Name: "genesis-spec", Usage: "Path to human-readable genesis JSON file", } + OutputSpecFlag = cli.StringFlag{ + Name: "output", + Usage: "Path to output the recently created genesis JSON file", + } ) // Network service configuration flags @@ -321,6 +325,7 @@ var ( BuildSpecFlags = append([]cli.Flag{ RawFlag, GenesisSpecFlag, + OutputSpecFlag, }, GlobalFlags...) // ExportFlags are the flags that are valid for use with the export subcommand diff --git a/cmd/gossamer/main.go b/cmd/gossamer/main.go index 92c748de2b..84dbe34650 100644 --- a/cmd/gossamer/main.go +++ b/cmd/gossamer/main.go @@ -353,6 +353,7 @@ func buildSpecAction(ctx *cli.Context) error { } var bs *dot.BuildSpec + if genesis := ctx.String(GenesisSpecFlag.Name); genesis != "" { bspec, e := dot.BuildFromGenesis(genesis, 0) if e != nil { @@ -380,17 +381,24 @@ func buildSpecAction(ctx *cli.Context) error { } var res []byte + if ctx.Bool(RawFlag.Name) { res, err = bs.ToJSONRaw() } else { res, err = bs.ToJSON() } + if err != nil { return err } - // TODO implement --output flag so that user can specify redirecting output a file. - // then this can be removed (See issue #1029) - fmt.Printf("%s", res) + + if outputPath := ctx.String(OutputSpecFlag.Name); outputPath != "" { + if err = dot.WriteGenesisSpecFile(res, outputPath); err != nil { + return err + } + } else { + fmt.Printf("%s", res) + } return nil } diff --git a/cmd/gossamer/main_test.go b/cmd/gossamer/main_test.go index 65c0fc7652..a05ebc6eac 100644 --- a/cmd/gossamer/main_test.go +++ b/cmd/gossamer/main_test.go @@ -284,6 +284,25 @@ func TestGossamerCommand(t *testing.T) { } +func TestBuildSpecCommandWithOutput(t *testing.T) { + tmpOutputfile := "/tmp/raw-genesis-spec-output.json" + buildSpecCommand := runTestGossamer(t, + "build-spec", + "--raw", + "--genesis-spec", "../../chain/gssmr/genesis-spec.json", + "--output", tmpOutputfile) + + time.Sleep(5 * time.Second) + + _, err := os.Stat(tmpOutputfile) + require.False(t, os.IsNotExist(err)) + defer os.Remove(tmpOutputfile) + + outb, errb := buildSpecCommand.GetOutput() + require.Empty(t, outb) + require.Empty(t, errb) +} + // TODO: TestExportCommand test "gossamer export" does not error // TODO: TestInitCommand test "gossamer init" does not error diff --git a/dot/build_spec.go b/dot/build_spec.go index 6e2fb6e073..70558f6825 100644 --- a/dot/build_spec.go +++ b/dot/build_spec.go @@ -18,10 +18,14 @@ package dot import ( "encoding/json" + "fmt" + "os" + "path/filepath" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/genesis" + "github.com/ChainSafe/gossamer/lib/utils" log "github.com/ChainSafe/log15" ) @@ -35,8 +39,10 @@ func (b *BuildSpec) ToJSON() ([]byte, error) { tmpGen := &genesis.Genesis{ Name: b.genesis.Name, ID: b.genesis.ID, + ChainType: b.genesis.ChainType, Bootnodes: b.genesis.Bootnodes, ProtocolID: b.genesis.ProtocolID, + Properties: b.genesis.Properties, Genesis: genesis.Fields{ Runtime: b.genesis.GenesisFields().Runtime, }, @@ -52,6 +58,7 @@ func (b *BuildSpec) ToJSONRaw() ([]byte, error) { ChainType: b.genesis.ChainType, Bootnodes: b.genesis.Bootnodes, ProtocolID: b.genesis.ProtocolID, + Properties: b.genesis.Properties, Genesis: genesis.Fields{ Raw: b.genesis.GenesisFields().Raw, }, @@ -71,6 +78,21 @@ func BuildFromGenesis(path string, authCount int) (*BuildSpec, error) { return bs, nil } +// WriteGenesisSpecFile writes the build-spec in the output filepath +func WriteGenesisSpecFile(data []byte, fp string) error { + // if file already exists then dont apply any written on it + if utils.PathExists(fp) { + return fmt.Errorf("file %s already exists, rename to avoid overwriting", fp) + } + + if err := os.MkdirAll(filepath.Dir(fp), os.ModeDir|os.ModePerm); err != nil { + return err + } + + WriteConfig(data, fp) + return nil +} + // BuildFromDB builds a BuildSpec from the DB located at path func BuildFromDB(path string) (*BuildSpec, error) { tmpGen := &genesis.Genesis{ diff --git a/dot/build_spec_test.go b/dot/build_spec_test.go index 98dea7b16b..10320e6327 100644 --- a/dot/build_spec_test.go +++ b/dot/build_spec_test.go @@ -17,6 +17,8 @@ package dot import ( "encoding/json" + "fmt" + "io/ioutil" "os" "testing" @@ -29,6 +31,17 @@ func TestBuildFromGenesis(t *testing.T) { defer os.Remove(file) require.NoError(t, err) bs, err := BuildFromGenesis(file, 0) + + expectedChainType := "TESTCHAINTYPE" + expectedProperties := map[string]interface{}{ + "ss58Format": 0.0, + "tokenDecimals": 0.0, + "tokenSymbol": "TEST", + } + + bs.genesis.ChainType = expectedChainType + bs.genesis.Properties = expectedProperties + require.NoError(t, err) // confirm human-readable fields @@ -39,6 +52,8 @@ func TestBuildFromGenesis(t *testing.T) { require.NoError(t, err) genesis.TestGenesis.Genesis = genesis.TestFieldsHR require.Equal(t, genesis.TestGenesis.Genesis.Runtime, jGen.Genesis.Runtime) + require.Equal(t, expectedChainType, jGen.ChainType) + require.Equal(t, expectedProperties, jGen.Properties) // confirm raw fields raw, err := bs.ToJSONRaw() @@ -48,6 +63,70 @@ func TestBuildFromGenesis(t *testing.T) { require.NoError(t, err) genesis.TestGenesis.Genesis = genesis.TestFieldsRaw require.Equal(t, genesis.TestGenesis.Genesis.Raw, jGenRaw.Genesis.Raw) + require.Equal(t, expectedChainType, jGenRaw.ChainType) + require.Equal(t, expectedProperties, jGenRaw.Properties) +} + +func TestBuildFromGenesis_WhenGenesisDoesNotExists(t *testing.T) { + bs, err := BuildFromGenesis("/not/exists/genesis.json", 0) + require.Nil(t, bs) + require.Error(t, err, os.ErrNotExist) +} + +func TestWriteGenesisSpecFileWhenFileAlreadyExists(t *testing.T) { + f, err := ioutil.TempFile("", "existing file data") + require.NoError(t, err) + defer os.Remove(f.Name()) + + someBytes := []byte("Testing some bytes") + err = WriteGenesisSpecFile(someBytes, f.Name()) + + require.Error(t, err, + fmt.Sprintf("file %s already exists, rename to avoid overwritten", f.Name())) +} + +func TestWriteGenesisSpecFile(t *testing.T) { + cfg := NewTestConfig(t) + cfg.Init.Genesis = "../chain/gssmr/genesis.json" + + expected, err := genesis.NewGenesisFromJSONRaw(cfg.Init.Genesis) + require.NoError(t, err) + + err = InitNode(cfg) + require.NoError(t, err) + + bs, err := BuildFromGenesis(cfg.Init.Genesis, 0) + require.NoError(t, err) + + data, err := bs.ToJSONRaw() + require.NoError(t, err) + + tmpFiles := []string{ + "/tmp/unique-raw-genesis.json", + "./unique-raw-genesis.json", + } + + for _, tmpFile := range tmpFiles { + err = WriteGenesisSpecFile(data, tmpFile) + require.NoError(t, err) + require.FileExists(t, tmpFile) + + defer os.Remove(tmpFile) + + file, err := os.Open(tmpFile) + require.NoError(t, err) + defer file.Close() + + genesisBytes, err := ioutil.ReadAll(file) + require.NoError(t, err) + + gen := new(genesis.Genesis) + err = json.Unmarshal(genesisBytes, gen) + require.NoError(t, err) + + require.Equal(t, expected.ChainType, gen.ChainType) + require.Equal(t, expected.Properties, gen.Properties) + } } func TestBuildFromDB(t *testing.T) {