diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6fd6c16e..afe2b9f97c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements * (cosmovisor) [\#792](https://github.com/line/lbm-sdk/pull/792) Use upstream's cosmovisor +* (server) [\#821](https://github.com/line/lbm-sdk/pull/821) Get validator pubkey considering KMS ### Bug Fixes * (client) [\#817](https://github.com/line/lbm-sdk/pull/817) remove support for composite (BLS) type diff --git a/server/oc_cmds.go b/server/oc_cmds.go index 65e7d852e7..64e95cff0f 100644 --- a/server/oc_cmds.go +++ b/server/oc_cmds.go @@ -5,14 +5,18 @@ package server import ( "fmt" + "github.com/spf13/cobra" + yaml "gopkg.in/yaml.v2" + + cfg "github.com/line/ostracon/config" + osjson "github.com/line/ostracon/libs/json" + ostos "github.com/line/ostracon/libs/os" + "github.com/line/ostracon/node" "github.com/line/ostracon/p2p" pvm "github.com/line/ostracon/privval" + "github.com/line/ostracon/types" ostversion "github.com/line/ostracon/version" - "github.com/spf13/cobra" - yaml "gopkg.in/yaml.v2" - "github.com/line/lbm-sdk/client" - cryptocodec "github.com/line/lbm-sdk/crypto/codec" sdk "github.com/line/lbm-sdk/types" ) @@ -43,29 +47,64 @@ func ShowValidatorCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { serverCtx := GetServerContextFromCmd(cmd) cfg := serverCtx.Config - - privValidator := pvm.LoadFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()) - pk, err := privValidator.GetPubKey() - if err != nil { - return err - } - sdkPK, err := cryptocodec.FromOcPubKeyInterface(pk) - if err != nil { - return err - } - clientCtx := client.GetClientContextFromCmd(cmd) - bz, err := clientCtx.Codec.MarshalInterfaceJSON(sdkPK) - if err != nil { - return err - } - fmt.Println(string(bz)) - return nil + return showValidator(cmd, cfg) }, } return &cmd } +func showValidator(cmd *cobra.Command, config *cfg.Config) error { + var pv types.PrivValidator + if config.PrivValidatorListenAddr != "" { + chainID, err := loadChainID(config) + if err != nil { + return err + } + serverCtx := GetServerContextFromCmd(cmd) + log := serverCtx.Logger + pv, err = node.CreateAndStartPrivValidatorSocketClient(config.PrivValidatorListenAddr, chainID, log) + if err != nil { + return err + } + } else { + keyFilePath := config.PrivValidatorKeyFile() + if !ostos.FileExists(keyFilePath) { + return fmt.Errorf("private validator file %s does not exist", keyFilePath) + } + pv = pvm.LoadFilePV(keyFilePath, config.PrivValidatorStateFile()) + } + + pubKey, err := pv.GetPubKey() + if err != nil { + return fmt.Errorf("can't get pubkey: %w", err) + } + + bz, err := osjson.Marshal(pubKey) + if err != nil { + return fmt.Errorf("failed to marshal private validator pubkey: %w", err) + } + + fmt.Println(string(bz)) + return nil +} + +func loadChainID(config *cfg.Config) (string, error) { + stateDB, err := node.DefaultDBProvider(&node.DBContext{ID: "state", Config: config}) + if err != nil { + return "", err + } + defer func() { + var _ = stateDB.Close() + }() + genesisDocProvider := node.DefaultGenesisDocProviderFunc(config) + _, genDoc, err := node.LoadStateFromDBOrGenesisDocProvider(stateDB, genesisDocProvider) + if err != nil { + return "", err + } + return genDoc.ChainID, nil +} + // ShowAddressCmd - show this node's validator address func ShowAddressCmd() *cobra.Command { cmd := &cobra.Command{ diff --git a/server/oc_cmds_test.go b/server/oc_cmds_test.go new file mode 100644 index 0000000000..1ea4bfc2c2 --- /dev/null +++ b/server/oc_cmds_test.go @@ -0,0 +1,233 @@ +package server + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" + + cfg "github.com/line/ostracon/config" + "github.com/line/ostracon/crypto" + tmjson "github.com/line/ostracon/libs/json" + "github.com/line/ostracon/libs/log" + tmos "github.com/line/ostracon/libs/os" + tmrand "github.com/line/ostracon/libs/rand" + "github.com/line/ostracon/p2p" + "github.com/line/ostracon/privval" + "github.com/line/ostracon/types" + tmtime "github.com/line/ostracon/types/time" +) + +var ( + logger = log.NewOCLogger(log.NewSyncWriter(os.Stdout)) +) + +func TestShowValidator(t *testing.T) { + testCommon := newPrecedenceCommon(t) + + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) + + if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun { + t.Fatalf("function failed with [%T] %v", err, err) + } + + // ostracon init & create the server config file + initFilesWithConfig(serverCtx.Config) + output := captureStdout(t, func() { ShowValidatorCmd().ExecuteContext(ctx) }) + + // output must match the locally stored priv_validator key + privKey := loadFilePVKey(t, serverCtx.Config.PrivValidatorKeyFile()) + bz, err := tmjson.Marshal(privKey.PubKey) + require.NoError(t, err) + require.Equal(t, string(bz), output) +} + +func TestShowValidatorWithKMS(t *testing.T) { + testCommon := newPrecedenceCommon(t) + + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) + + if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun { + t.Fatalf("function failed with [%T] %v", err, err) + } + + // ostracon init & create the server config file + initFilesWithConfig(serverCtx.Config) + + chainID, err := loadChainID(serverCtx.Config) + require.NoError(t, err) + + // remove config file + if tmos.FileExists(serverCtx.Config.PrivValidatorKeyFile()) { + err := os.Remove(serverCtx.Config.PrivValidatorKeyFile()) + require.NoError(t, err) + } + + privval.WithMockKMS(t, t.TempDir(), chainID, func(addr string, privKey crypto.PrivKey) { + serverCtx.Config.PrivValidatorListenAddr = addr + require.NoFileExists(t, serverCtx.Config.PrivValidatorKeyFile()) + output := captureStdout(t, func() { ShowValidatorCmd().ExecuteContext(ctx) }) + require.NoError(t, err) + + // output must contains the KMS public key + bz, err := tmjson.Marshal(privKey.PubKey()) + require.NoError(t, err) + expected := string(bz) + require.Contains(t, output, expected) + }) +} + +func TestShowValidatorWithInefficientKMSAddress(t *testing.T) { + testCommon := newPrecedenceCommon(t) + + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) + + if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun { + t.Fatalf("function failed with [%T] %v", err, err) + } + + // ostracon init & create the server config file + initFilesWithConfig(serverCtx.Config) + + // remove config file + if tmos.FileExists(serverCtx.Config.PrivValidatorKeyFile()) { + err := os.Remove(serverCtx.Config.PrivValidatorKeyFile()) + require.NoError(t, err) + } + + serverCtx.Config.PrivValidatorListenAddr = "127.0.0.1:inefficient" + err := ShowValidatorCmd().ExecuteContext(ctx) + require.Error(t, err) +} + +func TestLoadChainID(t *testing.T) { + expected := "c57861" + config := cfg.ResetTestRootWithChainID("TestLoadChainID", expected) + defer func() { + var _ = os.RemoveAll(config.RootDir) + }() + + require.FileExists(t, config.GenesisFile()) + genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) + require.NoError(t, err) + require.Equal(t, expected, genDoc.ChainID) + + chainID, err := loadChainID(config) + require.NoError(t, err) + require.Equal(t, expected, chainID) +} + +func TestLoadChainIDWithoutStateDB(t *testing.T) { + expected := "c34091" + config := cfg.ResetTestRootWithChainID("TestLoadChainID", expected) + defer func() { + var _ = os.RemoveAll(config.RootDir) + }() + + config.DBBackend = "goleveldb" + config.DBPath = "/../path with containing chars that cannot be used\\/:*?\"<>|\x00" + + _, err := loadChainID(config) + require.Error(t, err) +} + +func initFilesWithConfig(config *cfg.Config) error { + // private validator + privValKeyFile := config.PrivValidatorKeyFile() + privValStateFile := config.PrivValidatorStateFile() + privKeyType := config.PrivValidatorKeyType() + var pv *privval.FilePV + if tmos.FileExists(privValKeyFile) { + pv = privval.LoadFilePV(privValKeyFile, privValStateFile) + logger.Info("Found private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } else { + var err error + pv, err = privval.GenFilePV(privValKeyFile, privValStateFile, privKeyType) + if err != nil { + return err + } + if pv != nil { + pv.Save() + } + logger.Info("Generated private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) + } + + nodeKeyFile := config.NodeKeyFile() + if tmos.FileExists(nodeKeyFile) { + logger.Info("Found node key", "path", nodeKeyFile) + } else { + if _, err := p2p.LoadOrGenNodeKey(nodeKeyFile); err != nil { + return err + } + logger.Info("Generated node key", "path", nodeKeyFile) + } + + // genesis file + genFile := config.GenesisFile() + if tmos.FileExists(genFile) { + logger.Info("Found genesis file", "path", genFile) + } else { + genDoc := types.GenesisDoc{ + ChainID: fmt.Sprintf("test-chain-%v", tmrand.Str(6)), + GenesisTime: tmtime.Now(), + ConsensusParams: types.DefaultConsensusParams(), + VoterParams: types.DefaultVoterParams(), + } + pubKey, err := pv.GetPubKey() + if err != nil { + return fmt.Errorf("can't get pubkey: %w", err) + } + genDoc.Validators = []types.GenesisValidator{{ + Address: pubKey.Address(), + PubKey: pubKey, + Power: 10, + }} + + if err := genDoc.SaveAs(genFile); err != nil { + return err + } + logger.Info("Generated genesis file", "path", genFile) + } + + return nil +} + +func loadFilePVKey(t *testing.T, file string) privval.FilePVKey { + // output must match the locally stored priv_validator key + keyJSONBytes, err := ioutil.ReadFile(file) + require.NoError(t, err) + privKey := privval.FilePVKey{} + err = tmjson.Unmarshal(keyJSONBytes, &privKey) + require.NoError(t, err) + return privKey +} + +func captureStdout(t *testing.T, fnc func()) string { + t.Helper() + backup := os.Stdout + defer func() { + os.Stdout = backup + }() + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("fail pipe: %v", err) + } + os.Stdout = w + fnc() + w.Close() + var buffer bytes.Buffer + if n, err := buffer.ReadFrom(r); err != nil { + t.Fatalf("fail read buf: %v - number: %v", err, n) + } + output := buffer.String() + return output[:len(output)-1] +} diff --git a/server/util_test.go b/server/util_test.go index 0f62fe85fe..c015a88d3a 100644 --- a/server/util_test.go +++ b/server/util_test.go @@ -1,4 +1,4 @@ -package server_test +package server import ( "context" @@ -13,7 +13,6 @@ import ( "github.com/spf13/cobra" "github.com/line/lbm-sdk/client/flags" - "github.com/line/lbm-sdk/server" ) var cancelledInPreRun = errors.New("Cancelled in prerun") @@ -21,7 +20,7 @@ var cancelledInPreRun = errors.New("Cancelled in prerun") // Used in each test to run the function under test via Cobra // but to always halt the command func preRunETestImpl(cmd *cobra.Command, args []string) error { - err := server.InterceptConfigsPreRunHandler(cmd, "", nil) + err := InterceptConfigsPreRunHandler(cmd, "", nil) if err != nil { return err } @@ -31,15 +30,15 @@ func preRunETestImpl(cmd *cobra.Command, args []string) error { func TestInterceptConfigsPreRunHandlerCreatesConfigFilesWhenMissing(t *testing.T) { tempDir := t.TempDir() - cmd := server.StartCmd(nil, "/foobar") + cmd := StartCmd(nil, "/foobar") if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil { t.Fatalf("Could not set home flag [%T] %v", err, err) } cmd.PreRunE = preRunETestImpl - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) } @@ -107,15 +106,15 @@ func TestInterceptConfigsPreRunHandlerReadsConfigToml(t *testing.T) { t.Fatalf("Failed closing config.toml: %v", err) } - cmd := server.StartCmd(nil, "/foobar") + cmd := StartCmd(nil, "/foobar") if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil { t.Fatalf("Could not set home flag [%T] %v", err, err) } cmd.PreRunE = preRunETestImpl - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -147,12 +146,12 @@ func TestInterceptConfigsPreRunHandlerReadsAppToml(t *testing.T) { if err := writer.Close(); err != nil { t.Fatalf("Failed closing app.toml: %v", err) } - cmd := server.StartCmd(nil, tempDir) + cmd := StartCmd(nil, tempDir) cmd.PreRunE = preRunETestImpl - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -166,7 +165,7 @@ func TestInterceptConfigsPreRunHandlerReadsAppToml(t *testing.T) { func TestInterceptConfigsPreRunHandlerReadsFlags(t *testing.T) { const testAddr = "tcp://127.1.2.3:12345" tempDir := t.TempDir() - cmd := server.StartCmd(nil, "/foobar") + cmd := StartCmd(nil, "/foobar") if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil { t.Fatalf("Could not set home flag [%T] %v", err, err) @@ -179,8 +178,8 @@ func TestInterceptConfigsPreRunHandlerReadsFlags(t *testing.T) { cmd.PreRunE = preRunETestImpl - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -194,7 +193,7 @@ func TestInterceptConfigsPreRunHandlerReadsFlags(t *testing.T) { func TestInterceptConfigsPreRunHandlerReadsEnvVars(t *testing.T) { const testAddr = "tcp://127.1.2.3:12345" tempDir := t.TempDir() - cmd := server.StartCmd(nil, "/foobar") + cmd := StartCmd(nil, "/foobar") if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil { t.Fatalf("Could not set home flag [%T] %v", err, err) } @@ -214,8 +213,8 @@ func TestInterceptConfigsPreRunHandlerReadsEnvVars(t *testing.T) { cmd.PreRunE = preRunETestImpl - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -280,7 +279,7 @@ func newPrecedenceCommon(t *testing.T) precedenceCommon { }) // Set up the command object that is used in this test - retval.cmd = server.StartCmd(nil, tempDir) + retval.cmd = StartCmd(nil, tempDir) retval.cmd.PreRunE = preRunETestImpl return retval @@ -318,8 +317,8 @@ func TestInterceptConfigsPreRunHandlerPrecedenceFlag(t *testing.T) { testCommon := newPrecedenceCommon(t) testCommon.setAll(t, &TestAddrExpected, &TestAddrNotExpected, &TestAddrNotExpected) - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -334,8 +333,8 @@ func TestInterceptConfigsPreRunHandlerPrecedenceEnvVar(t *testing.T) { testCommon := newPrecedenceCommon(t) testCommon.setAll(t, nil, &TestAddrExpected, &TestAddrNotExpected) - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -350,8 +349,8 @@ func TestInterceptConfigsPreRunHandlerPrecedenceConfigFile(t *testing.T) { testCommon := newPrecedenceCommon(t) testCommon.setAll(t, nil, nil, &TestAddrExpected) - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -366,8 +365,8 @@ func TestInterceptConfigsPreRunHandlerPrecedenceConfigDefault(t *testing.T) { testCommon := newPrecedenceCommon(t) // Do not set anything - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun { t.Fatalf("function failed with [%T] %v", err, err) @@ -387,15 +386,15 @@ func TestInterceptConfigsWithBadPermissions(t *testing.T) { if err := os.Mkdir(subDir, 0o600); err != nil { t.Fatalf("Failed to create sub directory: %v", err) } - cmd := server.StartCmd(nil, "/foobar") + cmd := StartCmd(nil, "/foobar") if err := cmd.Flags().Set(flags.FlagHome, subDir); err != nil { t.Fatalf("Could not set home flag [%T] %v", err, err) } cmd.PreRunE = preRunETestImpl - serverCtx := &server.Context{} - ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx) + serverCtx := &Context{} + ctx := context.WithValue(context.Background(), ServerContextKey, serverCtx) if err := cmd.ExecuteContext(ctx); !os.IsPermission(err) { t.Fatalf("Failed to catch permissions error, got: [%T] %v", err, err) }