Skip to content

Commit

Permalink
cmd: add gen-p2pkey command (#316)
Browse files Browse the repository at this point in the history
Makes loading vs creating p2pkeys explicit both when running charon and as commands.

category: feature
ticket: #315
  • Loading branch information
corverroos authored Mar 30, 2022
1 parent f24bf55 commit 82352b7
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 53 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
tags: "${{ steps.get-version.outputs.version }}"

- name: Generate cli reference
run: docker run ghcr.io/obolnetwork/charon/charon:${{steps.get-version.outputs.version}} charon --help > cli-reference.txt
run: docker run ghcr.io/obolnetwork/charon/charon:${{steps.get-version.outputs.version}} charon run --help > cli-reference.txt

- name: View cli reference
run: cat cli-reference.txt
Expand Down
18 changes: 6 additions & 12 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func Run(ctx context.Context, conf Config) (err error) {
return err
}

tcpNode, localEnode, err := wireP2P(ctx, life, conf, manifest)
tcpNode, localEnode, err := wireP2P(life, conf, manifest)
if err != nil {
return err
}
Expand All @@ -140,7 +140,8 @@ func Run(ctx context.Context, conf Config) (err error) {
log.Info(ctx, "Manifest loaded",
z.Int("peers", len(manifest.Peers)),
z.Str("peer_id", p2p.ShortID(tcpNode.ID())),
z.Int("peer_index", nodeIdx.PeerIdx))
z.Int("peer_index", nodeIdx.PeerIdx),
z.Str("enr", localEnode.Node().String()))

wireMonitoringAPI(life, conf.MonitoringAddr, localEnode)

Expand All @@ -153,21 +154,14 @@ func Run(ctx context.Context, conf Config) (err error) {
}

// wireP2P constructs the p2p tcp (libp2p) and udp (discv5) nodes and registers it with the life cycle manager.
func wireP2P(ctx context.Context, life *lifecycle.Manager, conf Config, manifest Manifest,
func wireP2P(life *lifecycle.Manager, conf Config, manifest Manifest,
) (host.Host, *enode.LocalNode, error) {
p2pKey := conf.TestConfig.P2PKey
if p2pKey == nil {
var err error
var loaded bool
p2pKey, loaded, err = p2p.LoadOrCreatePrivKey(conf.DataDir)
p2pKey, err = p2p.LoadPrivKey(conf.DataDir)
if err != nil {
return nil, nil, errors.Wrap(err, "load or create peer ID")
}

if loaded {
log.Info(ctx, "Loaded p2p key", z.Str("dir", conf.DataDir))
} else {
log.Info(ctx, "Generated new p2p key", z.Str("dir", conf.DataDir))
return nil, nil, errors.Wrap(err, "load p2p key")
}
}

Expand Down
10 changes: 9 additions & 1 deletion app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"flag"
"fmt"
"net"
"strings"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -150,7 +151,14 @@ func pingCluster(t *testing.T, test pingTest) {
asserter.Await(t)
cancel()

require.NoError(t, eg.Wait())
err := eg.Wait()
if err != nil && strings.Contains(err.Error(), "bind: address already in use") {
// This sometimes happens, not sure how to lock available ports...
t.Skip("couldn't bind to available port")
return
}

require.NoError(t, err)
}

// startExtBootnode creates a new discv5 listener and returns its local enode.
Expand Down
8 changes: 7 additions & 1 deletion app/simnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,13 @@ func testSimnet(t *testing.T, args simnetArgs) {
}
})

require.NoError(t, eg.Wait())
err = eg.Wait()
if err != nil && strings.Contains(err.Error(), "bind: address already in use") {
// This sometimes happens, not sure how to lock available ports...
t.Skip("couldn't bind to available port")
return
}
require.NoError(t, err)
}

// startTeku starts a teku validator client for the provided node and returns updated args.
Expand Down
1 change: 1 addition & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func New() *cobra.Command {
return newRootCmd(
newVersionCmd(runVersionCmd),
newEnrCmd(runNewENR),
newGenP2PKeyCmd(runGenP2PKey),
newRunCmd(app.Run),
newGenSimnetCmd(runGenSimnet),
)
Expand Down
29 changes: 25 additions & 4 deletions cmd/cmd_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ func TestCmdFlags(t *testing.T) {
Name string
Args []string
VersionConfig *versionConfig
appConfig *app.Config
AppConfig *app.Config
P2PConfig *p2p.Config
Datadir string
}{
{
Name: "version verbose",
Expand All @@ -50,7 +52,7 @@ func TestCmdFlags(t *testing.T) {
{
Name: "run command",
Args: slice("run"),
appConfig: &app.Config{
AppConfig: &app.Config{
Log: log.Config{
Level: "info",
Format: "console",
Expand All @@ -71,6 +73,18 @@ func TestCmdFlags(t *testing.T) {
JaegerService: "charon",
},
},
{
Name: "gen p2p",
Args: slice("gen-p2pkey"),
Datadir: "./charon/data",
P2PConfig: &p2p.Config{
UDPAddr: "127.0.0.1:30309",
TCPAddrs: []string{"127.0.0.1:13900"},
Allowlist: "",
Denylist: "",
DBPath: "",
},
},
}

for _, test := range tests {
Expand All @@ -81,8 +95,15 @@ func TestCmdFlags(t *testing.T) {
require.Equal(t, *test.VersionConfig, config)
}),
newRunCmd(func(_ context.Context, config app.Config) error {
require.NotNil(t, test.appConfig)
require.Equal(t, *test.appConfig, config)
require.NotNil(t, test.AppConfig)
require.Equal(t, *test.AppConfig, config)

return nil
}),
newGenP2PKeyCmd(func(_ io.Writer, config p2p.Config, datadir string) error {
require.NotNil(t, test.P2PConfig)
require.Equal(t, *test.P2PConfig, config)
require.Equal(t, test.Datadir, datadir)

return nil
}),
Expand Down
12 changes: 3 additions & 9 deletions cmd/enr.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,14 @@ func newEnrCmd(runFunc func(io.Writer, p2p.Config, string) error) *cobra.Command
return cmd
}

// Function for printing status of ENR for this instance.
// runNewENR loads the p2pkey from disk and prints the ENR for the provided config.
func runNewENR(w io.Writer, config p2p.Config, dataDir string) error {
identityKey, loaded, err := p2p.LoadOrCreatePrivKey(dataDir)
key, err := p2p.LoadPrivKey(dataDir)
if err != nil {
return err
}

if loaded {
_, _ = fmt.Fprintf(w, "Loaded p2p key from folder %s", dataDir)
} else {
_, _ = fmt.Fprintf(w, "Generated new p2p key to folder %s", dataDir)
}

localEnode, db, err := p2p.NewLocalEnode(config, identityKey)
localEnode, db, err := p2p.NewLocalEnode(config, key)
if err != nil {
return errors.Wrap(err, "failed to open peer DB")
}
Expand Down
66 changes: 66 additions & 0 deletions cmd/genp2pkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright © 2021 Obol Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"fmt"
"io"

"github.com/spf13/cobra"

"github.com/obolnetwork/charon/app/errors"
"github.com/obolnetwork/charon/p2p"
)

func newGenP2PKeyCmd(runFunc func(io.Writer, p2p.Config, string) error) *cobra.Command {
var (
config p2p.Config
dataDir string
)

cmd := &cobra.Command{
Use: "gen-p2pkey",
Short: "Generates a new p2p key",
Long: `Generates a new p2p authentication key (ecdsa-k1) and saves it to the data directory`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runFunc(cmd.OutOrStdout(), config, dataDir)
},
}

bindGeneralFlags(cmd.Flags(), &dataDir)
bindP2PFlags(cmd.Flags(), &config)

return cmd
}

// runGenP2PKey stores a new p2pkey to disk and prints the ENR for the provided config.
func runGenP2PKey(w io.Writer, config p2p.Config, dataDir string) error {
key, err := p2p.NewSavedPrivKey(dataDir)
if err != nil {
return err
}

localEnode, db, err := p2p.NewLocalEnode(config, key)
if err != nil {
return errors.Wrap(err, "failed to open peer DB")
}
defer db.Close()

_, _ = fmt.Fprintf(w, "Created key: %s/p2pkey\n", dataDir)
_, _ = fmt.Fprintln(w, localEnode.Node().String())

return nil
}
33 changes: 33 additions & 0 deletions cmd/genp2pkey_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright © 2021 Obol Technologies Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"io"
"os"
"testing"

"github.com/stretchr/testify/require"

"github.com/obolnetwork/charon/p2p"
)

func TestRunGenP2P(t *testing.T) {
temp, err := os.MkdirTemp("", "")
require.NoError(t, err)

err = runGenP2PKey(io.Discard, p2p.Config{}, temp)
require.NoError(t, err)
}
2 changes: 1 addition & 1 deletion cmd/gen_simnet.go → cmd/gensimnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func newPeer(clusterDir, nodeDir, charonBin string, peerIdx int, nextPort func()
Port: nextPort(),
}

p2pKey, _, err := p2p.LoadOrCreatePrivKey(nodeDir)
p2pKey, err := p2p.NewSavedPrivKey(nodeDir)
if err != nil {
return p2p.Peer{}, errors.Wrap(err, "create p2p key")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@ import (
//go:generate go test . -run=TestGenSimnet -update

func TestGenSimnet(t *testing.T) {
dir := "testdata/simnet"
require.NoError(t, os.RemoveAll(dir))
err := os.MkdirAll(dir, 0o755)
dir, err := os.MkdirTemp("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)

var buf bytes.Buffer
conf := simnetConfig{
Expand All @@ -45,7 +42,9 @@ func TestGenSimnet(t *testing.T) {
err = runGenSimnet(&buf, conf)
require.NoError(t, err)

testutil.RequireGoldenBytes(t, buf.Bytes())
out := buf.Bytes()
out = bytes.Replace(out, []byte(dir), []byte("charon-simnet"), 1)
testutil.RequireGoldenBytes(t, out)

// TODO(corver): Assert generated files.
}
2 changes: 1 addition & 1 deletion cmd/testdata/TestGenSimnet
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Referencing charon binary in scripts: charon
Created a simnet cluster:

testdata/simnet/
charon-simnet/
├─ manifest.json Cluster manifest defines the cluster; used by all nodes
├─ run_cluster.sh Convenience script to run all nodes
├─ teamocil.yml Configuration for teamocil utility to show output in different tmux panes
Expand Down
1 change: 0 additions & 1 deletion cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ func runVersionCmd(out io.Writer, config versionConfig) {
}

buildInfo, ok := debug.ReadBuildInfo()

if !ok {
_, _ = fmt.Fprintf(out, "\nFailed to gather build info")
return
Expand Down
31 changes: 14 additions & 17 deletions p2p/k1.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,29 @@ import (
"crypto/ecdsa"
"os"
"path"
"path/filepath"

"github.com/ethereum/go-ethereum/crypto"

"github.com/obolnetwork/charon/app/errors"
)

// LoadOrCreatePrivKey returns a k1 (secp256k1) private key and true from the provided folder.
// If it doesn't exist, a new key is generated and stored and returned with false.
func LoadOrCreatePrivKey(dataDir string) (*ecdsa.PrivateKey, bool, error) {
keyPath := path.Join(dataDir, "p2pkey")

key, err := crypto.LoadECDSA(keyPath)
if errors.Is(err, os.ErrNotExist) {
key, err = newSavedPrivKey(keyPath)
return key, false, err
} else if err != nil {
return nil, false, errors.Wrap(err, "load key")
func p2pKeyPath(datadir string) string {
return path.Join(datadir, "p2pkey")
}

// LoadPrivKey returns the ecdsa k1 key saved in the directory.
func LoadPrivKey(dataDir string) (*ecdsa.PrivateKey, error) {
key, err := crypto.LoadECDSA(p2pKeyPath(dataDir))
if err != nil {
return nil, errors.Wrap(err, "load key")
}

return key, true, nil
return key, nil
}

// newSavedPrivKey generates a new key and saves the new node identity.
func newSavedPrivKey(keyPath string) (*ecdsa.PrivateKey, error) {
if err := os.MkdirAll(filepath.Dir(keyPath), 0o755); err != nil {
// NewSavedPrivKey generates a new ecdsa k1 key and saves it to the directory.
func NewSavedPrivKey(datadir string) (*ecdsa.PrivateKey, error) {
if err := os.MkdirAll(datadir, 0o755); err != nil {
return nil, errors.Wrap(err, "mkdir")
}

Expand All @@ -52,7 +49,7 @@ func newSavedPrivKey(keyPath string) (*ecdsa.PrivateKey, error) {
return nil, errors.Wrap(err, "gen key")
}

err = crypto.SaveECDSA(keyPath, key)
err = crypto.SaveECDSA(p2pKeyPath(datadir), key)
if err != nil {
return nil, errors.Wrap(err, "save key")
}
Expand Down

0 comments on commit 82352b7

Please sign in to comment.