Skip to content

Commit

Permalink
feat: Get validator pubkey considering KMS (#821)
Browse files Browse the repository at this point in the history
* feat: Get validator pubkey considering KMS

* fix: lint error

* test: Add test for `ShowValidatorCmd()` for KMS

* fix: Fix to sort imports by group

* feat: Add changes for KMS support in `ShowValidatorCmd`

* fix: Change LoadchainID() to a private function, and change server_test to server to use a private function for testing
  • Loading branch information
Kynea0b authored Dec 13, 2022
1 parent 22edb12 commit 04d8d91
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 51 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 60 additions & 21 deletions server/oc_cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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{
Expand Down
233 changes: 233 additions & 0 deletions server/oc_cmds_test.go
Original file line number Diff line number Diff line change
@@ -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]
}
Loading

0 comments on commit 04d8d91

Please sign in to comment.