diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index f360a8767..d5f6580ed 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -538,6 +538,7 @@ func (b *BeaconNode) registerP2P(cliCtx *cli.Context) error { HostAddress: cliCtx.String(cmd.P2PHost.Name), HostDNS: cliCtx.String(cmd.P2PHostDNS.Name), PrivateKey: cliCtx.String(cmd.P2PPrivKey.Name), + StaticPeerID: cliCtx.Bool(cmd.P2PStaticID.Name), MetaDataDir: cliCtx.String(cmd.P2PMetadata.Name), TCPPort: cliCtx.Uint(cmd.P2PTCPPort.Name), UDPPort: cliCtx.Uint(cmd.P2PUDPPort.Name), diff --git a/beacon-chain/p2p/config.go b/beacon-chain/p2p/config.go index 33e9b9577..8c12a2ee5 100644 --- a/beacon-chain/p2p/config.go +++ b/beacon-chain/p2p/config.go @@ -10,6 +10,7 @@ import ( type Config struct { NoDiscovery bool EnableUPnP bool + StaticPeerID bool StaticPeers []string BootstrapNodeAddr []string Discv5BootStrapAddr []string diff --git a/beacon-chain/p2p/options_test.go b/beacon-chain/p2p/options_test.go index 7a6c07a49..a106527d0 100644 --- a/beacon-chain/p2p/options_test.go +++ b/beacon-chain/p2p/options_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "net" "os" + "path" "testing" gethCrypto "github.com/ethereum/go-ethereum/crypto" @@ -50,6 +51,32 @@ func TestPrivateKeyLoading(t *testing.T) { assert.DeepEqual(t, rawBytes, newRaw, "Private keys do not match") } +func TestPrivateKeyLoading_StaticPrivateKey(t *testing.T) { + params.SetupTestConfigCleanup(t) + tempDir := t.TempDir() + + cfg := &Config{ + StaticPeerID: true, + DataDir: tempDir, + } + pKey, err := privKey(cfg) + require.NoError(t, err, "Could not apply option") + + newPkey, err := ecdsaprysm.ConvertToInterfacePrivkey(pKey) + require.NoError(t, err) + + retrievedKey, err := privKeyFromFile(path.Join(tempDir, keyPath)) + require.NoError(t, err) + retrievedPKey, err := ecdsaprysm.ConvertToInterfacePrivkey(retrievedKey) + require.NoError(t, err) + + rawBytes, err := retrievedPKey.Raw() + require.NoError(t, err) + newRaw, err := newPkey.Raw() + require.NoError(t, err) + assert.DeepEqual(t, rawBytes, newRaw, "Private keys do not match") +} + func TestIPV6Support(t *testing.T) { params.SetupTestConfigCleanup(t) key, err := gethCrypto.GenerateKey() diff --git a/beacon-chain/p2p/utils.go b/beacon-chain/p2p/utils.go index 95a84935e..7026be6ec 100644 --- a/beacon-chain/p2p/utils.go +++ b/beacon-chain/p2p/utils.go @@ -49,23 +49,43 @@ func privKey(cfg *Config) (*ecdsa.PrivateKey, error) { defaultKeyPath := path.Join(cfg.DataDir, keyPath) privateKeyPath := cfg.PrivateKey + // PrivateKey cli flag takes highest precedence. + if privateKeyPath != "" { + return privKeyFromFile(cfg.PrivateKey) + } + _, err := os.Stat(defaultKeyPath) defaultKeysExist := !os.IsNotExist(err) if err != nil && defaultKeysExist { return nil, err } - - if privateKeyPath == "" && !defaultKeysExist { - priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader) + // Default keys have the next highest precendence, if they exist. + if defaultKeysExist { + return privKeyFromFile(defaultKeyPath) + } + // There are no keys on the filesystem, so we need to generate one. + priv, _, err := crypto.GenerateSecp256k1Key(rand.Reader) + if err != nil { + return nil, err + } + // If the StaticPeerID flag is set, save the generated key as the default + // key, so that it will be used by default on the next node start. + if cfg.StaticPeerID { + rawbytes, err := priv.Raw() if err != nil { return nil, err } - return ecdsaprysm.ConvertFromInterfacePrivKey(priv) + dst := make([]byte, hex.EncodedLen(len(rawbytes))) + hex.Encode(dst, rawbytes) + if err := file.WriteFile(defaultKeyPath, dst); err != nil { + return nil, err + } + log.Infof("Wrote network key to file") + // Read the key from the defaultKeyPath file just written + // for the strongest guarantee that the next start will be the same as this one. + return privKeyFromFile(defaultKeyPath) } - if defaultKeysExist && privateKeyPath == "" { - privateKeyPath = defaultKeyPath - } - return privKeyFromFile(privateKeyPath) + return ecdsaprysm.ConvertFromInterfacePrivKey(priv) } // Retrieves a p2p networking private key from a file path. diff --git a/cmd/beacon-chain/main.go b/cmd/beacon-chain/main.go index 49216d094..8afcf5f34 100644 --- a/cmd/beacon-chain/main.go +++ b/cmd/beacon-chain/main.go @@ -89,6 +89,7 @@ var appFlags = []cli.Flag{ cmd.P2PHostDNS, cmd.P2PMaxPeers, cmd.P2PPrivKey, + cmd.P2PStaticID, cmd.P2PMetadata, cmd.P2PAllowList, cmd.P2PDenyList, diff --git a/cmd/beacon-chain/usage.go b/cmd/beacon-chain/usage.go index fd92c89f3..d4dc4db14 100644 --- a/cmd/beacon-chain/usage.go +++ b/cmd/beacon-chain/usage.go @@ -149,6 +149,7 @@ var appHelpFlagGroups = []flagGroup{ cmd.P2PHostDNS, cmd.P2PMaxPeers, cmd.P2PPrivKey, + cmd.P2PStaticID, cmd.P2PMetadata, cmd.P2PAllowList, cmd.P2PDenyList, diff --git a/cmd/flags.go b/cmd/flags.go index 87d01acb1..1022e8c09 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -144,6 +144,11 @@ var ( Usage: "The file containing the private key to use in communications with other peers.", Value: "", } + P2PStaticID = &cli.BoolFlag{ + Name: "p2p-static-id", + Usage: "Enables the peer id of the node to be fixed by saving the generated network key to the default key path.", + Value: false, + } // P2PMetadata defines a flag to specify the location of the peer metadata file. P2PMetadata = &cli.StringFlag{ Name: "p2p-metadata",