mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
persistent node key and ID
This commit is contained in:
parent
f2e0abf1dc
commit
b289d2baf4
@ -72,6 +72,9 @@ type BaseConfig struct {
|
|||||||
// A JSON file containing the private key to use as a validator in the consensus protocol
|
// A JSON file containing the private key to use as a validator in the consensus protocol
|
||||||
PrivValidator string `mapstructure:"priv_validator_file"`
|
PrivValidator string `mapstructure:"priv_validator_file"`
|
||||||
|
|
||||||
|
// A JSON file containing the private key to use for p2p authenticated encryption
|
||||||
|
NodeKey string `mapstructure:"node_key"`
|
||||||
|
|
||||||
// A custom human readable name for this node
|
// A custom human readable name for this node
|
||||||
Moniker string `mapstructure:"moniker"`
|
Moniker string `mapstructure:"moniker"`
|
||||||
|
|
||||||
@ -109,6 +112,7 @@ func DefaultBaseConfig() BaseConfig {
|
|||||||
return BaseConfig{
|
return BaseConfig{
|
||||||
Genesis: "genesis.json",
|
Genesis: "genesis.json",
|
||||||
PrivValidator: "priv_validator.json",
|
PrivValidator: "priv_validator.json",
|
||||||
|
NodeKey: "node_key.json",
|
||||||
Moniker: defaultMoniker,
|
Moniker: defaultMoniker,
|
||||||
ProxyApp: "tcp://127.0.0.1:46658",
|
ProxyApp: "tcp://127.0.0.1:46658",
|
||||||
ABCI: "socket",
|
ABCI: "socket",
|
||||||
@ -141,6 +145,11 @@ func (b BaseConfig) PrivValidatorFile() string {
|
|||||||
return rootify(b.PrivValidator, b.RootDir)
|
return rootify(b.PrivValidator, b.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NodeKeyFile returns the full path to the node_key.json file
|
||||||
|
func (b BaseConfig) NodeKeyFile() string {
|
||||||
|
return rootify(b.NodeKey, b.RootDir)
|
||||||
|
}
|
||||||
|
|
||||||
// DBDir returns the full path to the database directory
|
// DBDir returns the full path to the database directory
|
||||||
func (b BaseConfig) DBDir() string {
|
func (b BaseConfig) DBDir() string {
|
||||||
return rootify(b.DBPath, b.RootDir)
|
return rootify(b.DBPath, b.RootDir)
|
||||||
|
16
node/node.go
16
node/node.go
@ -367,12 +367,20 @@ func (n *Node) OnStart() error {
|
|||||||
n.sw.AddListener(l)
|
n.sw.AddListener(l)
|
||||||
|
|
||||||
// Generate node PrivKey
|
// Generate node PrivKey
|
||||||
// TODO: Load
|
// TODO: both the loading function and the target
|
||||||
privKey := crypto.GenPrivKeyEd25519().Wrap()
|
// will need to be configurable
|
||||||
|
difficulty := uint8(16) // number of leading 0s in bitstring
|
||||||
|
target := p2p.MakePoWTarget(difficulty)
|
||||||
|
nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile(), target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.Logger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", n.config.NodeKeyFile())
|
||||||
|
|
||||||
// Start the switch
|
// Start the switch
|
||||||
n.sw.SetNodeInfo(n.makeNodeInfo(privKey.PubKey()))
|
n.sw.SetNodeInfo(n.makeNodeInfo(nodeKey.PubKey()))
|
||||||
n.sw.SetNodePrivKey(privKey)
|
n.sw.SetNodeKey(nodeKey)
|
||||||
|
n.sw.SetPeerIDTarget(target)
|
||||||
err = n.sw.Start()
|
err = n.sw.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
111
p2p/key.go
Normal file
111
p2p/key.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package p2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
crypto "github.com/tendermint/go-crypto"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Persistent peer ID
|
||||||
|
// TODO: encrypt on disk
|
||||||
|
|
||||||
|
// NodeKey is the persistent peer key.
|
||||||
|
// It contains the nodes private key for authentication.
|
||||||
|
type NodeKey struct {
|
||||||
|
PrivKey crypto.PrivKey `json:"priv_key"` // our priv key
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the peer's canonical ID - the hash of its public key.
|
||||||
|
func (nodeKey *NodeKey) ID() []byte {
|
||||||
|
return nodeKey.PrivKey.PubKey().Address()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubKey returns the peer's PubKey
|
||||||
|
func (nodeKey *NodeKey) PubKey() crypto.PubKey {
|
||||||
|
return nodeKey.PrivKey.PubKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOrGenNodeKey attempts to load the NodeKey from the given filePath,
|
||||||
|
// and checks that the corresponding ID is less than the target.
|
||||||
|
// If the file does not exist, it generates and saves a new NodeKey
|
||||||
|
// with ID less than target.
|
||||||
|
func LoadOrGenNodeKey(filePath string, target []byte) (*NodeKey, error) {
|
||||||
|
if cmn.FileExists(filePath) {
|
||||||
|
nodeKey, err := loadNodeKey(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if bytes.Compare(nodeKey.ID(), target) >= 0 {
|
||||||
|
return nil, fmt.Errorf("Loaded ID (%X) does not satisfy target (%X)", nodeKey.ID(), target)
|
||||||
|
}
|
||||||
|
return nodeKey, nil
|
||||||
|
} else {
|
||||||
|
return genNodeKey(filePath, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePoWTarget returns a 20 byte target byte array.
|
||||||
|
func MakePoWTarget(difficulty uint8) []byte {
|
||||||
|
zeroPrefixLen := (int(difficulty) / 8)
|
||||||
|
prefix := bytes.Repeat([]byte{0}, zeroPrefixLen)
|
||||||
|
mod := (difficulty % 8)
|
||||||
|
if mod > 0 {
|
||||||
|
nonZeroPrefix := byte(1 << (8 - mod))
|
||||||
|
prefix = append(prefix, nonZeroPrefix)
|
||||||
|
}
|
||||||
|
return append(prefix, bytes.Repeat([]byte{255}, 20-len(prefix))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNodeKey(filePath string) (*NodeKey, error) {
|
||||||
|
jsonBytes, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodeKey := new(NodeKey)
|
||||||
|
err = json.Unmarshal(jsonBytes, nodeKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error reading NodeKey from %v: %v\n", filePath, err)
|
||||||
|
}
|
||||||
|
return nodeKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genNodeKey(filePath string, target []byte) (*NodeKey, error) {
|
||||||
|
privKey := genPrivKeyEd25519PoW(target).Wrap()
|
||||||
|
nodeKey := &NodeKey{
|
||||||
|
PrivKey: privKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(nodeKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(filePath, jsonBytes, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nodeKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate key with address satisfying the difficult target
|
||||||
|
func genPrivKeyEd25519PoW(target []byte) crypto.PrivKeyEd25519 {
|
||||||
|
secret := crypto.CRandBytes(32)
|
||||||
|
var privKey crypto.PrivKeyEd25519
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
privKey = crypto.GenPrivKeyEd25519FromSecret(secret)
|
||||||
|
if bytes.Compare(privKey.PubKey().Address(), target) < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
z := new(big.Int)
|
||||||
|
z.SetBytes(secret)
|
||||||
|
z = z.Add(z, big.NewInt(1))
|
||||||
|
secret = z.Bytes()
|
||||||
|
|
||||||
|
}
|
||||||
|
return privKey
|
||||||
|
}
|
49
p2p/key_test.go
Normal file
49
p2p/key_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package p2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
cmn "github.com/tendermint/tmlibs/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadOrGenNodeKey(t *testing.T) {
|
||||||
|
filePath := filepath.Join(os.TempDir(), cmn.RandStr(12)+"_peer_id.json")
|
||||||
|
|
||||||
|
target := MakePoWTarget(2)
|
||||||
|
nodeKey, err := LoadOrGenNodeKey(filePath, target)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
nodeKey2, err := LoadOrGenNodeKey(filePath, target)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, nodeKey, nodeKey2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func repeatBytes(val byte, n int) []byte {
|
||||||
|
return bytes.Repeat([]byte{val}, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoWTarget(t *testing.T) {
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
difficulty uint8
|
||||||
|
target []byte
|
||||||
|
}{
|
||||||
|
{0, bytes.Repeat([]byte{255}, 20)},
|
||||||
|
{1, append([]byte{128}, repeatBytes(255, 19)...)},
|
||||||
|
{8, append([]byte{0}, repeatBytes(255, 19)...)},
|
||||||
|
{9, append([]byte{0, 128}, repeatBytes(255, 18)...)},
|
||||||
|
{10, append([]byte{0, 64}, repeatBytes(255, 18)...)},
|
||||||
|
{16, append([]byte{0, 0}, repeatBytes(255, 18)...)},
|
||||||
|
{17, append([]byte{0, 0, 128}, repeatBytes(255, 17)...)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
assert.Equal(t, MakePoWTarget(c.difficulty), c.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -82,7 +82,8 @@ type Switch struct {
|
|||||||
peers *PeerSet
|
peers *PeerSet
|
||||||
dialing *cmn.CMap
|
dialing *cmn.CMap
|
||||||
nodeInfo *NodeInfo // our node info
|
nodeInfo *NodeInfo // our node info
|
||||||
nodePrivKey crypto.PrivKey // our node privkey
|
nodeKey *NodeKey // our node privkey
|
||||||
|
peerIDTarget []byte
|
||||||
|
|
||||||
filterConnByAddr func(net.Addr) error
|
filterConnByAddr func(net.Addr) error
|
||||||
filterConnByPubKey func(crypto.PubKey) error
|
filterConnByPubKey func(crypto.PubKey) error
|
||||||
@ -181,16 +182,22 @@ func (sw *Switch) NodeInfo() *NodeInfo {
|
|||||||
return sw.nodeInfo
|
return sw.nodeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNodePrivKey sets the switch's private key for authenticated encryption.
|
// SetNodeKey sets the switch's private key for authenticated encryption.
|
||||||
// NOTE: Overwrites sw.nodeInfo.PubKey.
|
// NOTE: Overwrites sw.nodeInfo.PubKey.
|
||||||
// NOTE: Not goroutine safe.
|
// NOTE: Not goroutine safe.
|
||||||
func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKey) {
|
func (sw *Switch) SetNodeKey(nodeKey *NodeKey) {
|
||||||
sw.nodePrivKey = nodePrivKey
|
sw.nodeKey = nodeKey
|
||||||
if sw.nodeInfo != nil {
|
if sw.nodeInfo != nil {
|
||||||
sw.nodeInfo.PubKey = nodePrivKey.PubKey()
|
sw.nodeInfo.PubKey = nodeKey.PubKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPeerIDTarget sets the target for incoming peer ID's -
|
||||||
|
// the ID must be less than the target
|
||||||
|
func (sw *Switch) SetPeerIDTarget(target []byte) {
|
||||||
|
sw.peerIDTarget = target
|
||||||
|
}
|
||||||
|
|
||||||
// OnStart implements BaseService. It starts all the reactors, peers, and listeners.
|
// OnStart implements BaseService. It starts all the reactors, peers, and listeners.
|
||||||
func (sw *Switch) OnStart() error {
|
func (sw *Switch) OnStart() error {
|
||||||
// Start reactors
|
// Start reactors
|
||||||
@ -370,7 +377,7 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer,
|
|||||||
defer sw.dialing.Delete(addr.IP.String())
|
defer sw.dialing.Delete(addr.IP.String())
|
||||||
|
|
||||||
sw.Logger.Info("Dialing peer", "address", addr)
|
sw.Logger.Info("Dialing peer", "address", addr)
|
||||||
peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, sw.peerConfig)
|
peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sw.Logger.Error("Failed to dial peer", "address", addr, "err", err)
|
sw.Logger.Error("Failed to dial peer", "address", addr, "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -598,24 +605,26 @@ func StartSwitches(switches []*Switch) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch {
|
func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch {
|
||||||
privKey := crypto.GenPrivKeyEd25519()
|
|
||||||
// new switch, add reactors
|
// new switch, add reactors
|
||||||
// TODO: let the config be passed in?
|
// TODO: let the config be passed in?
|
||||||
|
nodeKey := &NodeKey{
|
||||||
|
PrivKey: crypto.GenPrivKeyEd25519().Wrap(),
|
||||||
|
}
|
||||||
s := initSwitch(i, NewSwitch(cfg))
|
s := initSwitch(i, NewSwitch(cfg))
|
||||||
s.SetNodeInfo(&NodeInfo{
|
s.SetNodeInfo(&NodeInfo{
|
||||||
PubKey: privKey.PubKey(),
|
PubKey: nodeKey.PubKey(),
|
||||||
Moniker: cmn.Fmt("switch%d", i),
|
Moniker: cmn.Fmt("switch%d", i),
|
||||||
Network: network,
|
Network: network,
|
||||||
Version: version,
|
Version: version,
|
||||||
RemoteAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023),
|
RemoteAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023),
|
||||||
ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023),
|
ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023),
|
||||||
})
|
})
|
||||||
s.SetNodePrivKey(privKey.Wrap())
|
s.SetNodeKey(nodeKey)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sw *Switch) addPeerWithConnection(conn net.Conn) error {
|
func (sw *Switch) addPeerWithConnection(conn net.Conn) error {
|
||||||
peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, sw.peerConfig)
|
peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err := conn.Close(); err != nil {
|
if err := conn.Close(); err != nil {
|
||||||
sw.Logger.Error("Error closing connection", "err", err)
|
sw.Logger.Error("Error closing connection", "err", err)
|
||||||
@ -632,7 +641,7 @@ func (sw *Switch) addPeerWithConnection(conn net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConfig) error {
|
func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConfig) error {
|
||||||
peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, config)
|
peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err := conn.Close(); err != nil {
|
if err := conn.Close(); err != nil {
|
||||||
sw.Logger.Error("Error closing connection", "err", err)
|
sw.Logger.Error("Error closing connection", "err", err)
|
||||||
|
@ -236,7 +236,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) {
|
|||||||
rp.Start()
|
rp.Start()
|
||||||
defer rp.Stop()
|
defer rp.Stop()
|
||||||
|
|
||||||
peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, DefaultPeerConfig())
|
peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig())
|
||||||
require.Nil(err)
|
require.Nil(err)
|
||||||
err = sw.addPeer(peer)
|
err = sw.addPeer(peer)
|
||||||
require.Nil(err)
|
require.Nil(err)
|
||||||
@ -263,7 +263,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
|||||||
rp.Start()
|
rp.Start()
|
||||||
defer rp.Stop()
|
defer rp.Stop()
|
||||||
|
|
||||||
peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, DefaultPeerConfig())
|
peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig())
|
||||||
peer.makePersistent()
|
peer.makePersistent()
|
||||||
require.Nil(err)
|
require.Nil(err)
|
||||||
err = sw.addPeer(peer)
|
err = sw.addPeer(peer)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user