Skip to content

Commit

Permalink
refactor(nodebuilder): Allow setting keyring backend file with `--k…
Browse files Browse the repository at this point in the history
…eyring.backend` flag (celestiaorg#1565)

This PR introduces customisability for the keyring backend used by
celestia-node. The default backend will still remain `test`, but support
is added specifically for `file`. **NOTE** that no other backends have
explicitly tested support, but are still able to be specified and used
at a user's own discretion.

## celestia-node binary

1. Config for state now includes a field `KeyringBackend` which can
direct the node to use a backend other than the default (`test`), so
`file` for example.
2. This config field can be modified using a flag `--keyring.backend`
but it must be passed both on `init` and `start` to direct the node to
access the correct keyring for both operations.
3. `node.Init` now checks for existing keys, prompting the user for a
password **if `file` keyring backend is specified** (an existing
password if the keys already exist, and generates a new key with the
given password if no keys already exist).
4. `node.Start` will now block until user enters their password for the
keyring if keyring backend `file` is specified.

Note that `KeyringAccname` still works as intended. 

# TODO 
- [x] ensure key actually works (balance / sendtx)

# Using `--keyring.backend file`

```
celestia <node_type> init --keyring.backend file

celestia <node_type> start --keyring.backend file
```


Also resolves celestiaorg#603 and celestiaorg#1704

---------

Co-authored-by: Viacheslav <[email protected]>
  • Loading branch information
renaynay and vgonkivs authored Feb 21, 2023
1 parent ae2c637 commit c34c677
Show file tree
Hide file tree
Showing 19 changed files with 213 additions and 75 deletions.
2 changes: 1 addition & 1 deletion cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func newToken(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
ks, err := keystore.NewFSKeystore(expanded + "/keys")
ks, err := keystore.NewFSKeystore(filepath.Join(expanded, "keys"), nil)
if err != nil {
return err
}
Expand Down
8 changes: 7 additions & 1 deletion cmd/cel-key/node_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"

nodecmd "github.com/celestiaorg/celestia-node/cmd"
"github.com/celestiaorg/celestia-node/nodebuilder/p2p"
)

Expand Down Expand Up @@ -54,7 +55,12 @@ func ParseDirectoryFlags(cmd *cobra.Command) error {
}
switch nodeType {
case "bridge", "full", "light":
keyPath := fmt.Sprintf("~/.celestia-%s-%s/keys", nodeType, strings.ToLower(network))
path, err := nodecmd.DefaultNodeStorePath(nodeType, network)
if err != nil {
return err
}

keyPath := fmt.Sprintf("%s/keys", path)
fmt.Println("using directory: ", keyPath)
if err := cmd.Flags().Set(sdkflags.FlagKeyringDir, keyPath); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion cmd/cel-shed/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Custom store path is not supported yet.`,
}

s, err := nodebuilder.OpenStore(fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()),
strings.ToLower(network)))
strings.ToLower(network)), nil)
if err != nil {
return err
}
Expand Down
22 changes: 21 additions & 1 deletion cmd/flags_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

Expand Down Expand Up @@ -42,7 +43,11 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network
store := cmd.Flag(nodeStoreFlag).Value.String()
if store == "" {
tp := NodeType(ctx)
store = fmt.Sprintf("~/.celestia-%s-%s", strings.ToLower(tp.String()), strings.ToLower(string(network)))
var err error
store, err = DefaultNodeStorePath(tp.String(), string(network))
if err != nil {
return ctx, err
}
}
ctx = WithStorePath(ctx, store)

Expand All @@ -69,3 +74,18 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network
}
return ctx, nil
}

// DefaultNodeStorePath constructs the default node store path using the given
// node type and network.
func DefaultNodeStorePath(tp string, network string) (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return fmt.Sprintf(
"%s/.celestia-%s-%s",
home,
strings.ToLower(tp),
strings.ToLower(network),
), nil
}
25 changes: 22 additions & 3 deletions cmd/start.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package cmd

import (
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"

"github.com/celestiaorg/celestia-node/nodebuilder"
)

Expand All @@ -22,12 +28,25 @@ Options passed on start override configuration options only on start and are not
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

store, err := nodebuilder.OpenStore(StorePath(ctx))
// override config with all modifiers passed on start
cfg := NodeConfig(ctx)

storePath := StorePath(ctx)
keysPath := filepath.Join(storePath, "keys")

// construct ring
// TODO @renaynay: Include option for setting custom `userInput` parameter with
// implementation of https://github.com/celestiaorg/celestia-node/issues/415.
encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...)
ring, err := keyring.New(app.Name, cfg.State.KeyringBackend, keysPath, os.Stdin, encConf.Codec)
if err != nil {
return err
}

store, err := nodebuilder.OpenStore(storePath, ring)
if err != nil {
return err
}
// override config with all modifiers passed on start
cfg := NodeConfig(ctx)

nd, err := nodebuilder.NewWithConfig(NodeType(ctx), Network(ctx), store, &cfg, NodeOptions(ctx)...)
if err != nil {
Expand Down
15 changes: 13 additions & 2 deletions libs/keystore/fs_keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"io/fs"
"os"
"path/filepath"

"github.com/cosmos/cosmos-sdk/crypto/keyring"
)

// ErrNotFound is returned when the key does not exist.
Expand All @@ -15,16 +17,21 @@ var ErrNotFound = errors.New("keystore: key not found")
// fsKeystore implements persistent Keystore over OS filesystem.
type fsKeystore struct {
path string

ring keyring.Keyring
}

// NewFSKeystore creates a new Keystore over OS filesystem.
// The path must point to a directory. It is created automatically if necessary.
func NewFSKeystore(path string) (Keystore, error) {
func NewFSKeystore(path string, ring keyring.Keyring) (Keystore, error) {
err := os.Mkdir(path, 0755)
if err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("keystore: failed to make a dir: %w", err)
}
return &fsKeystore{path: path}, nil
return &fsKeystore{
path: path,
ring: ring,
}, nil
}

func (f *fsKeystore) Put(n KeyName, pk PrivKey) error {
Expand Down Expand Up @@ -122,6 +129,10 @@ func (f *fsKeystore) Path() string {
return f.path
}

func (f *fsKeystore) Keyring() keyring.Keyring {
return f.ring
}

func (f *fsKeystore) pathTo(file string) string {
return filepath.Join(f.path, file)
}
Expand Down
2 changes: 1 addition & 1 deletion libs/keystore/fs_keystore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func TestFSKeystore(t *testing.T) {
kstore, err := NewFSKeystore(t.TempDir() + "/keystore")
kstore, err := NewFSKeystore(t.TempDir()+"/keystore", nil)
require.NoError(t, err)

err = kstore.Put("test", PrivKey{Body: []byte("test_private_key")})
Expand Down
5 changes: 5 additions & 0 deletions libs/keystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keystore
import (
"fmt"

"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/multiformats/go-base32"
)

Expand Down Expand Up @@ -34,6 +35,10 @@ type Keystore interface {

// Path reports the path of the Keystore.
Path() string

// Keyring returns the keyring corresponding to the node's
// keystore.
Keyring() keyring.Keyring
}

// KeyNameFromBase32 decodes KeyName from Base32 format.
Expand Down
15 changes: 14 additions & 1 deletion libs/keystore/map_keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ package keystore
import (
"fmt"
"sync"

"github.com/cosmos/cosmos-sdk/crypto/keyring"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
)

// mapKeystore is a simple in-memory Keystore implementation.
type mapKeystore struct {
keys map[KeyName]PrivKey
keysLk sync.Mutex
ring keyring.Keyring
}

// NewMapKeystore constructs in-memory Keystore.
func NewMapKeystore() Keystore {
return &mapKeystore{keys: make(map[KeyName]PrivKey)}
return &mapKeystore{
keys: make(map[KeyName]PrivKey),
ring: keyring.NewInMemory(encoding.MakeConfig(app.ModuleEncodingRegisters...).Codec),
}
}

func (m *mapKeystore) Put(n KeyName, k PrivKey) error {
Expand Down Expand Up @@ -69,3 +78,7 @@ func (m *mapKeystore) List() ([]KeyName, error) {
func (m *mapKeystore) Path() string {
return ""
}

func (m *mapKeystore) Keyring() keyring.Keyring {
return m.ring
}
58 changes: 56 additions & 2 deletions nodebuilder/init.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package nodebuilder

import (
"fmt"
"os"
"path/filepath"

"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"

"github.com/celestiaorg/celestia-node/libs/fslock"
"github.com/celestiaorg/celestia-node/libs/utils"
"github.com/celestiaorg/celestia-node/nodebuilder/node"
Expand Down Expand Up @@ -32,7 +39,8 @@ func Init(cfg Config, path string, tp node.Type) error {
}
defer flock.Unlock() //nolint: errcheck

err = initDir(keysPath(path))
ksPath := keysPath(path)
err = initDir(ksPath)
if err != nil {
return err
}
Expand All @@ -47,7 +55,15 @@ func Init(cfg Config, path string, tp node.Type) error {
if err != nil {
return err
}
log.Infow("Saving config", "path", cfgPath)
log.Infow("Saved config", "path", cfgPath)

log.Infow("Accessing keyring...")
err = generateKeys(cfg, ksPath)
if err != nil {
log.Errorw("generating account keys", "err", err)
return err
}

log.Info("Node Store initialized")
return nil
}
Expand Down Expand Up @@ -105,3 +121,41 @@ func initDir(path string) error {
}
return os.Mkdir(path, perms)
}

// generateKeys will construct a keyring from the given keystore path and check
// if account keys already exist. If not, it will generate a new account key and
// store it.
func generateKeys(cfg Config, ksPath string) error {
encConf := encoding.MakeConfig(app.ModuleEncodingRegisters...)

if cfg.State.KeyringBackend == keyring.BackendTest {
log.Warn("Detected plaintext keyring backend. For elevated security properties, consider using" +
"the `file` keyring backend.")
}
ring, err := keyring.New(app.Name, cfg.State.KeyringBackend, ksPath, os.Stdin, encConf.Codec)
if err != nil {
return err
}
keys, err := ring.List()
if err != nil {
return err
}
if len(keys) > 0 {
// at least one key is already present
return nil
}
log.Infow("NO KEY FOUND IN STORE, GENERATING NEW KEY...", "path", ksPath)
keyInfo, mn, err := ring.NewMnemonic("my_celes_key", keyring.English, "",
"", hd.Secp256k1)
if err != nil {
return err
}
log.Info("NEW KEY GENERATED...")
addr, err := keyInfo.GetAddress()
if err != nil {
return err
}
fmt.Printf("\nNAME: %s\nADDRESS: %s\nMNEMONIC (save this somewhere safe!!!): \n%s\n\n",
keyInfo.Name, addr.String(), mn)
return nil
}
11 changes: 11 additions & 0 deletions nodebuilder/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ import (
)

func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store) fx.Option {
log.Infow("Accessing keyring...")
ks, err := store.Keystore()
if err != nil {
fx.Error(err)
}
signer, err := state.KeyringSigner(cfg.State, ks, network)
if err != nil {
fx.Error(err)
}

baseComponents := fx.Options(
fx.Supply(tp),
fx.Supply(network),
Expand All @@ -31,6 +41,7 @@ func ConstructModule(tp node.Type, network p2p.Network, cfg *Config, store Store
fx.Provide(store.Datastore),
fx.Provide(store.Keystore),
fx.Supply(node.StorePath(store.Path())),
fx.Supply(signer),
// modules provided by the node
p2p.ConstructModule(tp, &cfg.P2P),
state.ConstructModule(tp, &cfg.State),
Expand Down
6 changes: 6 additions & 0 deletions nodebuilder/state/config.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package state

import "github.com/cosmos/cosmos-sdk/crypto/keyring"

var defaultKeyringBackend = keyring.BackendTest

// Config contains configuration parameters for constructing
// the node's keyring signer.
type Config struct {
KeyringAccName string
KeyringBackend string
}

func DefaultConfig() Config {
return Config{
KeyringAccName: "",
KeyringBackend: defaultKeyringBackend,
}
}

Expand Down
Loading

0 comments on commit c34c677

Please sign in to comment.