From 528154f1a218b9c884ea0de2402304f002c601a1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 29 Dec 2017 01:53:41 -0500 Subject: [PATCH 01/32] p2p: PrivKey need not be Ed25519 --- node/node.go | 19 +++++++++---------- p2p/README.md | 1 - p2p/peer.go | 8 ++++---- p2p/peer_test.go | 18 +++++++++--------- p2p/secret_connection.go | 16 ++++++++-------- p2p/secret_connection_test.go | 15 +++++++-------- p2p/switch.go | 18 +++++++++--------- p2p/switch_test.go | 6 +++--- p2p/types.go | 14 +++++++------- 9 files changed, 56 insertions(+), 59 deletions(-) diff --git a/node/node.go b/node/node.go index f922d8321..7f845f902 100644 --- a/node/node.go +++ b/node/node.go @@ -96,7 +96,6 @@ type Node struct { privValidator types.PrivValidator // local node's validator key // network - privKey crypto.PrivKeyEd25519 // local node's p2p key sw *p2p.Switch // p2p connections addrBook *p2p.AddrBook // known peers trustMetricStore *trust.TrustMetricStore // trust metrics for all peers @@ -170,9 +169,6 @@ func NewNode(config *cfg.Config, // reload the state (it may have been updated by the handshake) state = sm.LoadState(stateDB) - // Generate node PrivKey - privKey := crypto.GenPrivKeyEd25519() - // Decide whether to fast-sync or not // We don't fast-sync when the only validator is us. fastSync := config.FastSync @@ -275,7 +271,7 @@ func NewNode(config *cfg.Config, } return nil }) - sw.SetPubKeyFilter(func(pubkey crypto.PubKeyEd25519) error { + sw.SetPubKeyFilter(func(pubkey crypto.PubKey) error { resQuery, err := proxyApp.Query().QuerySync(abci.RequestQuery{Path: cmn.Fmt("/p2p/filter/pubkey/%X", pubkey.Bytes())}) if err != nil { return err @@ -328,7 +324,6 @@ func NewNode(config *cfg.Config, genesisDoc: genDoc, privValidator: privValidator, - privKey: privKey, sw: sw, addrBook: addrBook, trustMetricStore: trustMetricStore, @@ -371,9 +366,13 @@ func (n *Node) OnStart() error { l := p2p.NewDefaultListener(protocol, address, n.config.P2P.SkipUPNP, n.Logger.With("module", "p2p")) n.sw.AddListener(l) + // Generate node PrivKey + // TODO: Load + privKey := crypto.GenPrivKeyEd25519().Wrap() + // Start the switch - n.sw.SetNodeInfo(n.makeNodeInfo()) - n.sw.SetNodePrivKey(n.privKey) + n.sw.SetNodeInfo(n.makeNodeInfo(privKey.PubKey())) + n.sw.SetNodePrivKey(privKey) err = n.sw.Start() if err != nil { return err @@ -534,13 +533,13 @@ func (n *Node) ProxyApp() proxy.AppConns { return n.proxyApp } -func (n *Node) makeNodeInfo() *p2p.NodeInfo { +func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { txIndexerStatus := "on" if _, ok := n.txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } nodeInfo := &p2p.NodeInfo{ - PubKey: n.privKey.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: pubKey, Moniker: n.config.Moniker, Network: n.genesisDoc.ChainID, Version: version.Version, diff --git a/p2p/README.md b/p2p/README.md index a30b83b7c..3d0e9eebc 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -11,4 +11,3 @@ See: - [docs/node] for details about different types of nodes and how they should work - [docs/pex] for details on peer discovery and exchange - [docs/config] for details on some config options - diff --git a/p2p/peer.go b/p2p/peer.go index cc7f4927a..91824dc8f 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -77,7 +77,7 @@ func DefaultPeerConfig() *PeerConfig { } func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { conn, err := dial(addr, config) if err != nil { @@ -95,13 +95,13 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] } func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) } func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { conn := rawConn @@ -216,7 +216,7 @@ func (p *peer) Addr() net.Addr { } // PubKey returns peer's public key. -func (p *peer) PubKey() crypto.PubKeyEd25519 { +func (p *peer) PubKey() crypto.PubKey { if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() } diff --git a/p2p/peer_test.go b/p2p/peer_test.go index b53b0bb12..a2884b336 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -16,7 +16,7 @@ func TestPeerBasic(t *testing.T) { assert, require := assert.New(t), require.New(t) // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() @@ -43,7 +43,7 @@ func TestPeerWithoutAuthEnc(t *testing.T) { config.AuthEnc = false // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: config} rp.Start() defer rp.Stop() @@ -64,7 +64,7 @@ func TestPeerSend(t *testing.T) { config.AuthEnc = false // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: config} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: config} rp.Start() defer rp.Stop() @@ -85,13 +85,13 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) {ID: 0x01, Priority: 1}, } reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} - pk := crypto.GenPrivKeyEd25519() + pk := crypto.GenPrivKeyEd25519().Wrap() p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config) if err != nil { return nil, err } err = p.HandshakeTimeout(&NodeInfo{ - PubKey: pk.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: pk.PubKey(), Moniker: "host_peer", Network: "testing", Version: "123.123.123", @@ -103,7 +103,7 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) } type remotePeer struct { - PrivKey crypto.PrivKeyEd25519 + PrivKey crypto.PrivKey Config *PeerConfig addr *NetAddress quit chan struct{} @@ -113,8 +113,8 @@ func (p *remotePeer) Addr() *NetAddress { return p.addr } -func (p *remotePeer) PubKey() crypto.PubKeyEd25519 { - return p.PrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519) +func (p *remotePeer) PubKey() crypto.PubKey { + return p.PrivKey.PubKey() } func (p *remotePeer) Start() { @@ -142,7 +142,7 @@ func (p *remotePeer) accept(l net.Listener) { golog.Fatalf("Failed to create a peer: %+v", err) } err = peer.HandshakeTimeout(&NodeInfo{ - PubKey: p.PrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: p.PrivKey.PubKey(), Moniker: "remote_peer", Network: "testing", Version: "123.123.123", diff --git a/p2p/secret_connection.go b/p2p/secret_connection.go index aec0a7519..f022d9c35 100644 --- a/p2p/secret_connection.go +++ b/p2p/secret_connection.go @@ -38,7 +38,7 @@ type SecretConnection struct { recvBuffer []byte recvNonce *[24]byte sendNonce *[24]byte - remPubKey crypto.PubKeyEd25519 + remPubKey crypto.PubKey shrSecret *[32]byte // shared secret } @@ -46,9 +46,9 @@ type SecretConnection struct { // Returns nil if error in handshake. // Caller should call conn.Close() // See docs/sts-final.pdf for more information. -func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKeyEd25519) (*SecretConnection, error) { +func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) { - locPubKey := locPrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519) + locPubKey := locPrivKey.PubKey() // Generate ephemeral keys for perfect forward secrecy. locEphPub, locEphPriv := genEphKeys() @@ -100,12 +100,12 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKeyEd25 } // We've authorized. - sc.remPubKey = remPubKey.Unwrap().(crypto.PubKeyEd25519) + sc.remPubKey = remPubKey return sc, nil } // Returns authenticated remote pubkey -func (sc *SecretConnection) RemotePubKey() crypto.PubKeyEd25519 { +func (sc *SecretConnection) RemotePubKey() crypto.PubKey { return sc.remPubKey } @@ -258,8 +258,8 @@ func genChallenge(loPubKey, hiPubKey *[32]byte) (challenge *[32]byte) { return hash32(append(loPubKey[:], hiPubKey[:]...)) } -func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKeyEd25519) (signature crypto.SignatureEd25519) { - signature = locPrivKey.Sign(challenge[:]).Unwrap().(crypto.SignatureEd25519) +func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) (signature crypto.Signature) { + signature = locPrivKey.Sign(challenge[:]) return } @@ -268,7 +268,7 @@ type authSigMessage struct { Sig crypto.Signature } -func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKeyEd25519, signature crypto.SignatureEd25519) (*authSigMessage, error) { +func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKey, signature crypto.Signature) (*authSigMessage, error) { var recvMsg authSigMessage var err1, err2 error diff --git a/p2p/secret_connection_test.go b/p2p/secret_connection_test.go index 8b58fb417..5e0611a87 100644 --- a/p2p/secret_connection_test.go +++ b/p2p/secret_connection_test.go @@ -1,7 +1,6 @@ package p2p import ( - "bytes" "io" "testing" @@ -32,10 +31,10 @@ func makeDummyConnPair() (fooConn, barConn dummyConn) { func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) { fooConn, barConn := makeDummyConnPair() - fooPrvKey := crypto.GenPrivKeyEd25519() - fooPubKey := fooPrvKey.PubKey().Unwrap().(crypto.PubKeyEd25519) - barPrvKey := crypto.GenPrivKeyEd25519() - barPubKey := barPrvKey.PubKey().Unwrap().(crypto.PubKeyEd25519) + fooPrvKey := crypto.GenPrivKeyEd25519().Wrap() + fooPubKey := fooPrvKey.PubKey() + barPrvKey := crypto.GenPrivKeyEd25519().Wrap() + barPubKey := barPrvKey.PubKey() cmn.Parallel( func() { @@ -46,7 +45,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection return } remotePubBytes := fooSecConn.RemotePubKey() - if !bytes.Equal(remotePubBytes[:], barPubKey[:]) { + if !remotePubBytes.Equals(barPubKey) { tb.Errorf("Unexpected fooSecConn.RemotePubKey. Expected %v, got %v", barPubKey, fooSecConn.RemotePubKey()) } @@ -59,7 +58,7 @@ func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection return } remotePubBytes := barSecConn.RemotePubKey() - if !bytes.Equal(remotePubBytes[:], fooPubKey[:]) { + if !remotePubBytes.Equals(fooPubKey) { tb.Errorf("Unexpected barSecConn.RemotePubKey. Expected %v, got %v", fooPubKey, barSecConn.RemotePubKey()) } @@ -93,7 +92,7 @@ func TestSecretConnectionReadWrite(t *testing.T) { genNodeRunner := func(nodeConn dummyConn, nodeWrites []string, nodeReads *[]string) func() { return func() { // Node handskae - nodePrvKey := crypto.GenPrivKeyEd25519() + nodePrvKey := crypto.GenPrivKeyEd25519().Wrap() nodeSecretConn, err := MakeSecretConnection(nodeConn, nodePrvKey) if err != nil { t.Errorf("Failed to establish SecretConnection for node: %v", err) diff --git a/p2p/switch.go b/p2p/switch.go index 76b019806..fde216429 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -81,11 +81,11 @@ type Switch struct { reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo *NodeInfo // our node info - nodePrivKey crypto.PrivKeyEd25519 // our node privkey + nodeInfo *NodeInfo // our node info + nodePrivKey crypto.PrivKey // our node privkey filterConnByAddr func(net.Addr) error - filterConnByPubKey func(crypto.PubKeyEd25519) error + filterConnByPubKey func(crypto.PubKey) error rng *rand.Rand // seed for randomizing dial times and orders } @@ -184,10 +184,10 @@ func (sw *Switch) NodeInfo() *NodeInfo { // SetNodePrivKey sets the switch's private key for authenticated encryption. // NOTE: Overwrites sw.nodeInfo.PubKey. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKeyEd25519) { +func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKey) { sw.nodePrivKey = nodePrivKey if sw.nodeInfo != nil { - sw.nodeInfo.PubKey = nodePrivKey.PubKey().Unwrap().(crypto.PubKeyEd25519) + sw.nodeInfo.PubKey = nodePrivKey.PubKey() } } @@ -285,7 +285,7 @@ func (sw *Switch) FilterConnByAddr(addr net.Addr) error { } // FilterConnByPubKey returns an error if connecting to the given public key is forbidden. -func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKeyEd25519) error { +func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKey) error { if sw.filterConnByPubKey != nil { return sw.filterConnByPubKey(pubkey) } @@ -299,7 +299,7 @@ func (sw *Switch) SetAddrFilter(f func(net.Addr) error) { } // SetPubKeyFilter sets the function for filtering connections by public key. -func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKeyEd25519) error) { +func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKey) error) { sw.filterConnByPubKey = f } @@ -603,14 +603,14 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f // TODO: let the config be passed in? s := initSwitch(i, NewSwitch(cfg)) s.SetNodeInfo(&NodeInfo{ - PubKey: privKey.PubKey().Unwrap().(crypto.PubKeyEd25519), + PubKey: privKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, Version: version, RemoteAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) - s.SetNodePrivKey(privKey) + s.SetNodePrivKey(privKey.Wrap()) return s } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 72807d36a..1d6d869af 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -200,7 +200,7 @@ func TestConnPubKeyFilter(t *testing.T) { c1, c2 := netPipe() // set pubkey filter - s1.SetPubKeyFilter(func(pubkey crypto.PubKeyEd25519) error { + s1.SetPubKeyFilter(func(pubkey crypto.PubKey) error { if bytes.Equal(pubkey.Bytes(), s2.nodeInfo.PubKey.Bytes()) { return fmt.Errorf("Error: pipe is blacklisted") } @@ -232,7 +232,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { defer sw.Stop() // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() @@ -259,7 +259,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { defer sw.Stop() // simulate remote peer - rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519(), Config: DefaultPeerConfig()} + rp := &remotePeer{PrivKey: crypto.GenPrivKeyEd25519().Wrap(), Config: DefaultPeerConfig()} rp.Start() defer rp.Stop() diff --git a/p2p/types.go b/p2p/types.go index 4e0994b71..63494d9cd 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -12,13 +12,13 @@ import ( const maxNodeInfoSize = 10240 // 10Kb type NodeInfo struct { - PubKey crypto.PubKeyEd25519 `json:"pub_key"` - Moniker string `json:"moniker"` - Network string `json:"network"` - RemoteAddr string `json:"remote_addr"` - ListenAddr string `json:"listen_addr"` - Version string `json:"version"` // major.minor.revision - Other []string `json:"other"` // other application specific data + PubKey crypto.PubKey `json:"pub_key"` + Moniker string `json:"moniker"` + Network string `json:"network"` + RemoteAddr string `json:"remote_addr"` + ListenAddr string `json:"listen_addr"` + Version string `json:"version"` // major.minor.revision + Other []string `json:"other"` // other application specific data } // CONTRACT: two nodes are compatible if the major/minor versions match and network match From f2e0abf1dc23544c7ce5cfb0bdd0fd46dc96ce1e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 20:21:11 -0500 Subject: [PATCH 02/32] p2p: reorder some checks in addPeer; add comments to NodeInfo --- p2p/peer.go | 1 + p2p/switch.go | 20 ++++++++++---------- p2p/types.go | 14 +++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 91824dc8f..e2e73f0f3 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -304,6 +304,7 @@ func (p *peer) Set(key string, data interface{}) { } // Key returns the peer's id key. +// TODO: call this ID func (p *peer) Key() string { return p.nodeInfo.ListenAddr // XXX: should probably be PubKey.KeyString() } diff --git a/p2p/switch.go b/p2p/switch.go index fde216429..d4e3b3484 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -232,10 +232,15 @@ func (sw *Switch) OnStop() { // NOTE: If error is returned, caller is responsible for calling peer.CloseConn() func (sw *Switch) addPeer(peer *peer) error { + // Avoid self + if sw.nodeInfo.PubKey.Equals(peer.PubKey().Wrap()) { + return errors.New("Ignoring connection from self") + } + + // Filter peer against white list if err := sw.FilterConnByAddr(peer.Addr()); err != nil { return err } - if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil { return err } @@ -244,9 +249,10 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Avoid self - if sw.nodeInfo.PubKey.Equals(peer.PubKey().Wrap()) { - return errors.New("Ignoring connection from self") + // Avoid duplicate + if sw.peers.Has(peer.Key()) { + return ErrSwitchDuplicatePeer + } // Check version, chain id @@ -254,12 +260,6 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Check for duplicate peer - if sw.peers.Has(peer.Key()) { - return ErrSwitchDuplicatePeer - - } - // Start peer if sw.IsRunning() { sw.startInitPeer(peer) diff --git a/p2p/types.go b/p2p/types.go index 63494d9cd..860132902 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -12,13 +12,13 @@ import ( const maxNodeInfoSize = 10240 // 10Kb type NodeInfo struct { - PubKey crypto.PubKey `json:"pub_key"` - Moniker string `json:"moniker"` - Network string `json:"network"` - RemoteAddr string `json:"remote_addr"` - ListenAddr string `json:"listen_addr"` - Version string `json:"version"` // major.minor.revision - Other []string `json:"other"` // other application specific data + PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey + Moniker string `json:"moniker"` // arbitrary moniker + Network string `json:"network"` // network/chain ID + RemoteAddr string `json:"remote_addr"` // address for the connection + ListenAddr string `json:"listen_addr"` // accepting incoming + Version string `json:"version"` // major.minor.revision + Other []string `json:"other"` // other application specific data } // CONTRACT: two nodes are compatible if the major/minor versions match and network match From b289d2baf44ae52ec9ee44b83d4f9ce2c56919b4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 20:21:42 -0500 Subject: [PATCH 03/32] persistent node key and ID --- config/config.go | 9 ++++ node/node.go | 16 +++++-- p2p/key.go | 111 +++++++++++++++++++++++++++++++++++++++++++++ p2p/key_test.go | 49 ++++++++++++++++++++ p2p/switch.go | 33 +++++++++----- p2p/switch_test.go | 4 +- 6 files changed, 204 insertions(+), 18 deletions(-) create mode 100644 p2p/key.go create mode 100644 p2p/key_test.go diff --git a/config/config.go b/config/config.go index 5d4a8ef65..6018dc8de 100644 --- a/config/config.go +++ b/config/config.go @@ -72,6 +72,9 @@ type BaseConfig struct { // A JSON file containing the private key to use as a validator in the consensus protocol 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 Moniker string `mapstructure:"moniker"` @@ -109,6 +112,7 @@ func DefaultBaseConfig() BaseConfig { return BaseConfig{ Genesis: "genesis.json", PrivValidator: "priv_validator.json", + NodeKey: "node_key.json", Moniker: defaultMoniker, ProxyApp: "tcp://127.0.0.1:46658", ABCI: "socket", @@ -141,6 +145,11 @@ func (b BaseConfig) PrivValidatorFile() string { 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 func (b BaseConfig) DBDir() string { return rootify(b.DBPath, b.RootDir) diff --git a/node/node.go b/node/node.go index 7f845f902..0027c6802 100644 --- a/node/node.go +++ b/node/node.go @@ -367,12 +367,20 @@ func (n *Node) OnStart() error { n.sw.AddListener(l) // Generate node PrivKey - // TODO: Load - privKey := crypto.GenPrivKeyEd25519().Wrap() + // TODO: both the loading function and the target + // 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 - n.sw.SetNodeInfo(n.makeNodeInfo(privKey.PubKey())) - n.sw.SetNodePrivKey(privKey) + n.sw.SetNodeInfo(n.makeNodeInfo(nodeKey.PubKey())) + n.sw.SetNodeKey(nodeKey) + n.sw.SetPeerIDTarget(target) err = n.sw.Start() if err != nil { return err diff --git a/p2p/key.go b/p2p/key.go new file mode 100644 index 000000000..aa2ac7677 --- /dev/null +++ b/p2p/key.go @@ -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 +} diff --git a/p2p/key_test.go b/p2p/key_test.go new file mode 100644 index 000000000..ef885e55d --- /dev/null +++ b/p2p/key_test.go @@ -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) + } + +} diff --git a/p2p/switch.go b/p2p/switch.go index d4e3b3484..344c3c1e1 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -81,8 +81,9 @@ type Switch struct { reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo *NodeInfo // our node info - nodePrivKey crypto.PrivKey // our node privkey + nodeInfo *NodeInfo // our node info + nodeKey *NodeKey // our node privkey + peerIDTarget []byte filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -181,16 +182,22 @@ func (sw *Switch) NodeInfo() *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: Not goroutine safe. -func (sw *Switch) SetNodePrivKey(nodePrivKey crypto.PrivKey) { - sw.nodePrivKey = nodePrivKey +func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { + sw.nodeKey = nodeKey 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. func (sw *Switch) OnStart() error { // Start reactors @@ -370,7 +377,7 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, defer sw.dialing.Delete(addr.IP.String()) 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 { sw.Logger.Error("Failed to dial peer", "address", addr, "err", 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 { - privKey := crypto.GenPrivKeyEd25519() // new switch, add reactors // TODO: let the config be passed in? + nodeKey := &NodeKey{ + PrivKey: crypto.GenPrivKeyEd25519().Wrap(), + } s := initSwitch(i, NewSwitch(cfg)) s.SetNodeInfo(&NodeInfo{ - PubKey: privKey.PubKey(), + PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, Version: version, RemoteAddr: 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 } 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 := conn.Close(); err != nil { 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 { - 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 := conn.Close(); err != nil { sw.Logger.Error("Error closing connection", "err", err) diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 1d6d869af..6c606a67a 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -236,7 +236,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { rp.Start() 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) err = sw.addPeer(peer) require.Nil(err) @@ -263,7 +263,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { rp.Start() 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() require.Nil(err) err = sw.addPeer(peer) From a17105fd46ec6c6926ffe27487790f83446eecea Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 21:27:38 -0500 Subject: [PATCH 04/32] p2p: peer.Key -> peer.ID --- benchmarks/codec_test.go | 6 ++-- blockchain/pool.go | 33 +++++++++++---------- blockchain/pool_test.go | 15 +++++----- blockchain/reactor.go | 10 +++---- blockchain/reactor_test.go | 12 ++++---- consensus/reactor.go | 10 +++---- consensus/replay.go | 12 ++++---- consensus/state.go | 47 +++++++++++++++--------------- consensus/types/height_vote_set.go | 15 +++++----- consensus/wal.go | 2 +- p2p/key.go | 17 +++++++++-- p2p/peer.go | 16 +++++----- p2p/peer_set.go | 24 +++++++-------- p2p/peer_set_test.go | 12 ++++---- p2p/pex_reactor.go | 3 +- p2p/switch.go | 2 +- p2p/switch_test.go | 4 +-- rpc/core/consensus.go | 5 ++-- rpc/core/types/responses.go | 2 +- types/vote_set.go | 7 +++-- 20 files changed, 137 insertions(+), 117 deletions(-) diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index 631b303eb..209fcd3ba 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -17,7 +17,7 @@ func BenchmarkEncodeStatusWire(b *testing.B) { pubKey := crypto.GenPrivKeyEd25519().PubKey() status := &ctypes.ResultStatus{ NodeInfo: &p2p.NodeInfo{ - PubKey: pubKey.Unwrap().(crypto.PubKeyEd25519), + PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", RemoteAddr: "SOMEADDR", @@ -42,7 +42,7 @@ func BenchmarkEncodeStatusWire(b *testing.B) { func BenchmarkEncodeNodeInfoWire(b *testing.B) { b.StopTimer() - pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) + pubKey := crypto.GenPrivKeyEd25519().PubKey() nodeInfo := &p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", @@ -63,7 +63,7 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) { func BenchmarkEncodeNodeInfoBinary(b *testing.B) { b.StopTimer() - pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) + pubKey := crypto.GenPrivKeyEd25519().PubKey() nodeInfo := &p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", diff --git a/blockchain/pool.go b/blockchain/pool.go index e39749dc0..f3148e6c5 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" flow "github.com/tendermint/tmlibs/flowrate" @@ -56,16 +57,16 @@ type BlockPool struct { height int64 // the lowest key in requesters. numPending int32 // number of requests pending assignment or block response // peers - peers map[string]*bpPeer + peers map[p2p.ID]*bpPeer maxPeerHeight int64 requestsCh chan<- BlockRequest - timeoutsCh chan<- string + timeoutsCh chan<- p2p.ID } -func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool { +func NewBlockPool(start int64, requestsCh chan<- BlockRequest, timeoutsCh chan<- p2p.ID) *BlockPool { bp := &BlockPool{ - peers: make(map[string]*bpPeer), + peers: make(map[p2p.ID]*bpPeer), requesters: make(map[int64]*bpRequester), height: start, @@ -210,7 +211,7 @@ func (pool *BlockPool) RedoRequest(height int64) { } // TODO: ensure that blocks come in order for each peer. -func (pool *BlockPool) AddBlock(peerID string, block *types.Block, blockSize int) { +func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -240,7 +241,7 @@ func (pool *BlockPool) MaxPeerHeight() int64 { } // Sets the peer's alleged blockchain height. -func (pool *BlockPool) SetPeerHeight(peerID string, height int64) { +func (pool *BlockPool) SetPeerHeight(peerID p2p.ID, height int64) { pool.mtx.Lock() defer pool.mtx.Unlock() @@ -258,14 +259,14 @@ func (pool *BlockPool) SetPeerHeight(peerID string, height int64) { } } -func (pool *BlockPool) RemovePeer(peerID string) { +func (pool *BlockPool) RemovePeer(peerID p2p.ID) { pool.mtx.Lock() defer pool.mtx.Unlock() pool.removePeer(peerID) } -func (pool *BlockPool) removePeer(peerID string) { +func (pool *BlockPool) removePeer(peerID p2p.ID) { for _, requester := range pool.requesters { if requester.getPeerID() == peerID { if requester.getBlock() != nil { @@ -321,14 +322,14 @@ func (pool *BlockPool) requestersLen() int64 { return int64(len(pool.requesters)) } -func (pool *BlockPool) sendRequest(height int64, peerID string) { +func (pool *BlockPool) sendRequest(height int64, peerID p2p.ID) { if !pool.IsRunning() { return } pool.requestsCh <- BlockRequest{height, peerID} } -func (pool *BlockPool) sendTimeout(peerID string) { +func (pool *BlockPool) sendTimeout(peerID p2p.ID) { if !pool.IsRunning() { return } @@ -357,7 +358,7 @@ func (pool *BlockPool) debug() string { type bpPeer struct { pool *BlockPool - id string + id p2p.ID recvMonitor *flow.Monitor height int64 @@ -368,7 +369,7 @@ type bpPeer struct { logger log.Logger } -func newBPPeer(pool *BlockPool, peerID string, height int64) *bpPeer { +func newBPPeer(pool *BlockPool, peerID p2p.ID, height int64) *bpPeer { peer := &bpPeer{ pool: pool, id: peerID, @@ -434,7 +435,7 @@ type bpRequester struct { redoCh chan struct{} mtx sync.Mutex - peerID string + peerID p2p.ID block *types.Block } @@ -458,7 +459,7 @@ func (bpr *bpRequester) OnStart() error { } // Returns true if the peer matches -func (bpr *bpRequester) setBlock(block *types.Block, peerID string) bool { +func (bpr *bpRequester) setBlock(block *types.Block, peerID p2p.ID) bool { bpr.mtx.Lock() if bpr.block != nil || bpr.peerID != peerID { bpr.mtx.Unlock() @@ -477,7 +478,7 @@ func (bpr *bpRequester) getBlock() *types.Block { return bpr.block } -func (bpr *bpRequester) getPeerID() string { +func (bpr *bpRequester) getPeerID() p2p.ID { bpr.mtx.Lock() defer bpr.mtx.Unlock() return bpr.peerID @@ -551,5 +552,5 @@ OUTER_LOOP: type BlockRequest struct { Height int64 - PeerID string + PeerID p2p.ID } diff --git a/blockchain/pool_test.go b/blockchain/pool_test.go index 3e347fd29..0cdbc3a9e 100644 --- a/blockchain/pool_test.go +++ b/blockchain/pool_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" @@ -15,14 +16,14 @@ func init() { } type testPeer struct { - id string + id p2p.ID height int64 } -func makePeers(numPeers int, minHeight, maxHeight int64) map[string]testPeer { - peers := make(map[string]testPeer, numPeers) +func makePeers(numPeers int, minHeight, maxHeight int64) map[p2p.ID]testPeer { + peers := make(map[p2p.ID]testPeer, numPeers) for i := 0; i < numPeers; i++ { - peerID := cmn.RandStr(12) + peerID := p2p.ID(cmn.RandStr(12)) height := minHeight + rand.Int63n(maxHeight-minHeight) peers[peerID] = testPeer{peerID, height} } @@ -32,7 +33,7 @@ func makePeers(numPeers int, minHeight, maxHeight int64) map[string]testPeer { func TestBasic(t *testing.T) { start := int64(42) peers := makePeers(10, start+1, 1000) - timeoutsCh := make(chan string, 100) + timeoutsCh := make(chan p2p.ID, 100) requestsCh := make(chan BlockRequest, 100) pool := NewBlockPool(start, requestsCh, timeoutsCh) pool.SetLogger(log.TestingLogger()) @@ -89,7 +90,7 @@ func TestBasic(t *testing.T) { func TestTimeout(t *testing.T) { start := int64(42) peers := makePeers(10, start+1, 1000) - timeoutsCh := make(chan string, 100) + timeoutsCh := make(chan p2p.ID, 100) requestsCh := make(chan BlockRequest, 100) pool := NewBlockPool(start, requestsCh, timeoutsCh) pool.SetLogger(log.TestingLogger()) @@ -127,7 +128,7 @@ func TestTimeout(t *testing.T) { // Pull from channels counter := 0 - timedOut := map[string]struct{}{} + timedOut := map[p2p.ID]struct{}{} for { select { case peerID := <-timeoutsCh: diff --git a/blockchain/reactor.go b/blockchain/reactor.go index d4b803dd6..701e04f68 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -52,7 +52,7 @@ type BlockchainReactor struct { pool *BlockPool fastSync bool requestsCh chan BlockRequest - timeoutsCh chan string + timeoutsCh chan p2p.ID } // NewBlockchainReactor returns new reactor instance. @@ -62,7 +62,7 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *Bl } requestsCh := make(chan BlockRequest, defaultChannelCapacity) - timeoutsCh := make(chan string, defaultChannelCapacity) + timeoutsCh := make(chan p2p.ID, defaultChannelCapacity) pool := NewBlockPool( store.Height()+1, requestsCh, @@ -131,7 +131,7 @@ func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) { // RemovePeer implements Reactor by removing peer from the pool. func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { - bcR.pool.RemovePeer(peer.Key()) + bcR.pool.RemovePeer(peer.ID()) } // respondToPeer loads a block and sends it to the requesting peer, @@ -170,7 +170,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) } case *bcBlockResponseMessage: // Got a block. - bcR.pool.AddBlock(src.Key(), msg.Block, len(msgBytes)) + bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes)) case *bcStatusRequestMessage: // Send peer our state. queued := src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{&bcStatusResponseMessage{bcR.store.Height()}}) @@ -179,7 +179,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) } case *bcStatusResponseMessage: // Got a peer status. Unverified. - bcR.pool.SetPeerHeight(src.Key(), msg.Height) + bcR.pool.SetPeerHeight(src.ID(), msg.Height) default: bcR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg))) } diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index fcb8a6f86..5bdd28694 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -55,7 +55,7 @@ func TestNoBlockMessageResponse(t *testing.T) { defer bcr.Stop() // Add some peers in - peer := newbcrTestPeer(cmn.RandStr(12)) + peer := newbcrTestPeer(p2p.ID(cmn.RandStr(12))) bcr.AddPeer(peer) chID := byte(0x01) @@ -113,16 +113,16 @@ func makeBlock(height int64, state sm.State) *types.Block { // The Test peer type bcrTestPeer struct { cmn.Service - key string - ch chan interface{} + id p2p.ID + ch chan interface{} } var _ p2p.Peer = (*bcrTestPeer)(nil) -func newbcrTestPeer(key string) *bcrTestPeer { +func newbcrTestPeer(id p2p.ID) *bcrTestPeer { return &bcrTestPeer{ Service: cmn.NewBaseService(nil, "bcrTestPeer", nil), - key: key, + id: id, ch: make(chan interface{}, 2), } } @@ -144,7 +144,7 @@ func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool { func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) } func (tp *bcrTestPeer) NodeInfo() *p2p.NodeInfo { return nil } func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } -func (tp *bcrTestPeer) Key() string { return tp.key } +func (tp *bcrTestPeer) ID() p2p.ID { return tp.id } func (tp *bcrTestPeer) IsOutbound() bool { return false } func (tp *bcrTestPeer) IsPersistent() bool { return true } func (tp *bcrTestPeer) Get(s string) interface{} { return s } diff --git a/consensus/reactor.go b/consensus/reactor.go index 9b3393e94..3f6ab506c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -205,7 +205,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) return } // Peer claims to have a maj23 for some BlockID at H,R,S, - votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.Key(), msg.BlockID) + votes.SetPeerMaj23(msg.Round, msg.Type, ps.Peer.ID(), msg.BlockID) // Respond with a VoteSetBitsMessage showing which votes we have. // (and consequently shows which we don't have) var ourVotes *cmn.BitArray @@ -242,12 +242,12 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) switch msg := msg.(type) { case *ProposalMessage: ps.SetHasProposal(msg.Proposal) - conR.conS.peerMsgQueue <- msgInfo{msg, src.Key()} + conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()} case *ProposalPOLMessage: ps.ApplyProposalPOLMessage(msg) case *BlockPartMessage: ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Index) - conR.conS.peerMsgQueue <- msgInfo{msg, src.Key()} + conR.conS.peerMsgQueue <- msgInfo{msg, src.ID()} default: conR.Logger.Error(cmn.Fmt("Unknown message type %v", reflect.TypeOf(msg))) } @@ -267,7 +267,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) ps.EnsureVoteBitArrays(height-1, lastCommitSize) ps.SetHasVote(msg.Vote) - cs.peerMsgQueue <- msgInfo{msg, src.Key()} + cs.peerMsgQueue <- msgInfo{msg, src.ID()} default: // don't punish (leave room for soft upgrades) @@ -1200,7 +1200,7 @@ func (ps *PeerState) StringIndented(indent string) string { %s Key %v %s PRS %v %s}`, - indent, ps.Peer.Key(), + indent, ps.Peer.ID(), indent, ps.PeerRoundState.StringIndented(indent+" "), indent) } diff --git a/consensus/replay.go b/consensus/replay.go index 784e8bd6e..881571077 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -61,21 +61,21 @@ func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan } } case msgInfo: - peerKey := m.PeerKey - if peerKey == "" { - peerKey = "local" + peerID := m.PeerID + if peerID == "" { + peerID = "local" } switch msg := m.Msg.(type) { case *ProposalMessage: p := msg.Proposal cs.Logger.Info("Replay: Proposal", "height", p.Height, "round", p.Round, "header", - p.BlockPartsHeader, "pol", p.POLRound, "peer", peerKey) + p.BlockPartsHeader, "pol", p.POLRound, "peer", peerID) case *BlockPartMessage: - cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerKey) + cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerID) case *VoteMessage: v := msg.Vote cs.Logger.Info("Replay: Vote", "height", v.Height, "round", v.Round, "type", v.Type, - "blockID", v.BlockID, "peer", peerKey) + "blockID", v.BlockID, "peer", peerID) } cs.handleMsg(m) diff --git a/consensus/state.go b/consensus/state.go index 518d81c58..56070b03a 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -17,6 +17,7 @@ import ( cfg "github.com/tendermint/tendermint/config" cstypes "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -46,8 +47,8 @@ var ( // msgs from the reactor which may update the state type msgInfo struct { - Msg ConsensusMessage `json:"msg"` - PeerKey string `json:"peer_key"` + Msg ConsensusMessage `json:"msg"` + PeerID p2p.ID `json:"peer_key"` } // internally generated messages which may update the state @@ -303,17 +304,17 @@ func (cs *ConsensusState) OpenWAL(walFile string) (WAL, error) { //------------------------------------------------------------ // Public interface for passing messages into the consensus state, possibly causing a state transition. -// If peerKey == "", the msg is considered internal. +// If peerID == "", the msg is considered internal. // Messages are added to the appropriate queue (peer or internal). // If the queue is full, the function may block. // TODO: should these return anything or let callers just use events? // AddVote inputs a vote. -func (cs *ConsensusState) AddVote(vote *types.Vote, peerKey string) (added bool, err error) { - if peerKey == "" { +func (cs *ConsensusState) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { + if peerID == "" { cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""} } else { - cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerKey} + cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerID} } // TODO: wait for event?! @@ -321,12 +322,12 @@ func (cs *ConsensusState) AddVote(vote *types.Vote, peerKey string) (added bool, } // SetProposal inputs a proposal. -func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerKey string) error { +func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerID p2p.ID) error { - if peerKey == "" { + if peerID == "" { cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""} } else { - cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerKey} + cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID} } // TODO: wait for event?! @@ -334,12 +335,12 @@ func (cs *ConsensusState) SetProposal(proposal *types.Proposal, peerKey string) } // AddProposalBlockPart inputs a part of the proposal block. -func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerKey string) error { +func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *types.Part, peerID p2p.ID) error { - if peerKey == "" { + if peerID == "" { cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""} } else { - cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerKey} + cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID} } // TODO: wait for event?! @@ -347,13 +348,13 @@ func (cs *ConsensusState) AddProposalBlockPart(height int64, round int, part *ty } // SetProposalAndBlock inputs the proposal and all block parts. -func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerKey string) error { - if err := cs.SetProposal(proposal, peerKey); err != nil { +func (cs *ConsensusState) SetProposalAndBlock(proposal *types.Proposal, block *types.Block, parts *types.PartSet, peerID p2p.ID) error { + if err := cs.SetProposal(proposal, peerID); err != nil { return err } for i := 0; i < parts.Total(); i++ { part := parts.GetPart(i) - if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerKey); err != nil { + if err := cs.AddProposalBlockPart(proposal.Height, proposal.Round, part, peerID); err != nil { return err } } @@ -561,7 +562,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) { defer cs.mtx.Unlock() var err error - msg, peerKey := mi.Msg, mi.PeerKey + msg, peerID := mi.Msg, mi.PeerID switch msg := msg.(type) { case *ProposalMessage: // will not cause transition. @@ -569,14 +570,14 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) { err = cs.setProposal(msg.Proposal) case *BlockPartMessage: // if the proposal is complete, we'll enterPrevote or tryFinalizeCommit - _, err = cs.addProposalBlockPart(msg.Height, msg.Part, peerKey != "") + _, err = cs.addProposalBlockPart(msg.Height, msg.Part, peerID != "") if err != nil && msg.Round != cs.Round { err = nil } case *VoteMessage: // attempt to add the vote and dupeout the validator if its a duplicate signature // if the vote gives us a 2/3-any or 2/3-one, we transition - err := cs.tryAddVote(msg.Vote, peerKey) + err := cs.tryAddVote(msg.Vote, peerID) if err == ErrAddingVote { // TODO: punish peer } @@ -591,7 +592,7 @@ func (cs *ConsensusState) handleMsg(mi msgInfo) { cs.Logger.Error("Unknown msg type", reflect.TypeOf(msg)) } if err != nil { - cs.Logger.Error("Error with msg", "type", reflect.TypeOf(msg), "peer", peerKey, "err", err, "msg", msg) + cs.Logger.Error("Error with msg", "type", reflect.TypeOf(msg), "peer", peerID, "err", err, "msg", msg) } } @@ -1308,8 +1309,8 @@ func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part, v } // Attempt to add the vote. if its a duplicate signature, dupeout the validator -func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error { - _, err := cs.addVote(vote, peerKey) +func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) error { + _, err := cs.addVote(vote, peerID) if err != nil { // If the vote height is off, we'll just ignore it, // But if it's a conflicting sig, add it to the cs.evpool. @@ -1335,7 +1336,7 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error { //----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, err error) { +func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { cs.Logger.Debug("addVote", "voteHeight", vote.Height, "voteType", vote.Type, "valIndex", vote.ValidatorIndex, "csHeight", cs.Height) // A precommit for the previous height? @@ -1365,7 +1366,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerKey string) (added bool, // A prevote/precommit for this height? if vote.Height == cs.Height { height := cs.Height - added, err = cs.Votes.AddVote(vote, peerKey) + added, err = cs.Votes.AddVote(vote, peerID) if added { cs.eventBus.PublishEventVote(types.EventDataVote{vote}) diff --git a/consensus/types/height_vote_set.go b/consensus/types/height_vote_set.go index 0a0a25fea..1435cf421 100644 --- a/consensus/types/height_vote_set.go +++ b/consensus/types/height_vote_set.go @@ -4,6 +4,7 @@ import ( "strings" "sync" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" ) @@ -35,7 +36,7 @@ type HeightVoteSet struct { mtx sync.Mutex round int // max tracked round roundVoteSets map[int]RoundVoteSet // keys: [0...round] - peerCatchupRounds map[string][]int // keys: peer.Key; values: at most 2 rounds + peerCatchupRounds map[p2p.ID][]int // keys: peer.ID; values: at most 2 rounds } func NewHeightVoteSet(chainID string, height int64, valSet *types.ValidatorSet) *HeightVoteSet { @@ -53,7 +54,7 @@ func (hvs *HeightVoteSet) Reset(height int64, valSet *types.ValidatorSet) { hvs.height = height hvs.valSet = valSet hvs.roundVoteSets = make(map[int]RoundVoteSet) - hvs.peerCatchupRounds = make(map[string][]int) + hvs.peerCatchupRounds = make(map[p2p.ID][]int) hvs.addRound(0) hvs.round = 0 @@ -101,8 +102,8 @@ func (hvs *HeightVoteSet) addRound(round int) { } // Duplicate votes return added=false, err=nil. -// By convention, peerKey is "" if origin is self. -func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerKey string) (added bool, err error) { +// By convention, peerID is "" if origin is self. +func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(vote.Type) { @@ -110,10 +111,10 @@ func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerKey string) (added bool, } voteSet := hvs.getVoteSet(vote.Round, vote.Type) if voteSet == nil { - if rndz := hvs.peerCatchupRounds[peerKey]; len(rndz) < 2 { + if rndz := hvs.peerCatchupRounds[peerID]; len(rndz) < 2 { hvs.addRound(vote.Round) voteSet = hvs.getVoteSet(vote.Round, vote.Type) - hvs.peerCatchupRounds[peerKey] = append(rndz, vote.Round) + hvs.peerCatchupRounds[peerID] = append(rndz, vote.Round) } else { // Peer has sent a vote that does not match our round, // for more than one round. Bad peer! @@ -206,7 +207,7 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string { // NOTE: if there are too many peers, or too much peer churn, // this can cause memory issues. // TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID string, blockID types.BlockID) { +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(type_) { diff --git a/consensus/wal.go b/consensus/wal.go index dfbef8790..88218940d 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -121,7 +121,7 @@ func (wal *baseWAL) Save(msg WALMessage) { if wal.light { // in light mode we only write new steps, timeouts, and our own votes (no proposals, block parts) if mi, ok := msg.(msgInfo); ok { - if mi.PeerKey != "" { + if mi.PeerID != "" { return } } diff --git a/p2p/key.go b/p2p/key.go index aa2ac7677..eede7ce7c 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -2,6 +2,7 @@ package p2p import ( "bytes" + "encoding/hex" "encoding/json" "fmt" "io/ioutil" @@ -21,8 +22,14 @@ type NodeKey struct { PrivKey crypto.PrivKey `json:"priv_key"` // our priv key } +type ID string + // ID returns the peer's canonical ID - the hash of its public key. -func (nodeKey *NodeKey) ID() []byte { +func (nodeKey *NodeKey) ID() ID { + return ID(hex.EncodeToString(nodeKey.id())) +} + +func (nodeKey *NodeKey) id() []byte { return nodeKey.PrivKey.PubKey().Address() } @@ -31,6 +38,10 @@ func (nodeKey *NodeKey) PubKey() crypto.PubKey { return nodeKey.PrivKey.PubKey() } +func (nodeKey *NodeKey) SatisfiesTarget(target []byte) bool { + return bytes.Compare(nodeKey.id(), target) < 0 +} + // 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 @@ -41,8 +52,8 @@ func LoadOrGenNodeKey(filePath string, target []byte) (*NodeKey, error) { 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) + if !nodeKey.SatisfiesTarget(target) { + return nil, fmt.Errorf("Loaded ID (%s) does not satisfy target (%X)", nodeKey.ID(), target) } return nodeKey, nil } else { diff --git a/p2p/peer.go b/p2p/peer.go index e2e73f0f3..35556a597 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -1,6 +1,7 @@ package p2p import ( + "encoding/hex" "fmt" "net" "time" @@ -17,7 +18,7 @@ import ( type Peer interface { cmn.Service - Key() string + ID() ID IsOutbound() bool IsPersistent() bool NodeInfo() *NodeInfo @@ -282,15 +283,15 @@ func (p *peer) CanSend(chID byte) bool { // String representation. func (p *peer) String() string { if p.outbound { - return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.Key()) + return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID()) } - return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.Key()) + return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) } // Equals reports whenever 2 peers are actually represent the same node. func (p *peer) Equals(other Peer) bool { - return p.Key() == other.Key() + return p.ID() == other.ID() } // Get the data for a given key. @@ -303,10 +304,9 @@ func (p *peer) Set(key string, data interface{}) { p.Data.Set(key, data) } -// Key returns the peer's id key. -// TODO: call this ID -func (p *peer) Key() string { - return p.nodeInfo.ListenAddr // XXX: should probably be PubKey.KeyString() +// Key returns the peer's ID - the hex encoded hash of its pubkey. +func (p *peer) ID() ID { + return ID(hex.EncodeToString(p.nodeInfo.PubKey.Address())) } // NodeInfo returns a copy of the peer's NodeInfo. diff --git a/p2p/peer_set.go b/p2p/peer_set.go index c21748cf9..dc53174a1 100644 --- a/p2p/peer_set.go +++ b/p2p/peer_set.go @@ -6,8 +6,8 @@ import ( // IPeerSet has a (immutable) subset of the methods of PeerSet. type IPeerSet interface { - Has(key string) bool - Get(key string) Peer + Has(key ID) bool + Get(key ID) Peer List() []Peer Size() int } @@ -18,7 +18,7 @@ type IPeerSet interface { // Iteration over the peers is super fast and thread-safe. type PeerSet struct { mtx sync.Mutex - lookup map[string]*peerSetItem + lookup map[ID]*peerSetItem list []Peer } @@ -30,7 +30,7 @@ type peerSetItem struct { // NewPeerSet creates a new peerSet with a list of initial capacity of 256 items. func NewPeerSet() *PeerSet { return &PeerSet{ - lookup: make(map[string]*peerSetItem), + lookup: make(map[ID]*peerSetItem), list: make([]Peer, 0, 256), } } @@ -40,7 +40,7 @@ func NewPeerSet() *PeerSet { func (ps *PeerSet) Add(peer Peer) error { ps.mtx.Lock() defer ps.mtx.Unlock() - if ps.lookup[peer.Key()] != nil { + if ps.lookup[peer.ID()] != nil { return ErrSwitchDuplicatePeer } @@ -48,13 +48,13 @@ func (ps *PeerSet) Add(peer Peer) error { // Appending is safe even with other goroutines // iterating over the ps.list slice. ps.list = append(ps.list, peer) - ps.lookup[peer.Key()] = &peerSetItem{peer, index} + ps.lookup[peer.ID()] = &peerSetItem{peer, index} return nil } // Has returns true iff the PeerSet contains // the peer referred to by this peerKey. -func (ps *PeerSet) Has(peerKey string) bool { +func (ps *PeerSet) Has(peerKey ID) bool { ps.mtx.Lock() _, ok := ps.lookup[peerKey] ps.mtx.Unlock() @@ -62,7 +62,7 @@ func (ps *PeerSet) Has(peerKey string) bool { } // Get looks up a peer by the provided peerKey. -func (ps *PeerSet) Get(peerKey string) Peer { +func (ps *PeerSet) Get(peerKey ID) Peer { ps.mtx.Lock() defer ps.mtx.Unlock() item, ok := ps.lookup[peerKey] @@ -77,7 +77,7 @@ func (ps *PeerSet) Get(peerKey string) Peer { func (ps *PeerSet) Remove(peer Peer) { ps.mtx.Lock() defer ps.mtx.Unlock() - item := ps.lookup[peer.Key()] + item := ps.lookup[peer.ID()] if item == nil { return } @@ -90,18 +90,18 @@ func (ps *PeerSet) Remove(peer Peer) { // If it's the last peer, that's an easy special case. if index == len(ps.list)-1 { ps.list = newList - delete(ps.lookup, peer.Key()) + delete(ps.lookup, peer.ID()) return } // Replace the popped item with the last item in the old list. lastPeer := ps.list[len(ps.list)-1] - lastPeerKey := lastPeer.Key() + lastPeerKey := lastPeer.ID() lastPeerItem := ps.lookup[lastPeerKey] newList[index] = lastPeer lastPeerItem.index = index ps.list = newList - delete(ps.lookup, peer.Key()) + delete(ps.lookup, peer.ID()) } // Size returns the number of unique items in the peerSet. diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index a7f29315a..609c49004 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" + crypto "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" ) @@ -16,6 +17,7 @@ func randPeer() *peer { nodeInfo: &NodeInfo{ RemoteAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), + PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, } } @@ -39,7 +41,7 @@ func TestPeerSetAddRemoveOne(t *testing.T) { peerSet.Remove(peerAtFront) wantSize := n - i - 1 for j := 0; j < 2; j++ { - assert.Equal(t, false, peerSet.Has(peerAtFront.Key()), "#%d Run #%d: failed to remove peer", i, j) + assert.Equal(t, false, peerSet.Has(peerAtFront.ID()), "#%d Run #%d: failed to remove peer", i, j) assert.Equal(t, wantSize, peerSet.Size(), "#%d Run #%d: failed to remove peer and decrement size", i, j) // Test the route of removing the now non-existent element peerSet.Remove(peerAtFront) @@ -58,7 +60,7 @@ func TestPeerSetAddRemoveOne(t *testing.T) { for i := n - 1; i >= 0; i-- { peerAtEnd := peerList[i] peerSet.Remove(peerAtEnd) - assert.Equal(t, false, peerSet.Has(peerAtEnd.Key()), "#%d: failed to remove item at end", i) + assert.Equal(t, false, peerSet.Has(peerAtEnd.ID()), "#%d: failed to remove item at end", i) assert.Equal(t, i, peerSet.Size(), "#%d: differing sizes after peerSet.Remove(atEndPeer)", i) } } @@ -82,7 +84,7 @@ func TestPeerSetAddRemoveMany(t *testing.T) { for i, peer := range peers { peerSet.Remove(peer) - if peerSet.Has(peer.Key()) { + if peerSet.Has(peer.ID()) { t.Errorf("Failed to remove peer") } if peerSet.Size() != len(peers)-i-1 { @@ -129,7 +131,7 @@ func TestPeerSetGet(t *testing.T) { t.Parallel() peerSet := NewPeerSet() peer := randPeer() - assert.Nil(t, peerSet.Get(peer.Key()), "expecting a nil lookup, before .Add") + assert.Nil(t, peerSet.Get(peer.ID()), "expecting a nil lookup, before .Add") if err := peerSet.Add(peer); err != nil { t.Fatalf("Failed to add new peer: %v", err) @@ -142,7 +144,7 @@ func TestPeerSetGet(t *testing.T) { wg.Add(1) go func(i int) { defer wg.Done() - got, want := peerSet.Get(peer.Key()), peer + got, want := peerSet.Get(peer.ID()), peer assert.Equal(t, got, want, "#%d: got=%v want=%v", i, got, want) }(i) } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 2bfe7dcab..17b9d61ac 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -265,7 +265,8 @@ func (r *PEXReactor) ensurePeers() { continue } // XXX: Should probably use pubkey as peer key ... - if connected := r.Switch.Peers().Has(try.String()); connected { + // TODO: use the ID correctly + if connected := r.Switch.Peers().Has(ID(try.String())); connected { continue } r.Logger.Info("Will dial address", "addr", try) diff --git a/p2p/switch.go b/p2p/switch.go index 344c3c1e1..4da9355a1 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -257,7 +257,7 @@ func (sw *Switch) addPeer(peer *peer) error { } // Avoid duplicate - if sw.peers.Has(peer.Key()) { + if sw.peers.Has(peer.ID()) { return ErrSwitchDuplicatePeer } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 6c606a67a..7d61fa39a 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -28,7 +28,7 @@ func init() { } type PeerMessage struct { - PeerKey string + PeerID ID Bytes []byte Counter int } @@ -77,7 +77,7 @@ func (tr *TestReactor) Receive(chID byte, peer Peer, msgBytes []byte) { tr.mtx.Lock() defer tr.mtx.Unlock() //fmt.Printf("Received: %X, %X\n", chID, msgBytes) - tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.Key(), msgBytes, tr.msgsCounter}) + tr.msgsReceived[chID] = append(tr.msgsReceived[chID], PeerMessage{peer.ID(), msgBytes, tr.msgsCounter}) tr.msgsCounter++ } } diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 65c9fc364..25b67925c 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -3,6 +3,7 @@ package core import ( cm "github.com/tendermint/tendermint/consensus" cstypes "github.com/tendermint/tendermint/consensus/types" + p2p "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -82,11 +83,11 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { // } // ``` func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - peerRoundStates := make(map[string]*cstypes.PeerRoundState) + peerRoundStates := make(map[p2p.ID]*cstypes.PeerRoundState) for _, peer := range p2pSwitch.Peers().List() { peerState := peer.Get(types.PeerStateKey).(*cm.PeerState) peerRoundState := peerState.GetRoundState() - peerRoundStates[peer.Key()] = peerRoundState + peerRoundStates[peer.ID()] = peerRoundState } return &ctypes.ResultDumpConsensusState{consensusState.GetRoundState(), peerRoundStates}, nil } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index dae7c0046..97d227c1d 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -99,7 +99,7 @@ type ResultValidators struct { type ResultDumpConsensusState struct { RoundState *cstypes.RoundState `json:"round_state"` - PeerRoundStates map[string]*cstypes.PeerRoundState `json:"peer_round_states"` + PeerRoundStates map[p2p.ID]*cstypes.PeerRoundState `json:"peer_round_states"` } type ResultBroadcastTx struct { diff --git a/types/vote_set.go b/types/vote_set.go index 34f989567..a97676f6e 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" + "github.com/tendermint/tendermint/p2p" cmn "github.com/tendermint/tmlibs/common" ) @@ -58,7 +59,7 @@ type VoteSet struct { sum int64 // Sum of voting power for seen votes, discounting conflicts maj23 *BlockID // First 2/3 majority seen votesByBlock map[string]*blockVotes // string(blockHash|blockParts) -> blockVotes - peerMaj23s map[string]BlockID // Maj23 for each peer + peerMaj23s map[p2p.ID]BlockID // Maj23 for each peer } // Constructs a new VoteSet struct used to accumulate votes for given height/round. @@ -77,7 +78,7 @@ func NewVoteSet(chainID string, height int64, round int, type_ byte, valSet *Val sum: 0, maj23: nil, votesByBlock: make(map[string]*blockVotes, valSet.Size()), - peerMaj23s: make(map[string]BlockID), + peerMaj23s: make(map[p2p.ID]BlockID), } } @@ -290,7 +291,7 @@ func (voteSet *VoteSet) addVerifiedVote(vote *Vote, blockKey string, votingPower // this can cause memory issues. // TODO: implement ability to remove peers too // NOTE: VoteSet must not be nil -func (voteSet *VoteSet) SetPeerMaj23(peerID string, blockID BlockID) { +func (voteSet *VoteSet) SetPeerMaj23(peerID p2p.ID, blockID BlockID) { if voteSet == nil { cmn.PanicSanity("SetPeerMaj23() on nil VoteSet") } From 7d35500e6b8f8d7110cd6c64d6204d84c87fee25 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 22:34:49 -0500 Subject: [PATCH 05/32] p2p: add ID to NetAddress and use for AddrBook --- p2p/addrbook.go | 34 +++++++++++++++++++--------------- p2p/addrbook_test.go | 5 ++++- p2p/key.go | 4 ++-- p2p/netaddress.go | 10 ++++++++-- p2p/pex_reactor.go | 19 ++++++++++--------- p2p/pex_reactor_test.go | 2 ++ p2p/switch.go | 18 ++++++++++-------- 7 files changed, 55 insertions(+), 37 deletions(-) diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 8f924d129..6ccec61f7 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -89,7 +89,7 @@ type AddrBook struct { mtx sync.Mutex rand *rand.Rand ourAddrs map[string]*NetAddress - addrLookup map[string]*knownAddress // new & old + addrLookup map[ID]*knownAddress // new & old bucketsOld []map[string]*knownAddress bucketsNew []map[string]*knownAddress nOld int @@ -104,7 +104,7 @@ func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook { am := &AddrBook{ rand: rand.New(rand.NewSource(time.Now().UnixNano())), ourAddrs: make(map[string]*NetAddress), - addrLookup: make(map[string]*knownAddress), + addrLookup: make(map[ID]*knownAddress), filePath: filePath, routabilityStrict: routabilityStrict, } @@ -244,11 +244,11 @@ func (a *AddrBook) PickAddress(newBias int) *NetAddress { } // MarkGood marks the peer as good and moves it into an "old" bucket. -// XXX: we never call this! +// TODO: call this from somewhere func (a *AddrBook) MarkGood(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka == nil { return } @@ -262,7 +262,7 @@ func (a *AddrBook) MarkGood(addr *NetAddress) { func (a *AddrBook) MarkAttempt(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka == nil { return } @@ -279,11 +279,11 @@ func (a *AddrBook) MarkBad(addr *NetAddress) { func (a *AddrBook) RemoveAddress(addr *NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka == nil { return } - a.Logger.Info("Remove address from book", "addr", addr) + a.Logger.Info("Remove address from book", "addr", ka.Addr) a.removeFromAllBuckets(ka) } @@ -300,8 +300,8 @@ func (a *AddrBook) GetSelection() []*NetAddress { allAddr := make([]*NetAddress, a.size()) i := 0 - for _, v := range a.addrLookup { - allAddr[i] = v.Addr + for _, ka := range a.addrLookup { + allAddr[i] = ka.Addr i++ } @@ -388,7 +388,7 @@ func (a *AddrBook) loadFromFile(filePath string) bool { bucket := a.getBucket(ka.BucketType, bucketIndex) bucket[ka.Addr.String()] = ka } - a.addrLookup[ka.Addr.String()] = ka + a.addrLookup[ka.ID()] = ka if ka.BucketType == bucketTypeNew { a.nNew++ } else { @@ -466,7 +466,7 @@ func (a *AddrBook) addToNewBucket(ka *knownAddress, bucketIdx int) bool { } // Ensure in addrLookup - a.addrLookup[addrStr] = ka + a.addrLookup[ka.ID()] = ka return true } @@ -503,7 +503,7 @@ func (a *AddrBook) addToOldBucket(ka *knownAddress, bucketIdx int) bool { } // Ensure in addrLookup - a.addrLookup[addrStr] = ka + a.addrLookup[ka.ID()] = ka return true } @@ -521,7 +521,7 @@ func (a *AddrBook) removeFromBucket(ka *knownAddress, bucketType byte, bucketIdx } else { a.nOld-- } - delete(a.addrLookup, ka.Addr.String()) + delete(a.addrLookup, ka.ID()) } } @@ -536,7 +536,7 @@ func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) { } else { a.nOld-- } - delete(a.addrLookup, ka.Addr.String()) + delete(a.addrLookup, ka.ID()) } func (a *AddrBook) pickOldest(bucketType byte, bucketIdx int) *knownAddress { @@ -559,7 +559,7 @@ func (a *AddrBook) addAddress(addr, src *NetAddress) error { return fmt.Errorf("Cannot add ourselves with address %v", addr) } - ka := a.addrLookup[addr.String()] + ka := a.addrLookup[addr.ID] if ka != nil { // Already old. @@ -768,6 +768,10 @@ func newKnownAddress(addr *NetAddress, src *NetAddress) *knownAddress { } } +func (ka *knownAddress) ID() ID { + return ka.Addr.ID +} + func (ka *knownAddress) isOld() bool { return ka.BucketType == bucketTypeOld } diff --git a/p2p/addrbook_test.go b/p2p/addrbook_test.go index d84c008ed..07ab3474c 100644 --- a/p2p/addrbook_test.go +++ b/p2p/addrbook_test.go @@ -1,12 +1,14 @@ package p2p import ( + "encoding/hex" "fmt" "io/ioutil" "math/rand" "testing" "github.com/stretchr/testify/assert" + cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" ) @@ -102,7 +104,7 @@ func TestAddrBookLookup(t *testing.T) { src := addrSrc.src book.AddAddress(addr, src) - ka := book.addrLookup[addr.String()] + ka := book.addrLookup[addr.ID] assert.NotNil(t, ka, "Expected to find KnownAddress %v but wasn't there.", addr) if !(ka.Addr.Equals(addr) && ka.Src.Equals(src)) { @@ -188,6 +190,7 @@ func randIPv4Address(t *testing.T) *NetAddress { ) port := rand.Intn(65535-1) + 1 addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) + addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) // TODO assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/key.go b/p2p/key.go index eede7ce7c..d0031458d 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -12,6 +12,8 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) +type ID string + //------------------------------------------------------------------------------ // Persistent peer ID // TODO: encrypt on disk @@ -22,8 +24,6 @@ type NodeKey struct { PrivKey crypto.PrivKey `json:"priv_key"` // our priv key } -type ID string - // ID returns the peer's canonical ID - the hash of its public key. func (nodeKey *NodeKey) ID() ID { return ID(hex.EncodeToString(nodeKey.id())) diff --git a/p2p/netaddress.go b/p2p/netaddress.go index 41c2cc976..8176cfde4 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -16,8 +16,9 @@ import ( ) // NetAddress defines information about a peer on the network -// including its IP address, and port. +// including its ID, IP address, and port. type NetAddress struct { + ID ID IP net.IP Port uint16 str string @@ -122,10 +123,15 @@ func (na *NetAddress) Less(other interface{}) bool { // String representation. func (na *NetAddress) String() string { if na.str == "" { - na.str = net.JoinHostPort( + + addrStr := net.JoinHostPort( na.IP.String(), strconv.FormatUint(uint64(na.Port), 10), ) + if na.ID != "" { + addrStr = fmt.Sprintf("%s@%s", na.ID, addrStr) + } + na.str = addrStr } return na.str } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 17b9d61ac..47169a1b4 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -107,6 +107,7 @@ func (r *PEXReactor) AddPeer(p Peer) { } } else { // For inbound connections, the peer is its own source addr, err := NewNetAddressString(p.NodeInfo().ListenAddr) + addr.ID = p.ID() // TODO: handle in NewNetAddress func if err != nil { // peer gave us a bad ListenAddr. TODO: punish r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err) @@ -154,9 +155,9 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { case *pexAddrsMessage: // We received some peer addresses from src. // TODO: (We don't want to get spammed with bad peers) - for _, addr := range msg.Addrs { - if addr != nil { - r.book.AddAddress(addr, srcAddr) + for _, netAddr := range msg.Addrs { + if netAddr != nil { + r.book.AddAddress(netAddr, srcAddr) } } default: @@ -170,8 +171,8 @@ func (r *PEXReactor) RequestPEX(p Peer) { } // SendAddrs sends addrs to the peer. -func (r *PEXReactor) SendAddrs(p Peer, addrs []*NetAddress) { - p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) +func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*NetAddress) { + p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: netAddrs}}) } // SetEnsurePeersPeriod sets period to ensure peers connected. @@ -258,19 +259,19 @@ func (r *PEXReactor) ensurePeers() { if try == nil { continue } - if _, selected := toDial[try.IP.String()]; selected { + if _, selected := toDial[string(try.ID)]; selected { continue } - if dialling := r.Switch.IsDialing(try); dialling { + if dialling := r.Switch.IsDialing(try.ID); dialling { continue } // XXX: Should probably use pubkey as peer key ... // TODO: use the ID correctly - if connected := r.Switch.Peers().Has(ID(try.String())); connected { + if connected := r.Switch.Peers().Has(try.ID); connected { continue } r.Logger.Info("Will dial address", "addr", try) - toDial[try.IP.String()] = try + toDial[string(try.ID)] = try } // Dial picked addresses diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index a14f0eb2a..a830b6c44 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" @@ -197,6 +198,7 @@ func createRandomPeer(outbound bool) *peer { nodeInfo: &NodeInfo{ ListenAddr: addr, RemoteAddr: netAddr.String(), + PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, mconn: &MConnection{RemoteAddress: netAddr}, diff --git a/p2p/switch.go b/p2p/switch.go index 4da9355a1..8c89ee01d 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -325,6 +325,7 @@ func (sw *Switch) startInitPeer(peer *peer) { // DialSeeds dials a list of seeds asynchronously in random order. func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { netAddrs, errs := NewNetAddressStrings(seeds) + // TODO: IDs for _, err := range errs { sw.Logger.Error("Error in seed's address", "err", err) } @@ -373,8 +374,8 @@ func (sw *Switch) dialSeed(addr *NetAddress) { // DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { - sw.dialing.Set(addr.IP.String(), addr) - defer sw.dialing.Delete(addr.IP.String()) + sw.dialing.Set(string(addr.ID), addr) + defer sw.dialing.Delete(string(addr.ID)) sw.Logger.Info("Dialing peer", "address", addr) peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) @@ -396,9 +397,9 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, return peer, nil } -// IsDialing returns true if the switch is currently dialing the given address. -func (sw *Switch) IsDialing(addr *NetAddress) bool { - return sw.dialing.Has(addr.IP.String()) +// IsDialing returns true if the switch is currently dialing the given ID. +func (sw *Switch) IsDialing(id ID) bool { + return sw.dialing.Has(string(id)) } // Broadcast runs a go routine for each attempted send, which will block @@ -454,7 +455,8 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { // If no success after all that, it stops trying, and leaves it // to the PEX/Addrbook to find the peer again func (sw *Switch) reconnectToPeer(peer Peer) { - addr, _ := NewNetAddressString(peer.NodeInfo().RemoteAddr) + netAddr, _ := NewNetAddressString(peer.NodeInfo().RemoteAddr) + netAddr.ID = peer.ID() // TODO: handle above start := time.Now() sw.Logger.Info("Reconnecting to peer", "peer", peer) for i := 0; i < reconnectAttempts; i++ { @@ -462,7 +464,7 @@ func (sw *Switch) reconnectToPeer(peer Peer) { return } - peer, err := sw.DialPeerWithAddress(addr, true) + peer, err := sw.DialPeerWithAddress(netAddr, true) if err != nil { sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) // sleep a set amount @@ -484,7 +486,7 @@ func (sw *Switch) reconnectToPeer(peer Peer) { // sleep an exponentially increasing amount sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) - peer, err := sw.DialPeerWithAddress(addr, true) + peer, err := sw.DialPeerWithAddress(netAddr, true) if err != nil { sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) continue From 6e823c6e8735c5e351f054f7f608b0ddc81d81c4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 23:08:20 -0500 Subject: [PATCH 06/32] p2p: support addr format ID@IP:PORT --- p2p/connection.go | 6 ---- p2p/netaddress.go | 63 +++++++++++++++++++++++++---------------- p2p/netaddress_test.go | 14 +++++++-- p2p/peer_test.go | 2 +- p2p/pex_reactor_test.go | 2 +- p2p/switch.go | 2 ++ 6 files changed, 54 insertions(+), 35 deletions(-) diff --git a/p2p/connection.go b/p2p/connection.go index 626aeb10f..306eaf7eb 100644 --- a/p2p/connection.go +++ b/p2p/connection.go @@ -88,9 +88,6 @@ type MConnection struct { flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. pingTimer *cmn.RepeatTimer // send pings periodically chStatsTimer *cmn.RepeatTimer // update channel stats periodically - - LocalAddress *NetAddress - RemoteAddress *NetAddress } // MConnConfig is a MConnection configuration. @@ -140,9 +137,6 @@ func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onRec onReceive: onReceive, onError: onError, config: config, - - LocalAddress: NewNetAddress(conn.LocalAddr()), - RemoteAddress: NewNetAddress(conn.RemoteAddr()), } // Create channels diff --git a/p2p/netaddress.go b/p2p/netaddress.go index 8176cfde4..d804e3488 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -5,6 +5,7 @@ package p2p import ( + "encoding/hex" "flag" "fmt" "net" @@ -12,6 +13,7 @@ import ( "strings" "time" + "github.com/pkg/errors" cmn "github.com/tendermint/tmlibs/common" ) @@ -29,25 +31,45 @@ type NetAddress struct { // using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will // panic. // TODO: socks proxies? -func NewNetAddress(addr net.Addr) *NetAddress { +func NewNetAddress(id ID, addr net.Addr) *NetAddress { tcpAddr, ok := addr.(*net.TCPAddr) if !ok { if flag.Lookup("test.v") == nil { // normal run cmn.PanicSanity(cmn.Fmt("Only TCPAddrs are supported. Got: %v", addr)) } else { // in testing - return NewNetAddressIPPort(net.IP("0.0.0.0"), 0) + netAddr := NewNetAddressIPPort(net.IP("0.0.0.0"), 0) + netAddr.ID = id + return netAddr } } ip := tcpAddr.IP port := uint16(tcpAddr.Port) - return NewNetAddressIPPort(ip, port) + netAddr := NewNetAddressIPPort(ip, port) + netAddr.ID = id + return netAddr } // NewNetAddressString returns a new NetAddress using the provided // address in the form of "IP:Port". Also resolves the host if host // is not an IP. func NewNetAddressString(addr string) (*NetAddress, error) { - host, portStr, err := net.SplitHostPort(removeProtocolIfDefined(addr)) + addr = removeProtocolIfDefined(addr) + + var id ID + spl := strings.Split(addr, "@") + if len(spl) == 2 { + idStr := spl[0] + idBytes, err := hex.DecodeString(idStr) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("Address (%s) contains invalid ID", addr)) + } + if len(idBytes) != 20 { + return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be 20 hex-encoded bytes", len(idBytes)) + } + id, addr = ID(idStr), spl[1] + } + + host, portStr, err := net.SplitHostPort(addr) if err != nil { return nil, err } @@ -69,6 +91,7 @@ func NewNetAddressString(addr string) (*NetAddress, error) { } na := NewNetAddressIPPort(ip, uint16(port)) + na.ID = id return na, nil } @@ -94,10 +117,6 @@ func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { na := &NetAddress{ IP: ip, Port: port, - str: net.JoinHostPort( - ip.String(), - strconv.FormatUint(uint64(port), 10), - ), } return na } @@ -111,23 +130,10 @@ func (na *NetAddress) Equals(other interface{}) bool { return false } -func (na *NetAddress) Less(other interface{}) bool { - if o, ok := other.(*NetAddress); ok { - return na.String() < o.String() - } - - cmn.PanicSanity("Cannot compare unequal types") - return false -} - -// String representation. +// String representation: @: func (na *NetAddress) String() string { if na.str == "" { - - addrStr := net.JoinHostPort( - na.IP.String(), - strconv.FormatUint(uint64(na.Port), 10), - ) + addrStr := na.DialString() if na.ID != "" { addrStr = fmt.Sprintf("%s@%s", na.ID, addrStr) } @@ -136,9 +142,16 @@ func (na *NetAddress) String() string { return na.str } +func (na *NetAddress) DialString() string { + return net.JoinHostPort( + na.IP.String(), + strconv.FormatUint(uint64(na.Port), 10), + ) +} + // Dial calls net.Dial on the address. func (na *NetAddress) Dial() (net.Conn, error) { - conn, err := net.Dial("tcp", na.String()) + conn, err := net.Dial("tcp", na.DialString()) if err != nil { return nil, err } @@ -147,7 +160,7 @@ func (na *NetAddress) Dial() (net.Conn, error) { // DialTimeout calls net.DialTimeout on the address. func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { - conn, err := net.DialTimeout("tcp", na.String(), timeout) + conn, err := net.DialTimeout("tcp", na.DialString(), timeout) if err != nil { return nil, err } diff --git a/p2p/netaddress_test.go b/p2p/netaddress_test.go index 137be090c..0aa45423c 100644 --- a/p2p/netaddress_test.go +++ b/p2p/netaddress_test.go @@ -13,12 +13,12 @@ func TestNewNetAddress(t *testing.T) { tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") require.Nil(err) - addr := NewNetAddress(tcpAddr) + addr := NewNetAddress("", tcpAddr) assert.Equal("127.0.0.1:8080", addr.String()) assert.NotPanics(func() { - NewNetAddress(&net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000}) + NewNetAddress("", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8000}) }, "Calling NewNetAddress with UDPAddr should not panic in testing") } @@ -38,6 +38,16 @@ func TestNewNetAddressString(t *testing.T) { {"notahost:8080", "", false}, {"8082", "", false}, {"127.0.0:8080000", "", false}, + + {"deadbeef@127.0.0.1:8080", "", false}, + {"this-isnot-hex@127.0.0.1:8080", "", false}, + {"xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, + {"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true}, + + {"tcp://deadbeef@127.0.0.1:8080", "", false}, + {"tcp://this-isnot-hex@127.0.0.1:8080", "", false}, + {"tcp://xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, + {"tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true}, } for _, tc := range testCases { diff --git a/p2p/peer_test.go b/p2p/peer_test.go index a2884b336..78a4e2f5b 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -122,7 +122,7 @@ func (p *remotePeer) Start() { if e != nil { golog.Fatalf("net.Listen tcp :0: %+v", e) } - p.addr = NewNetAddress(l.Addr()) + p.addr = NewNetAddress("", l.Addr()) p.quit = make(chan struct{}) go p.accept(l) } diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index a830b6c44..22703746e 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -201,7 +201,7 @@ func createRandomPeer(outbound bool) *peer { PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, - mconn: &MConnection{RemoteAddress: netAddr}, + mconn: &MConnection{}, } p.SetLogger(log.TestingLogger().With("peer", addr)) return p diff --git a/p2p/switch.go b/p2p/switch.go index 8c89ee01d..22d3d5a5f 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -12,6 +12,7 @@ import ( crypto "github.com/tendermint/go-crypto" cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" ) const ( @@ -622,6 +623,7 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) s.SetNodeKey(nodeKey) + s.SetLogger(log.TestingLogger()) return s } From 488ae529add8d90c7835991978450a55e7da9a28 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jan 2018 23:23:11 -0500 Subject: [PATCH 07/32] p2p: authenticate peer ID --- p2p/peer.go | 12 ++++++------ p2p/switch.go | 11 +++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 35556a597..ecf2efce5 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -92,6 +92,7 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] } return nil, err } + return peer, nil } @@ -218,13 +219,12 @@ func (p *peer) Addr() net.Addr { // PubKey returns peer's public key. func (p *peer) PubKey() crypto.PubKey { - if p.config.AuthEnc { + if p.NodeInfo() != nil { + return p.nodeInfo.PubKey + } else if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() } - if p.NodeInfo() == nil { - panic("Attempt to get peer's PubKey before calling Handshake") - } - return p.PubKey() + panic("Attempt to get peer's PubKey before calling Handshake") } // OnStart implements BaseService. @@ -306,7 +306,7 @@ func (p *peer) Set(key string, data interface{}) { // Key returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { - return ID(hex.EncodeToString(p.nodeInfo.PubKey.Address())) + return ID(hex.EncodeToString(p.PubKey().Address())) } // NodeInfo returns a copy of the peer's NodeInfo. diff --git a/p2p/switch.go b/p2p/switch.go index 22d3d5a5f..e86acfad7 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -239,9 +239,8 @@ func (sw *Switch) OnStop() { // NOTE: This performs a blocking handshake before the peer is added. // NOTE: If error is returned, caller is responsible for calling peer.CloseConn() func (sw *Switch) addPeer(peer *peer) error { - // Avoid self - if sw.nodeInfo.PubKey.Equals(peer.PubKey().Wrap()) { + if sw.nodeKey.ID() == peer.ID() { return errors.New("Ignoring connection from self") } @@ -385,6 +384,14 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, return nil, err } peer.SetLogger(sw.Logger.With("peer", addr)) + + // authenticate peer + if addr.ID == "" { + peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) + } else if addr.ID != peer.ID() { + return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) + } + if persistent { peer.makePersistent() } From 179d6062e4c385b67af414129d6c3cd9b09cb79a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 27 Dec 2017 14:16:49 -0600 Subject: [PATCH 08/32] try to connect through addrbook before requesting peers from seeds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit we only use seeds if we can’t connect to peers in the addrbook. Refs #864 --- node/node.go | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/node/node.go b/node/node.go index fde8e1e0a..f8164955d 100644 --- a/node/node.go +++ b/node/node.go @@ -8,6 +8,7 @@ import ( "net" "net/http" "strings" + "time" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -379,19 +380,42 @@ func (n *Node) OnStart() error { return err } - // If seeds exist, add them to the address book and dial out - if n.config.P2P.Seeds != "" { - // dial out - seeds := strings.Split(n.config.P2P.Seeds, ",") - if err := n.DialSeeds(seeds); err != nil { - return err - } + err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() + if err != nil { + return err } // start tx indexer return n.indexerService.Start() } +func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() error { + if n.config.P2P.Seeds == "" { + return nil + } + + seeds := strings.Split(n.config.P2P.Seeds, ",") + + // prefer peers from address book + if n.config.P2P.PexReactor && n.addrBook.Size() > 0 { + // give some time to PexReactor to connect us to other peers + const fallbackToSeedsAfterSec = 30 * time.Second + go func() { + time.Sleep(fallbackToSeedsAfterSec) + // fallback to dialing seeds if for some reason we can't connect to any + // peers + outbound, inbound, _ := n.sw.NumPeers() + if n.IsRunning() && outbound+inbound == 0 { + n.DialSeeds(seeds) + } + }() + return nil + } + + // add seeds to the address book and dial out + return n.DialSeeds(seeds) +} + // OnStop stops the Node. It implements cmn.Service. func (n *Node) OnStop() { n.BaseService.OnStop() From 28fc15028a16ed866354ca8f4be853effda14ca6 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 28 Dec 2017 12:54:39 -0600 Subject: [PATCH 09/32] distinguish between seeds and manual peers in the config/flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - we only use seeds if we can’t connect to peers in the addrbook. - we always connect to nodes given in config/flags Refs #864 --- benchmarks/blockchain/localsync.sh | 2 +- cmd/tendermint/commands/run_node.go | 1 + config/config.go | 7 +++- config/toml.go | 2 + docs/deploy-testnets.rst | 6 +-- docs/specification/configuration.rst | 2 + docs/specification/rpc.rst | 2 +- docs/using-tendermint.rst | 10 ++--- node/node.go | 41 +++++++++---------- p2p/pex_reactor.go | 2 +- p2p/switch.go | 28 ++++++------- rpc/client/localclient.go | 4 +- rpc/client/mock/client.go | 4 +- rpc/core/doc.go | 2 +- rpc/core/net.go | 16 ++++---- rpc/core/pipe.go | 2 +- rpc/core/routes.go | 2 +- rpc/core/types/responses.go | 2 +- test/p2p/README.md | 2 +- test/p2p/fast_sync/test_peer.sh | 6 +-- test/p2p/local_testnet_start.sh | 10 ++--- test/p2p/manual_peers.sh | 12 ++++++ .../{dial_seeds.sh => dial_manual_peers.sh} | 12 +++--- test/p2p/pex/test.sh | 4 +- test/p2p/pex/test_addrbook.sh | 8 ++-- ...ial_seeds.sh => test_dial_manual_peers.sh} | 14 +++---- test/p2p/seeds.sh | 12 ------ test/p2p/test.sh | 4 +- 28 files changed, 112 insertions(+), 107 deletions(-) create mode 100644 test/p2p/manual_peers.sh rename test/p2p/pex/{dial_seeds.sh => dial_manual_peers.sh} (60%) rename test/p2p/pex/{test_dial_seeds.sh => test_dial_manual_peers.sh} (75%) delete mode 100644 test/p2p/seeds.sh diff --git a/benchmarks/blockchain/localsync.sh b/benchmarks/blockchain/localsync.sh index e181c5655..18afaee82 100755 --- a/benchmarks/blockchain/localsync.sh +++ b/benchmarks/blockchain/localsync.sh @@ -51,7 +51,7 @@ tendermint node \ --proxy_app dummy \ --p2p.laddr tcp://127.0.0.1:56666 \ --rpc.laddr tcp://127.0.0.1:56667 \ - --p2p.seeds 127.0.0.1:56656 \ + --p2p.manual_peers 127.0.0.1:56656 \ --log_level error & # wait for node to start up so we only count time where we are actually syncing diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 0f37bb319..5b87a4b82 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -29,6 +29,7 @@ func AddNodeFlags(cmd *cobra.Command) { // p2p flags cmd.Flags().String("p2p.laddr", config.P2P.ListenAddress, "Node listen address. (0.0.0.0:0 means any interface, any port)") cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma delimited host:port seed nodes") + cmd.Flags().String("p2p.manual_peers", config.P2P.ManualPeers, "Comma delimited host:port manual peers") cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange") diff --git a/config/config.go b/config/config.go index 5d4a8ef65..2d38ed916 100644 --- a/config/config.go +++ b/config/config.go @@ -170,7 +170,7 @@ type RPCConfig struct { // NOTE: This server only supports /broadcast_tx_commit GRPCListenAddress string `mapstructure:"grpc_laddr"` - // Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool + // Activate unsafe RPC commands like /dial_manual_peers and /unsafe_flush_mempool Unsafe bool `mapstructure:"unsafe"` } @@ -203,8 +203,13 @@ type P2PConfig struct { ListenAddress string `mapstructure:"laddr"` // Comma separated list of seed nodes to connect to + // We only use these if we can’t connect to peers in the addrbook Seeds string `mapstructure:"seeds"` + // Comma separated list of manual peers to connect to + // We always connect to these + ManualPeers string `mapstructure:"manual_peers"` + // Skip UPNP port forwarding SkipUPNP bool `mapstructure:"skip_upnp"` diff --git a/config/toml.go b/config/toml.go index 735f45c12..59fc46dcf 100644 --- a/config/toml.go +++ b/config/toml.go @@ -42,6 +42,7 @@ laddr = "tcp://0.0.0.0:46657" [p2p] laddr = "tcp://0.0.0.0:46656" seeds = "" +manual_peers = "" ` func defaultConfig(moniker string) string { @@ -106,6 +107,7 @@ laddr = "tcp://0.0.0.0:36657" [p2p] laddr = "tcp://0.0.0.0:36656" seeds = "" +manual_peers = "" ` func testConfig(moniker string) (testConfig string) { diff --git a/docs/deploy-testnets.rst b/docs/deploy-testnets.rst index 89fa4b799..8c66c4b34 100644 --- a/docs/deploy-testnets.rst +++ b/docs/deploy-testnets.rst @@ -24,13 +24,13 @@ Here are the steps to setting up a testnet manually: ``tendermint gen_validator`` 4) Compile a list of public keys for each validator into a ``genesis.json`` file. -5) Run ``tendermint node --p2p.seeds=< seed addresses >`` on each node, - where ``< seed addresses >`` is a comma separated list of the IP:PORT +5) Run ``tendermint node --p2p.manual_peers=< peer addresses >`` on each node, + where ``< peer addresses >`` is a comma separated list of the IP:PORT combination for each node. The default port for Tendermint is ``46656``. Thus, if the IP addresses of your nodes were ``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command would look like: - ``tendermint node --p2p.seeds=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. + ``tendermint node --p2p.manual_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. After a few seconds, all the nodes should connect to eachother and start making blocks! For more information, see the Tendermint Networks section diff --git a/docs/specification/configuration.rst b/docs/specification/configuration.rst index 74b41d09d..3271cd59d 100644 --- a/docs/specification/configuration.rst +++ b/docs/specification/configuration.rst @@ -49,6 +49,8 @@ The main config parameters are defined - ``p2p.pex``: Enable Peer-Exchange (dev feature). *Default*: ``false`` - ``p2p.seeds``: Comma delimited host:port seed nodes. *Default*: ``""`` +- ``p2p.manual_peers``: Comma delimited host:port manual peers. *Default*: + ``""`` - ``p2p.skip_upnp``: Skip UPNP detection. *Default*: ``false`` - ``rpc.grpc_laddr``: GRPC listen address (BroadcastTx only). Port diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index 33173d196..f637c1717 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -111,7 +111,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_commit?tx=_ http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ - http://localhost:46657/dial_seeds?seeds=_ + http://localhost:46657/dial_manual_peers?manual_peers=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ http://localhost:46657/unsafe_start_cpu_profiler?filename=_ diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 9076230ea..2ec5624ec 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -270,14 +270,14 @@ For instance, :: - tendermint node --p2p.seeds "1.2.3.4:46656,5.6.7.8:46656" + tendermint node --p2p.manual_peers "1.2.3.4:46656,5.6.7.8:46656" -Alternatively, you can use the ``/dial_seeds`` endpoint of the RPC to +Alternatively, you can use the ``/dial_manual_peers`` endpoint of the RPC to specify peers for a running node to connect to: :: - curl --data-urlencode "seeds=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_seeds + curl --data-urlencode "manual_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_manual_peers Additionally, the peer-exchange protocol can be enabled using the ``--pex`` flag, though this feature is `still under @@ -290,7 +290,7 @@ Adding a Non-Validator Adding a non-validator is simple. Just copy the original ``genesis.json`` to ``~/.tendermint`` on the new machine and start the -node, specifying seeds as necessary. If no seeds are specified, the node +node, specifying manual_peers as necessary. If no manual_peers are specified, the node won't make any blocks, because it's not a validator, and it won't hear about any blocks, because it's not connected to the other peer. @@ -363,7 +363,7 @@ and the new ``priv_validator.json`` to the ``~/.tendermint`` on a new machine. Now run ``tendermint node`` on both machines, and use either -``--p2p.seeds`` or the ``/dial_seeds`` to get them to peer up. They +``--p2p.manual_peers`` or the ``/dial_manual_peers`` to get them to peer up. They should start making blocks, and will only continue to do so as long as both of them are online. diff --git a/node/node.go b/node/node.go index f8164955d..1970bb88e 100644 --- a/node/node.go +++ b/node/node.go @@ -380,40 +380,44 @@ func (n *Node) OnStart() error { return err } - err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() - if err != nil { - return err + // Always connect to manual peers + if n.config.P2P.ManualPeers != "" { + err = n.sw.DialPeersAsync(n.addrBook, strings.Split(n.config.P2P.ManualPeers, ","), true) + if err != nil { + return err + } + } + + if n.config.P2P.Seeds != "" { + err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(strings.Split(n.config.P2P.Seeds, ",")) + if err != nil { + return err + } } // start tx indexer return n.indexerService.Start() } -func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect() error { - if n.config.P2P.Seeds == "" { - return nil - } - - seeds := strings.Split(n.config.P2P.Seeds, ",") - +func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(seeds []string) error { // prefer peers from address book if n.config.P2P.PexReactor && n.addrBook.Size() > 0 { - // give some time to PexReactor to connect us to other peers - const fallbackToSeedsAfterSec = 30 * time.Second + // give some time for PexReactor to connect us to other peers + const fallbackToSeedsAfter = 30 * time.Second go func() { - time.Sleep(fallbackToSeedsAfterSec) + time.Sleep(fallbackToSeedsAfter) // fallback to dialing seeds if for some reason we can't connect to any // peers outbound, inbound, _ := n.sw.NumPeers() if n.IsRunning() && outbound+inbound == 0 { - n.DialSeeds(seeds) + // TODO: ignore error? + n.sw.DialPeersAsync(n.addrBook, seeds, false) } }() return nil } - // add seeds to the address book and dial out - return n.DialSeeds(seeds) + return n.sw.DialPeersAsync(n.addrBook, seeds, false) } // OnStop stops the Node. It implements cmn.Service. @@ -599,11 +603,6 @@ func (n *Node) NodeInfo() *p2p.NodeInfo { return n.sw.NodeInfo() } -// DialSeeds dials the given seeds on the Switch. -func (n *Node) DialSeeds(seeds []string) error { - return n.sw.DialSeeds(n.addrBook, seeds) -} - //------------------------------------------------------------------------------ var ( diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 2bfe7dcab..d4a8cbf18 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -100,7 +100,7 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { // For outbound peers, the address is already in the books. - // Either it was added in DialSeeds or when we + // Either it was added in DialManualPeers or when we // received the peer's address in r.Receive if r.book.NeedMoreAddrs() { r.RequestPEX(p) diff --git a/p2p/switch.go b/p2p/switch.go index 76b019806..4deee63d4 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -16,7 +16,7 @@ import ( const ( // wait a random amount of time from this interval - // before dialing seeds or reconnecting to help prevent DoS + // before dialing peers or reconnecting to help prevent DoS dialRandomizerIntervalMilliseconds = 3000 // repeatedly try to reconnect for a few minutes @@ -315,15 +315,15 @@ func (sw *Switch) startInitPeer(peer *peer) { } } -// DialSeeds dials a list of seeds asynchronously in random order. -func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { - netAddrs, errs := NewNetAddressStrings(seeds) +// DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). +func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent bool) error { + netAddrs, errs := NewNetAddressStrings(peers) for _, err := range errs { - sw.Logger.Error("Error in seed's address", "err", err) + sw.Logger.Error("Error in peer's address", "err", err) } if addrBook != nil { - // add seeds to `addrBook` + // add manual peers to `addrBook` ourAddrS := sw.nodeInfo.ListenAddr ourAddr, _ := NewNetAddressString(ourAddrS) for _, netAddr := range netAddrs { @@ -342,7 +342,12 @@ func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error { go func(i int) { sw.randomSleep(0) j := perm[i] - sw.dialSeed(netAddrs[j]) + peer, err := sw.DialPeerWithAddress(netAddrs[j], persistent) + if err != nil { + sw.Logger.Error("Error dialing peer", "err", err) + } else { + sw.Logger.Info("Connected to peer", "peer", peer) + } }(i) } return nil @@ -354,15 +359,6 @@ func (sw *Switch) randomSleep(interval time.Duration) { time.Sleep(r + interval) } -func (sw *Switch) dialSeed(addr *NetAddress) { - peer, err := sw.DialPeerWithAddress(addr, true) - if err != nil { - sw.Logger.Error("Error dialing seed", "err", err) - } else { - sw.Logger.Info("Connected to seed", "peer", peer) - } -} - // DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 71f25ef23..d30f7543c 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -84,8 +84,8 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return core.DumpConsensusState() } -func (Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(seeds) +func (Local) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { + return core.UnsafeDialManualPeers(manual_peers) } func (Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index dc75e04cb..84f6aa2d7 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -107,8 +107,8 @@ func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo() } -func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(seeds) +func (c Client) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { + return core.UnsafeDialManualPeers(manual_peers) } func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/core/doc.go b/rpc/core/doc.go index a72cec020..1a59459dd 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -94,7 +94,7 @@ Endpoints that require arguments: /broadcast_tx_commit?tx=_ /broadcast_tx_sync?tx=_ /commit?height=_ -/dial_seeds?seeds=_ +/dial_manual_peers?manual_peers=_ /subscribe?event=_ /tx?hash=_&prove=_ /unsafe_start_cpu_profiler?filename=_ diff --git a/rpc/core/net.go b/rpc/core/net.go index b3f1c7ce5..845cda646 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -54,18 +54,18 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { +func UnsafeDialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { - if len(seeds) == 0 { - return &ctypes.ResultDialSeeds{}, fmt.Errorf("No seeds provided") + if len(manual_peers) == 0 { + return &ctypes.ResultDialManualPeers{}, fmt.Errorf("No manual peers provided") } - // starts go routines to dial each seed after random delays - logger.Info("DialSeeds", "addrBook", addrBook, "seeds", seeds) - err := p2pSwitch.DialSeeds(addrBook, seeds) + // starts go routines to dial each peer after random delays + logger.Info("DialManualPeers", "addrBook", addrBook, "manual_peers", manual_peers) + err := p2pSwitch.DialPeersAsync(addrBook, manual_peers, true) if err != nil { - return &ctypes.ResultDialSeeds{}, err + return &ctypes.ResultDialManualPeers{}, err } - return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil + return &ctypes.ResultDialManualPeers{"Dialing manual peers in progress. See /net_info for details"}, nil } // Get genesis file. diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 927d7ccad..6d67fd898 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -32,7 +32,7 @@ type P2P interface { NumPeers() (outbound, inbound, dialig int) NodeInfo() *p2p.NodeInfo IsListening() bool - DialSeeds(*p2p.AddrBook, []string) error + DialPeersAsync(*p2p.AddrBook, []string, bool) error } //---------------------------------------------- diff --git a/rpc/core/routes.go b/rpc/core/routes.go index fb5a1fd36..84d405ac6 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -38,7 +38,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API - Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") + Routes["dial_manual_peers"] = rpc.NewRPCFunc(UnsafeDialManualPeers, "manual_peers") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index dae7c0046..4d8e79469 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -82,7 +82,7 @@ type ResultNetInfo struct { Peers []Peer `json:"peers"` } -type ResultDialSeeds struct { +type ResultDialManualPeers struct { Log string `json:"log"` } diff --git a/test/p2p/README.md b/test/p2p/README.md index e2a577cfa..692b730b2 100644 --- a/test/p2p/README.md +++ b/test/p2p/README.md @@ -38,7 +38,7 @@ for i in $(seq 1 4); do --name local_testnet_$i \ --entrypoint tendermint \ -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$i/core \ - tendermint_tester node --p2p.seeds 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy + tendermint_tester node --p2p.manual_peers 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy done ``` diff --git a/test/p2p/fast_sync/test_peer.sh b/test/p2p/fast_sync/test_peer.sh index 8174be0e7..ab5d517d9 100644 --- a/test/p2p/fast_sync/test_peer.sh +++ b/test/p2p/fast_sync/test_peer.sh @@ -23,11 +23,11 @@ docker rm -vf local_testnet_$ID set -e # restart peer - should have an empty blockchain -SEEDS="$(test/p2p/ip.sh 1):46656" +MANUAL_PEERS="$(test/p2p/ip.sh 1):46656" for j in `seq 2 $N`; do - SEEDS="$SEEDS,$(test/p2p/ip.sh $j):46656" + MANUAL_PEERS="$MANUAL_PEERS,$(test/p2p/ip.sh $j):46656" done -bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $ID $PROXY_APP "--p2p.seeds $SEEDS --p2p.pex --rpc.unsafe" +bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $ID $PROXY_APP "--p2p.manual_peers $MANUAL_PEERS --p2p.pex --rpc.unsafe" # wait for peer to sync and check the app hash bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME fs_$ID "test/p2p/fast_sync/check_peer.sh $ID" diff --git a/test/p2p/local_testnet_start.sh b/test/p2p/local_testnet_start.sh index f70bdf04c..c808c613d 100644 --- a/test/p2p/local_testnet_start.sh +++ b/test/p2p/local_testnet_start.sh @@ -7,10 +7,10 @@ N=$3 APP_PROXY=$4 set +u -SEEDS=$5 -if [[ "$SEEDS" != "" ]]; then - echo "Seeds: $SEEDS" - SEEDS="--p2p.seeds $SEEDS" +MANUAL_PEERS=$5 +if [[ "$MANUAL_PEERS" != "" ]]; then + echo "ManualPeers: $MANUAL_PEERS" + MANUAL_PEERS="--p2p.manual_peers $MANUAL_PEERS" fi set -u @@ -20,5 +20,5 @@ cd "$GOPATH/src/github.com/tendermint/tendermint" docker network create --driver bridge --subnet 172.57.0.0/16 "$NETWORK_NAME" for i in $(seq 1 "$N"); do - bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$SEEDS --p2p.pex --rpc.unsafe" + bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$MANUAL_PEERS --p2p.pex --rpc.unsafe" done diff --git a/test/p2p/manual_peers.sh b/test/p2p/manual_peers.sh new file mode 100644 index 000000000..90051b15b --- /dev/null +++ b/test/p2p/manual_peers.sh @@ -0,0 +1,12 @@ +#! /bin/bash +set -eu + +N=$1 + +cd "$GOPATH/src/github.com/tendermint/tendermint" + +manual_peers="$(test/p2p/ip.sh 1):46656" +for i in $(seq 2 $N); do + manual_peers="$manual_peers,$(test/p2p/ip.sh $i):46656" +done +echo "$manual_peers" diff --git a/test/p2p/pex/dial_seeds.sh b/test/p2p/pex/dial_manual_peers.sh similarity index 60% rename from test/p2p/pex/dial_seeds.sh rename to test/p2p/pex/dial_manual_peers.sh index 15c22af67..4ee79b869 100644 --- a/test/p2p/pex/dial_seeds.sh +++ b/test/p2p/pex/dial_manual_peers.sh @@ -19,13 +19,13 @@ for i in `seq 1 $N`; do done set -e -# seeds need quotes -seeds="\"$(test/p2p/ip.sh 1):46656\"" +# manual_peers need quotes +manual_peers="\"$(test/p2p/ip.sh 1):46656\"" for i in `seq 2 $N`; do - seeds="$seeds,\"$(test/p2p/ip.sh $i):46656\"" + manual_peers="$manual_peers,\"$(test/p2p/ip.sh $i):46656\"" done -echo $seeds +echo $manual_peers -echo $seeds +echo $manual_peers IP=$(test/p2p/ip.sh 1) -curl --data-urlencode "seeds=[$seeds]" "$IP:46657/dial_seeds" +curl --data-urlencode "manual_peers=[$manual_peers]" "$IP:46657/dial_manual_peers" diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index d54d81350..2c42781dd 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -11,5 +11,5 @@ cd $GOPATH/src/github.com/tendermint/tendermint echo "Test reconnecting from the address book" bash test/p2p/pex/test_addrbook.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP -echo "Test connecting via /dial_seeds" -bash test/p2p/pex/test_dial_seeds.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +echo "Test connecting via /dial_manual_peers" +bash test/p2p/pex/test_dial_manual_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP diff --git a/test/p2p/pex/test_addrbook.sh b/test/p2p/pex/test_addrbook.sh index 35dcb89d3..b215606d6 100644 --- a/test/p2p/pex/test_addrbook.sh +++ b/test/p2p/pex/test_addrbook.sh @@ -9,7 +9,7 @@ PROXY_APP=$4 ID=1 echo "----------------------------------------------------------------------" -echo "Testing pex creates the addrbook and uses it if seeds are not provided" +echo "Testing pex creates the addrbook and uses it if manual_peers are not provided" echo "(assuming peers are started with pex enabled)" CLIENT_NAME="pex_addrbook_$ID" @@ -22,7 +22,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide seeds +# NOTE that we do not provide manual_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/addrbook.json" echo "with the following addrbook:" @@ -35,7 +35,7 @@ echo "" bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $ID $N" echo "----------------------------------------------------------------------" -echo "Testing other peers connect to us if we have neither seeds nor the addrbook" +echo "Testing other peers connect to us if we have neither manual_peers nor the addrbook" echo "(assuming peers are started with pex enabled)" CLIENT_NAME="pex_no_addrbook_$ID" @@ -46,7 +46,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide seeds +# NOTE that we do not provide manual_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" # if the client runs forever, it means other peers have removed us from their books (which should not happen) diff --git a/test/p2p/pex/test_dial_seeds.sh b/test/p2p/pex/test_dial_manual_peers.sh similarity index 75% rename from test/p2p/pex/test_dial_seeds.sh rename to test/p2p/pex/test_dial_manual_peers.sh index ea72004db..ba3e1c4d0 100644 --- a/test/p2p/pex/test_dial_seeds.sh +++ b/test/p2p/pex/test_dial_manual_peers.sh @@ -11,7 +11,7 @@ ID=1 cd $GOPATH/src/github.com/tendermint/tendermint echo "----------------------------------------------------------------------" -echo "Testing full network connection using one /dial_seeds call" +echo "Testing full network connection using one /dial_manual_peers call" echo "(assuming peers are started with pex enabled)" # stop the existing testnet and remove local network @@ -21,16 +21,16 @@ set -e # start the testnet on a local network # NOTE we re-use the same network for all tests -SEEDS="" -bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $SEEDS +MANUAL_PEERS="" +bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $MANUAL_PEERS -# dial seeds from one node -CLIENT_NAME="dial_seeds" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_seeds.sh $N" +# dial manual_peers from one node +CLIENT_NAME="dial_manual_peers" +bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_manual_peers.sh $N" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes -CLIENT_NAME="dial_seeds_basic" +CLIENT_NAME="dial_manual_peers_basic" bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/basic/test.sh $N" diff --git a/test/p2p/seeds.sh b/test/p2p/seeds.sh deleted file mode 100644 index 4bf866cba..000000000 --- a/test/p2p/seeds.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash -set -eu - -N=$1 - -cd "$GOPATH/src/github.com/tendermint/tendermint" - -seeds="$(test/p2p/ip.sh 1):46656" -for i in $(seq 2 $N); do - seeds="$seeds,$(test/p2p/ip.sh $i):46656" -done -echo "$seeds" diff --git a/test/p2p/test.sh b/test/p2p/test.sh index 6a5537b98..f348efe4a 100644 --- a/test/p2p/test.sh +++ b/test/p2p/test.sh @@ -13,11 +13,11 @@ set +e bash test/p2p/local_testnet_stop.sh "$NETWORK_NAME" "$N" set -e -SEEDS=$(bash test/p2p/seeds.sh $N) +MANUAL_PEERS=$(bash test/p2p/manual_peers.sh $N) # start the testnet on a local network # NOTE we re-use the same network for all tests -bash test/p2p/local_testnet_start.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" "$SEEDS" +bash test/p2p/local_testnet_start.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" "$MANUAL_PEERS" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes From 37f86f9518cc80b1d2f759ce09f381ea96730e54 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 28 Dec 2017 18:27:36 -0600 Subject: [PATCH 10/32] update changelog [ci skip] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cda150717..1ceddd2ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,13 @@ BUG FIXES: - Graceful handling/recovery for apps that have non-determinism or fail to halt - Graceful handling/recovery for violations of safety, or liveness +## 0.16.0 (TBD) + +BREAKING CHANGES: +- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_manual_peers` +- [p2p] old `seeds` is now `manual_peers` (persistent peers to which TM will always connect to) +- [p2p] now `seeds` only used for getting addresses (if addrbook is empty; not persistent) + ## 0.15.0 (December 29, 2017) BREAKING CHANGES: From e4897b7bdd033067d7d0fbbbd25c3f917d35d25a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 16:18:05 -0600 Subject: [PATCH 11/32] rename manual peers to persistent peers --- CHANGELOG.md | 4 ++-- benchmarks/blockchain/localsync.sh | 2 +- cmd/tendermint/commands/run_node.go | 2 +- config/config.go | 6 +++--- config/toml.go | 4 ++-- docs/deploy-testnets.rst | 4 ++-- docs/specification/configuration.rst | 2 +- docs/specification/rpc.rst | 2 +- docs/using-tendermint.rst | 10 +++++----- node/node.go | 6 +++--- p2p/pex_reactor.go | 2 +- p2p/switch.go | 2 +- rpc/client/localclient.go | 4 ++-- rpc/client/mock/client.go | 4 ++-- rpc/core/doc.go | 2 +- rpc/core/net.go | 14 +++++++------- rpc/core/routes.go | 2 +- rpc/core/types/responses.go | 2 +- test/p2p/README.md | 2 +- test/p2p/fast_sync/test_peer.sh | 6 +++--- test/p2p/local_testnet_start.sh | 10 +++++----- test/p2p/manual_peers.sh | 6 +++--- test/p2p/pex/dial_manual_peers.sh | 16 ++++++++-------- test/p2p/pex/test.sh | 4 ++-- test/p2p/pex/test_addrbook.sh | 8 ++++---- test/p2p/pex/test_dial_manual_peers.sh | 14 +++++++------- test/p2p/test.sh | 4 ++-- 27 files changed, 72 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ceddd2ef..b935a3816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,8 @@ BUG FIXES: ## 0.16.0 (TBD) BREAKING CHANGES: -- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_manual_peers` -- [p2p] old `seeds` is now `manual_peers` (persistent peers to which TM will always connect to) +- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_persistent_peers` +- [p2p] old `seeds` is now `persistent_peers` (persistent peers to which TM will always connect to) - [p2p] now `seeds` only used for getting addresses (if addrbook is empty; not persistent) ## 0.15.0 (December 29, 2017) diff --git a/benchmarks/blockchain/localsync.sh b/benchmarks/blockchain/localsync.sh index 18afaee82..389464b6c 100755 --- a/benchmarks/blockchain/localsync.sh +++ b/benchmarks/blockchain/localsync.sh @@ -51,7 +51,7 @@ tendermint node \ --proxy_app dummy \ --p2p.laddr tcp://127.0.0.1:56666 \ --rpc.laddr tcp://127.0.0.1:56667 \ - --p2p.manual_peers 127.0.0.1:56656 \ + --p2p.persistent_peers 127.0.0.1:56656 \ --log_level error & # wait for node to start up so we only count time where we are actually syncing diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 5b87a4b82..0eb7a4259 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -29,7 +29,7 @@ func AddNodeFlags(cmd *cobra.Command) { // p2p flags cmd.Flags().String("p2p.laddr", config.P2P.ListenAddress, "Node listen address. (0.0.0.0:0 means any interface, any port)") cmd.Flags().String("p2p.seeds", config.P2P.Seeds, "Comma delimited host:port seed nodes") - cmd.Flags().String("p2p.manual_peers", config.P2P.ManualPeers, "Comma delimited host:port manual peers") + cmd.Flags().String("p2p.persistent_peers", config.P2P.PersistentPeers, "Comma delimited host:port persistent peers") cmd.Flags().Bool("p2p.skip_upnp", config.P2P.SkipUPNP, "Skip UPNP configuration") cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable/disable Peer-Exchange") diff --git a/config/config.go b/config/config.go index 2d38ed916..04a37c68c 100644 --- a/config/config.go +++ b/config/config.go @@ -170,7 +170,7 @@ type RPCConfig struct { // NOTE: This server only supports /broadcast_tx_commit GRPCListenAddress string `mapstructure:"grpc_laddr"` - // Activate unsafe RPC commands like /dial_manual_peers and /unsafe_flush_mempool + // Activate unsafe RPC commands like /dial_persistent_peers and /unsafe_flush_mempool Unsafe bool `mapstructure:"unsafe"` } @@ -206,9 +206,9 @@ type P2PConfig struct { // We only use these if we can’t connect to peers in the addrbook Seeds string `mapstructure:"seeds"` - // Comma separated list of manual peers to connect to + // Comma separated list of persistent peers to connect to // We always connect to these - ManualPeers string `mapstructure:"manual_peers"` + PersistentPeers string `mapstructure:"persistent_peers"` // Skip UPNP port forwarding SkipUPNP bool `mapstructure:"skip_upnp"` diff --git a/config/toml.go b/config/toml.go index 59fc46dcf..e644445f0 100644 --- a/config/toml.go +++ b/config/toml.go @@ -42,7 +42,7 @@ laddr = "tcp://0.0.0.0:46657" [p2p] laddr = "tcp://0.0.0.0:46656" seeds = "" -manual_peers = "" +persistent_peers = "" ` func defaultConfig(moniker string) string { @@ -107,7 +107,7 @@ laddr = "tcp://0.0.0.0:36657" [p2p] laddr = "tcp://0.0.0.0:36656" seeds = "" -manual_peers = "" +persistent_peers = "" ` func testConfig(moniker string) (testConfig string) { diff --git a/docs/deploy-testnets.rst b/docs/deploy-testnets.rst index 8c66c4b34..33c214570 100644 --- a/docs/deploy-testnets.rst +++ b/docs/deploy-testnets.rst @@ -24,13 +24,13 @@ Here are the steps to setting up a testnet manually: ``tendermint gen_validator`` 4) Compile a list of public keys for each validator into a ``genesis.json`` file. -5) Run ``tendermint node --p2p.manual_peers=< peer addresses >`` on each node, +5) Run ``tendermint node --p2p.persistent_peers=< peer addresses >`` on each node, where ``< peer addresses >`` is a comma separated list of the IP:PORT combination for each node. The default port for Tendermint is ``46656``. Thus, if the IP addresses of your nodes were ``192.168.0.1, 192.168.0.2, 192.168.0.3, 192.168.0.4``, the command would look like: - ``tendermint node --p2p.manual_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. + ``tendermint node --p2p.persistent_peers=192.168.0.1:46656,192.168.0.2:46656,192.168.0.3:46656,192.168.0.4:46656``. After a few seconds, all the nodes should connect to eachother and start making blocks! For more information, see the Tendermint Networks section diff --git a/docs/specification/configuration.rst b/docs/specification/configuration.rst index 3271cd59d..46e4059e3 100644 --- a/docs/specification/configuration.rst +++ b/docs/specification/configuration.rst @@ -49,7 +49,7 @@ The main config parameters are defined - ``p2p.pex``: Enable Peer-Exchange (dev feature). *Default*: ``false`` - ``p2p.seeds``: Comma delimited host:port seed nodes. *Default*: ``""`` -- ``p2p.manual_peers``: Comma delimited host:port manual peers. *Default*: +- ``p2p.persistent_peers``: Comma delimited host:port persistent peers. *Default*: ``""`` - ``p2p.skip_upnp``: Skip UPNP detection. *Default*: ``false`` diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index f637c1717..daafce111 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -111,7 +111,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_commit?tx=_ http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ - http://localhost:46657/dial_manual_peers?manual_peers=_ + http://localhost:46657/dial_persistent_peers?persistent_peers=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ http://localhost:46657/unsafe_start_cpu_profiler?filename=_ diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 2ec5624ec..2a04ac543 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -270,14 +270,14 @@ For instance, :: - tendermint node --p2p.manual_peers "1.2.3.4:46656,5.6.7.8:46656" + tendermint node --p2p.persistent_peers "1.2.3.4:46656,5.6.7.8:46656" -Alternatively, you can use the ``/dial_manual_peers`` endpoint of the RPC to +Alternatively, you can use the ``/dial_persistent_peers`` endpoint of the RPC to specify peers for a running node to connect to: :: - curl --data-urlencode "manual_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_manual_peers + curl --data-urlencode "persistent_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_persistent_peers Additionally, the peer-exchange protocol can be enabled using the ``--pex`` flag, though this feature is `still under @@ -290,7 +290,7 @@ Adding a Non-Validator Adding a non-validator is simple. Just copy the original ``genesis.json`` to ``~/.tendermint`` on the new machine and start the -node, specifying manual_peers as necessary. If no manual_peers are specified, the node +node, specifying persistent_peers as necessary. If no persistent_peers are specified, the node won't make any blocks, because it's not a validator, and it won't hear about any blocks, because it's not connected to the other peer. @@ -363,7 +363,7 @@ and the new ``priv_validator.json`` to the ``~/.tendermint`` on a new machine. Now run ``tendermint node`` on both machines, and use either -``--p2p.manual_peers`` or the ``/dial_manual_peers`` to get them to peer up. They +``--p2p.persistent_peers`` or the ``/dial_persistent_peers`` to get them to peer up. They should start making blocks, and will only continue to do so as long as both of them are online. diff --git a/node/node.go b/node/node.go index 1970bb88e..a4ea193b8 100644 --- a/node/node.go +++ b/node/node.go @@ -380,9 +380,9 @@ func (n *Node) OnStart() error { return err } - // Always connect to manual peers - if n.config.P2P.ManualPeers != "" { - err = n.sw.DialPeersAsync(n.addrBook, strings.Split(n.config.P2P.ManualPeers, ","), true) + // Always connect to persistent peers + if n.config.P2P.PersistentPeers != "" { + err = n.sw.DialPeersAsync(n.addrBook, strings.Split(n.config.P2P.PersistentPeers, ","), true) if err != nil { return err } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index d4a8cbf18..64e7dafb9 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -100,7 +100,7 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { // For outbound peers, the address is already in the books. - // Either it was added in DialManualPeers or when we + // Either it was added in DialPersistentPeers or when we // received the peer's address in r.Receive if r.book.NeedMoreAddrs() { r.RequestPEX(p) diff --git a/p2p/switch.go b/p2p/switch.go index 4deee63d4..1b449d7e0 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -323,7 +323,7 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent } if addrBook != nil { - // add manual peers to `addrBook` + // add persistent peers to `addrBook` ourAddrS := sw.nodeInfo.ListenAddr ourAddr, _ := NewNetAddressString(ourAddrS) for _, netAddr := range netAddrs { diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index d30f7543c..af91ac791 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -84,8 +84,8 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return core.DumpConsensusState() } -func (Local) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { - return core.UnsafeDialManualPeers(manual_peers) +func (Local) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { + return core.UnsafeDialPersistentPeers(persistent_peers) } func (Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 84f6aa2d7..469e80a67 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -107,8 +107,8 @@ func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo() } -func (c Client) DialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { - return core.UnsafeDialManualPeers(manual_peers) +func (c Client) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { + return core.UnsafeDialPersistentPeers(persistent_peers) } func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/core/doc.go b/rpc/core/doc.go index 1a59459dd..030e5d617 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -94,7 +94,7 @@ Endpoints that require arguments: /broadcast_tx_commit?tx=_ /broadcast_tx_sync?tx=_ /commit?height=_ -/dial_manual_peers?manual_peers=_ +/dial_persistent_peers?persistent_peers=_ /subscribe?event=_ /tx?hash=_&prove=_ /unsafe_start_cpu_profiler?filename=_ diff --git a/rpc/core/net.go b/rpc/core/net.go index 845cda646..c79c55deb 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -54,18 +54,18 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialManualPeers(manual_peers []string) (*ctypes.ResultDialManualPeers, error) { +func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - if len(manual_peers) == 0 { - return &ctypes.ResultDialManualPeers{}, fmt.Errorf("No manual peers provided") + if len(persistent_peers) == 0 { + return &ctypes.ResultDialPersistentPeers{}, fmt.Errorf("No persistent peers provided") } // starts go routines to dial each peer after random delays - logger.Info("DialManualPeers", "addrBook", addrBook, "manual_peers", manual_peers) - err := p2pSwitch.DialPeersAsync(addrBook, manual_peers, true) + logger.Info("DialPersistentPeers", "addrBook", addrBook, "persistent_peers", persistent_peers) + err := p2pSwitch.DialPeersAsync(addrBook, persistent_peers, true) if err != nil { - return &ctypes.ResultDialManualPeers{}, err + return &ctypes.ResultDialPersistentPeers{}, err } - return &ctypes.ResultDialManualPeers{"Dialing manual peers in progress. See /net_info for details"}, nil + return &ctypes.ResultDialPersistentPeers{"Dialing persistent peers in progress. See /net_info for details"}, nil } // Get genesis file. diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 84d405ac6..0bf7af623 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -38,7 +38,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API - Routes["dial_manual_peers"] = rpc.NewRPCFunc(UnsafeDialManualPeers, "manual_peers") + Routes["dial_persistent_peers"] = rpc.NewRPCFunc(UnsafeDialPersistentPeers, "persistent_peers") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 4d8e79469..59c2aeea9 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -82,7 +82,7 @@ type ResultNetInfo struct { Peers []Peer `json:"peers"` } -type ResultDialManualPeers struct { +type ResultDialPersistentPeers struct { Log string `json:"log"` } diff --git a/test/p2p/README.md b/test/p2p/README.md index 692b730b2..b68f7a819 100644 --- a/test/p2p/README.md +++ b/test/p2p/README.md @@ -38,7 +38,7 @@ for i in $(seq 1 4); do --name local_testnet_$i \ --entrypoint tendermint \ -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$i/core \ - tendermint_tester node --p2p.manual_peers 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy + tendermint_tester node --p2p.persistent_peers 172.57.0.101:46656,172.57.0.102:46656,172.57.0.103:46656,172.57.0.104:46656 --proxy_app=dummy done ``` diff --git a/test/p2p/fast_sync/test_peer.sh b/test/p2p/fast_sync/test_peer.sh index ab5d517d9..1f341bf5d 100644 --- a/test/p2p/fast_sync/test_peer.sh +++ b/test/p2p/fast_sync/test_peer.sh @@ -23,11 +23,11 @@ docker rm -vf local_testnet_$ID set -e # restart peer - should have an empty blockchain -MANUAL_PEERS="$(test/p2p/ip.sh 1):46656" +PERSISTENT_PEERS="$(test/p2p/ip.sh 1):46656" for j in `seq 2 $N`; do - MANUAL_PEERS="$MANUAL_PEERS,$(test/p2p/ip.sh $j):46656" + PERSISTENT_PEERS="$PERSISTENT_PEERS,$(test/p2p/ip.sh $j):46656" done -bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $ID $PROXY_APP "--p2p.manual_peers $MANUAL_PEERS --p2p.pex --rpc.unsafe" +bash test/p2p/peer.sh $DOCKER_IMAGE $NETWORK_NAME $ID $PROXY_APP "--p2p.persistent_peers $PERSISTENT_PEERS --p2p.pex --rpc.unsafe" # wait for peer to sync and check the app hash bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME fs_$ID "test/p2p/fast_sync/check_peer.sh $ID" diff --git a/test/p2p/local_testnet_start.sh b/test/p2p/local_testnet_start.sh index c808c613d..25b3c6d3e 100644 --- a/test/p2p/local_testnet_start.sh +++ b/test/p2p/local_testnet_start.sh @@ -7,10 +7,10 @@ N=$3 APP_PROXY=$4 set +u -MANUAL_PEERS=$5 -if [[ "$MANUAL_PEERS" != "" ]]; then - echo "ManualPeers: $MANUAL_PEERS" - MANUAL_PEERS="--p2p.manual_peers $MANUAL_PEERS" +PERSISTENT_PEERS=$5 +if [[ "$PERSISTENT_PEERS" != "" ]]; then + echo "PersistentPeers: $PERSISTENT_PEERS" + PERSISTENT_PEERS="--p2p.persistent_peers $PERSISTENT_PEERS" fi set -u @@ -20,5 +20,5 @@ cd "$GOPATH/src/github.com/tendermint/tendermint" docker network create --driver bridge --subnet 172.57.0.0/16 "$NETWORK_NAME" for i in $(seq 1 "$N"); do - bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$MANUAL_PEERS --p2p.pex --rpc.unsafe" + bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$i" "$APP_PROXY" "$PERSISTENT_PEERS --p2p.pex --rpc.unsafe" done diff --git a/test/p2p/manual_peers.sh b/test/p2p/manual_peers.sh index 90051b15b..4ad55bc03 100644 --- a/test/p2p/manual_peers.sh +++ b/test/p2p/manual_peers.sh @@ -5,8 +5,8 @@ N=$1 cd "$GOPATH/src/github.com/tendermint/tendermint" -manual_peers="$(test/p2p/ip.sh 1):46656" +persistent_peers="$(test/p2p/ip.sh 1):46656" for i in $(seq 2 $N); do - manual_peers="$manual_peers,$(test/p2p/ip.sh $i):46656" + persistent_peers="$persistent_peers,$(test/p2p/ip.sh $i):46656" done -echo "$manual_peers" +echo "$persistent_peers" diff --git a/test/p2p/pex/dial_manual_peers.sh b/test/p2p/pex/dial_manual_peers.sh index 4ee79b869..95c1d6e9c 100644 --- a/test/p2p/pex/dial_manual_peers.sh +++ b/test/p2p/pex/dial_manual_peers.sh @@ -1,4 +1,4 @@ -#! /bin/bash +#! /bin/bash set -u N=$1 @@ -11,7 +11,7 @@ for i in `seq 1 $N`; do curl -s $addr/status > /dev/null ERR=$? while [ "$ERR" != 0 ]; do - sleep 1 + sleep 1 curl -s $addr/status > /dev/null ERR=$? done @@ -19,13 +19,13 @@ for i in `seq 1 $N`; do done set -e -# manual_peers need quotes -manual_peers="\"$(test/p2p/ip.sh 1):46656\"" +# persistent_peers need quotes +persistent_peers="\"$(test/p2p/ip.sh 1):46656\"" for i in `seq 2 $N`; do - manual_peers="$manual_peers,\"$(test/p2p/ip.sh $i):46656\"" + persistent_peers="$persistent_peers,\"$(test/p2p/ip.sh $i):46656\"" done -echo $manual_peers +echo $persistent_peers -echo $manual_peers +echo $persistent_peers IP=$(test/p2p/ip.sh 1) -curl --data-urlencode "manual_peers=[$manual_peers]" "$IP:46657/dial_manual_peers" +curl --data-urlencode "persistent_peers=[$persistent_peers]" "$IP:46657/dial_persistent_peers" diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index 2c42781dd..7cf6151d5 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -11,5 +11,5 @@ cd $GOPATH/src/github.com/tendermint/tendermint echo "Test reconnecting from the address book" bash test/p2p/pex/test_addrbook.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP -echo "Test connecting via /dial_manual_peers" -bash test/p2p/pex/test_dial_manual_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +echo "Test connecting via /dial_persistent_peers" +bash test/p2p/pex/test_dial_persistent_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP diff --git a/test/p2p/pex/test_addrbook.sh b/test/p2p/pex/test_addrbook.sh index b215606d6..1dd26b172 100644 --- a/test/p2p/pex/test_addrbook.sh +++ b/test/p2p/pex/test_addrbook.sh @@ -9,7 +9,7 @@ PROXY_APP=$4 ID=1 echo "----------------------------------------------------------------------" -echo "Testing pex creates the addrbook and uses it if manual_peers are not provided" +echo "Testing pex creates the addrbook and uses it if persistent_peers are not provided" echo "(assuming peers are started with pex enabled)" CLIENT_NAME="pex_addrbook_$ID" @@ -22,7 +22,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide manual_peers +# NOTE that we do not provide persistent_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/addrbook.json" echo "with the following addrbook:" @@ -35,7 +35,7 @@ echo "" bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $ID $N" echo "----------------------------------------------------------------------" -echo "Testing other peers connect to us if we have neither manual_peers nor the addrbook" +echo "Testing other peers connect to us if we have neither persistent_peers nor the addrbook" echo "(assuming peers are started with pex enabled)" CLIENT_NAME="pex_no_addrbook_$ID" @@ -46,7 +46,7 @@ set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e -# NOTE that we do not provide manual_peers +# NOTE that we do not provide persistent_peers bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe" # if the client runs forever, it means other peers have removed us from their books (which should not happen) diff --git a/test/p2p/pex/test_dial_manual_peers.sh b/test/p2p/pex/test_dial_manual_peers.sh index ba3e1c4d0..7dda62b13 100644 --- a/test/p2p/pex/test_dial_manual_peers.sh +++ b/test/p2p/pex/test_dial_manual_peers.sh @@ -11,7 +11,7 @@ ID=1 cd $GOPATH/src/github.com/tendermint/tendermint echo "----------------------------------------------------------------------" -echo "Testing full network connection using one /dial_manual_peers call" +echo "Testing full network connection using one /dial_persistent_peers call" echo "(assuming peers are started with pex enabled)" # stop the existing testnet and remove local network @@ -21,16 +21,16 @@ set -e # start the testnet on a local network # NOTE we re-use the same network for all tests -MANUAL_PEERS="" -bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $MANUAL_PEERS +PERSISTENT_PEERS="" +bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $PERSISTENT_PEERS -# dial manual_peers from one node -CLIENT_NAME="dial_manual_peers" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_manual_peers.sh $N" +# dial persistent_peers from one node +CLIENT_NAME="dial_persistent_peers" +bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_persistent_peers.sh $N" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes -CLIENT_NAME="dial_manual_peers_basic" +CLIENT_NAME="dial_persistent_peers_basic" bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/basic/test.sh $N" diff --git a/test/p2p/test.sh b/test/p2p/test.sh index f348efe4a..c95f69733 100644 --- a/test/p2p/test.sh +++ b/test/p2p/test.sh @@ -13,11 +13,11 @@ set +e bash test/p2p/local_testnet_stop.sh "$NETWORK_NAME" "$N" set -e -MANUAL_PEERS=$(bash test/p2p/manual_peers.sh $N) +PERSISTENT_PEERS=$(bash test/p2p/persistent_peers.sh $N) # start the testnet on a local network # NOTE we re-use the same network for all tests -bash test/p2p/local_testnet_start.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" "$MANUAL_PEERS" +bash test/p2p/local_testnet_start.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" "$PERSISTENT_PEERS" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes From 1b455883d2a5acfd0705c2dddf5c31eff2b7a01b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 16:36:15 -0600 Subject: [PATCH 12/32] readd /dial_seeds --- CHANGELOG.md | 4 +++- docs/specification/rpc.rst | 1 + rpc/client/localclient.go | 4 ++++ rpc/client/mock/client.go | 4 ++++ rpc/core/doc.go | 1 + rpc/core/net.go | 19 +++++++++++++++---- rpc/core/routes.go | 1 + rpc/core/types/responses.go | 4 ++++ ...nual_peers.sh => dial_persistent_peers.sh} | 0 test/p2p/pex/test.sh | 6 +++--- ...peers.sh => test_dial_persistent_peers.sh} | 0 11 files changed, 36 insertions(+), 8 deletions(-) rename test/p2p/pex/{dial_manual_peers.sh => dial_persistent_peers.sh} (100%) rename test/p2p/pex/{test_dial_manual_peers.sh => test_dial_persistent_peers.sh} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b935a3816..fdf3aea50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,10 +28,12 @@ BUG FIXES: ## 0.16.0 (TBD) BREAKING CHANGES: -- rpc: `/unsafe_dial_seeds` renamed to `/unsafe_dial_persistent_peers` - [p2p] old `seeds` is now `persistent_peers` (persistent peers to which TM will always connect to) - [p2p] now `seeds` only used for getting addresses (if addrbook is empty; not persistent) +FEATURES: +- [p2p] added new `/dial_persistent_peers` **unsafe** endpoint + ## 0.15.0 (December 29, 2017) BREAKING CHANGES: diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index daafce111..e273ce84a 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -111,6 +111,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_commit?tx=_ http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ + http://localhost:46657/dial_seeds?seeds=_ http://localhost:46657/dial_persistent_peers?persistent_peers=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index af91ac791..cc23b9449 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -84,6 +84,10 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return core.DumpConsensusState() } +func (Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + return core.UnsafeDialSeeds(seeds) +} + func (Local) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { return core.UnsafeDialPersistentPeers(persistent_peers) } diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 469e80a67..913812d6d 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -107,6 +107,10 @@ func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo() } +func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + return core.UnsafeDialSeeds(seeds) +} + func (c Client) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { return core.UnsafeDialPersistentPeers(persistent_peers) } diff --git a/rpc/core/doc.go b/rpc/core/doc.go index 030e5d617..a801cd0d2 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -94,6 +94,7 @@ Endpoints that require arguments: /broadcast_tx_commit?tx=_ /broadcast_tx_sync?tx=_ /commit?height=_ +/dial_seeds?seeds=_ /dial_persistent_peers?persistent_peers=_ /subscribe?event=_ /tx?hash=_&prove=_ diff --git a/rpc/core/net.go b/rpc/core/net.go index c79c55deb..b528e1e35 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -1,8 +1,7 @@ package core import ( - "fmt" - + "github.com/pkg/errors" ctypes "github.com/tendermint/tendermint/rpc/core/types" ) @@ -54,10 +53,22 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { }, nil } -func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { +func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + if len(seeds) == 0 { + return &ctypes.ResultDialSeeds{}, errors.New("No seeds provided") + } + // starts go routines to dial each peer after random delays + logger.Info("DialSeeds", "addrBook", addrBook, "seeds", seeds) + err := p2pSwitch.DialPeersAsync(addrBook, seeds, false) + if err != nil { + return &ctypes.ResultDialSeeds{}, err + } + return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil +} +func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { if len(persistent_peers) == 0 { - return &ctypes.ResultDialPersistentPeers{}, fmt.Errorf("No persistent peers provided") + return &ctypes.ResultDialPersistentPeers{}, errors.New("No persistent peers provided") } // starts go routines to dial each peer after random delays logger.Info("DialPersistentPeers", "addrBook", addrBook, "persistent_peers", persistent_peers) diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 0bf7af623..d00165e6a 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -38,6 +38,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API + Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") Routes["dial_persistent_peers"] = rpc.NewRPCFunc(UnsafeDialPersistentPeers, "persistent_peers") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 59c2aeea9..c26defb76 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -82,6 +82,10 @@ type ResultNetInfo struct { Peers []Peer `json:"peers"` } +type ResultDialSeeds struct { + Log string `json:"log"` +} + type ResultDialPersistentPeers struct { Log string `json:"log"` } diff --git a/test/p2p/pex/dial_manual_peers.sh b/test/p2p/pex/dial_persistent_peers.sh similarity index 100% rename from test/p2p/pex/dial_manual_peers.sh rename to test/p2p/pex/dial_persistent_peers.sh diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index 7cf6151d5..06d40c3ed 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -6,10 +6,10 @@ NETWORK_NAME=$2 N=$3 PROXY_APP=$4 -cd $GOPATH/src/github.com/tendermint/tendermint +cd "$GOPATH/src/github.com/tendermint/tendermint" echo "Test reconnecting from the address book" -bash test/p2p/pex/test_addrbook.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +bash test/p2p/pex/test_addrbook.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" echo "Test connecting via /dial_persistent_peers" -bash test/p2p/pex/test_dial_persistent_peers.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP +bash test/p2p/pex/test_dial_persistent_peers.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" diff --git a/test/p2p/pex/test_dial_manual_peers.sh b/test/p2p/pex/test_dial_persistent_peers.sh similarity index 100% rename from test/p2p/pex/test_dial_manual_peers.sh rename to test/p2p/pex/test_dial_persistent_peers.sh From ef0493ddf3744249a47f20ab49f27fa974b3cf84 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 17:41:49 -0600 Subject: [PATCH 13/32] rewrite Peers section of Using Tendermint guide [ci skip] --- docs/using-tendermint.rst | 45 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 2a04ac543..20c5fa06d 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -129,7 +129,7 @@ No Empty Blocks This much requested feature was implemented in version 0.10.3. While the default behaviour of ``tendermint`` is still to create blocks approximately once per second, it is possible to disable empty blocks or set a block creation interval. In the former case, blocks will be created when there are new transactions or when the AppHash changes. -To configure tendermint to not produce empty blocks unless there are txs or the app hash changes, +To configure tendermint to not produce empty blocks unless there are txs or the app hash changes, run tendermint with this additional flag: :: @@ -263,36 +263,47 @@ with the consensus protocol. Peers ~~~~~ -To connect to peers on start-up, specify them in the ``config.toml`` or -on the command line. +If you are starting Tendermint core for the first time, it will need some peers. + +You can provide a list of seeds (nodes, whole purpose is providing you with +peers) in the ``config.toml`` or on the command line. For instance, :: - tendermint node --p2p.persistent_peers "1.2.3.4:46656,5.6.7.8:46656" + tendermint node --p2p.seeds "1.2.3.4:46656,5.6.7.8:46656" -Alternatively, you can use the ``/dial_persistent_peers`` endpoint of the RPC to -specify peers for a running node to connect to: +Alternatively, you can use the ``/dial_seeds`` endpoint of the RPC to +specify seeds for a running node to connect to: :: - curl --data-urlencode "persistent_peers=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_persistent_peers + curl --data-urlencode "seeds=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_seeds + +Note, if the peer-exchange protocol (PEX) is enabled (default), you should not +normally need seeds after the first start. Peers will be gossipping about known +peers and forming a network, storing peer addresses in the addrbook. + +If you want Tendermint to connect to specific set of addresses and maintain a +persistent connection with each, you can use the ``--p2p.persistent_peers`` +flag or the corresponding setting in the ``config.toml`` or the +``/dial_persistent_peers`` RPC endpoint to do it without stopping Tendermint +core instance. + +:: -Additionally, the peer-exchange protocol can be enabled using the -``--pex`` flag, though this feature is `still under -development `__. If -``--pex`` is enabled, peers will gossip about known peers and form a -more resilient network. + tendermint node --p2p.persistent_peers "10.11.12.13:46656,10.11.12.14:46656" + curl --data-urlencode "persistent_peers=[\"10.11.12.13:46656\",\"10.11.12.14:46656\"]" localhost:46657/dial_persistent_peers Adding a Non-Validator ~~~~~~~~~~~~~~~~~~~~~~ -Adding a non-validator is simple. Just copy the original -``genesis.json`` to ``~/.tendermint`` on the new machine and start the -node, specifying persistent_peers as necessary. If no persistent_peers are specified, the node -won't make any blocks, because it's not a validator, and it won't hear -about any blocks, because it's not connected to the other peer. +Adding a non-validator is simple. Just copy the original ``genesis.json`` to +``~/.tendermint`` on the new machine and start the node, specifying seeds or +persistent peers. If no seeds or persistent peers are specified, the node won't +make any blocks, because it's not a validator, and it won't hear about any +blocks, because it's not connected to the other peers. Adding a Validator ~~~~~~~~~~~~~~~~~~ From 705d51aa423b617d293501b3888eb57367cbf16f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 17:09:09 -0600 Subject: [PATCH 14/32] move dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect into PEX reactor --- node/node.go | 32 ++----------------- p2p/pex_reactor.go | 19 +++++++++-- p2p/pex_reactor_test.go | 10 +++--- .../{manual_peers.sh => persistent_peers.sh} | 0 4 files changed, 24 insertions(+), 37 deletions(-) rename test/p2p/{manual_peers.sh => persistent_peers.sh} (100%) diff --git a/node/node.go b/node/node.go index a4ea193b8..cf78a8aaa 100644 --- a/node/node.go +++ b/node/node.go @@ -8,7 +8,6 @@ import ( "net" "net/http" "strings" - "time" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -256,7 +255,8 @@ func NewNode(config *cfg.Config, trustMetricStore = trust.NewTrustMetricStore(trustHistoryDB, trust.DefaultConfig()) trustMetricStore.SetLogger(p2pLogger) - pexReactor := p2p.NewPEXReactor(addrBook) + pexReactor := p2p.NewPEXReactor(addrBook, + &p2p.PEXReactorConfig{Seeds: strings.Split(config.P2P.Seeds, ",")}) pexReactor.SetLogger(p2pLogger) sw.AddReactor("PEX", pexReactor) } @@ -388,38 +388,10 @@ func (n *Node) OnStart() error { } } - if n.config.P2P.Seeds != "" { - err = n.dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(strings.Split(n.config.P2P.Seeds, ",")) - if err != nil { - return err - } - } - // start tx indexer return n.indexerService.Start() } -func (n *Node) dialSeedsIfAddrBookIsEmptyOrPEXFailedToConnect(seeds []string) error { - // prefer peers from address book - if n.config.P2P.PexReactor && n.addrBook.Size() > 0 { - // give some time for PexReactor to connect us to other peers - const fallbackToSeedsAfter = 30 * time.Second - go func() { - time.Sleep(fallbackToSeedsAfter) - // fallback to dialing seeds if for some reason we can't connect to any - // peers - outbound, inbound, _ := n.sw.NumPeers() - if n.IsRunning() && outbound+inbound == 0 { - // TODO: ignore error? - n.sw.DialPeersAsync(n.addrBook, seeds, false) - } - }() - return nil - } - - return n.sw.DialPeersAsync(n.addrBook, seeds, false) -} - // OnStop stops the Node. It implements cmn.Service. func (n *Node) OnStop() { n.BaseService.OnStop() diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 64e7dafb9..9e67a6c2f 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -45,6 +45,7 @@ type PEXReactor struct { BaseReactor book *AddrBook + config *PEXReactorConfig ensurePeersPeriod time.Duration // tracks message count by peer, so we can prevent abuse @@ -52,10 +53,18 @@ type PEXReactor struct { maxMsgCountByPeer uint16 } +// PEXReactorConfig holds reactor specific configuration data. +type PEXReactorConfig struct { + // Seeds is a list of addresses reactor may use if it can't connect to peers + // in the addrbook. + Seeds []string +} + // NewPEXReactor creates new PEX reactor. -func NewPEXReactor(b *AddrBook) *PEXReactor { +func NewPEXReactor(b *AddrBook, config *PEXReactorConfig) *PEXReactor { r := &PEXReactor{ book: b, + config: config, ensurePeersPeriod: defaultEnsurePeersPeriod, msgCountByPeer: cmn.NewCMap(), maxMsgCountByPeer: defaultMaxMsgCountByPeer, @@ -238,7 +247,7 @@ func (r *PEXReactor) ensurePeersRoutine() { // placeholder. It should not be the case that an address becomes old/vetted // upon a single successful connection. func (r *PEXReactor) ensurePeers() { - numOutPeers, _, numDialing := r.Switch.NumPeers() + numOutPeers, numInPeers, numDialing := r.Switch.NumPeers() numToDial := minNumOutboundPeers - (numOutPeers + numDialing) r.Logger.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial) if numToDial <= 0 { @@ -291,6 +300,12 @@ func (r *PEXReactor) ensurePeers() { r.RequestPEX(peer) } } + + // If we can't connect to any known address, fallback to dialing seeds + if numOutPeers+numInPeers+numDialing == 0 { + r.Logger.Info("No addresses to dial nor connected peers. Will dial seeds", "seeds", r.config.Seeds) + r.Switch.DialPeersAsync(r.book, r.config.Seeds, false) + } } func (r *PEXReactor) flushMsgCountByPeer() { diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index a14f0eb2a..b37f66411 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -24,7 +24,7 @@ func TestPEXReactorBasic(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", true) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) assert.NotNil(r) @@ -40,7 +40,7 @@ func TestPEXReactorAddRemovePeer(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", true) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) size := book.Size() @@ -76,7 +76,7 @@ func TestPEXReactorRunning(t *testing.T) { switches[i] = makeSwitch(config, i, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { sw.SetLogger(log.TestingLogger().With("switch", i)) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) r.SetEnsurePeersPeriod(250 * time.Millisecond) sw.AddReactor("pex", r) @@ -141,7 +141,7 @@ func TestPEXReactorReceive(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", false) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) peer := createRandomPeer(false) @@ -166,7 +166,7 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { book := NewAddrBook(dir+"addrbook.json", true) book.SetLogger(log.TestingLogger()) - r := NewPEXReactor(book) + r := NewPEXReactor(book, &PEXReactorConfig{}) r.SetLogger(log.TestingLogger()) r.SetMaxMsgCountByPeer(5) diff --git a/test/p2p/manual_peers.sh b/test/p2p/persistent_peers.sh similarity index 100% rename from test/p2p/manual_peers.sh rename to test/p2p/persistent_peers.sh From 075ae1e301c5957dd1954a769e3e09eb270852cc Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 9 Jan 2018 18:29:29 -0600 Subject: [PATCH 15/32] minimal test for dialing seeds in pex reactor --- p2p/pex_reactor.go | 13 ++++++----- p2p/pex_reactor_test.go | 49 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 9e67a6c2f..ce2bba8bc 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -293,16 +293,17 @@ func (r *PEXReactor) ensurePeers() { // If we need more addresses, pick a random peer and ask for more. if r.book.NeedMoreAddrs() { - if peers := r.Switch.Peers().List(); len(peers) > 0 { - i := rand.Int() % len(peers) // nolint: gas - peer := peers[i] - r.Logger.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer) + peers := r.Switch.Peers().List() + peersCount := len(peers) + if peersCount > 0 { + peer := peers[rand.Int()%peersCount] // nolint: gas + r.Logger.Info("We need more addresses. Sending pexRequest to random peer", "peer", peer) r.RequestPEX(peer) } } - // If we can't connect to any known address, fallback to dialing seeds - if numOutPeers+numInPeers+numDialing == 0 { + // If we are not connected to nor dialing anybody, fallback to dialing seeds. + if numOutPeers+numInPeers+numDialing+len(toDial) == 0 { r.Logger.Info("No addresses to dial nor connected peers. Will dial seeds", "seeds", r.config.Seeds) r.Switch.DialPeersAsync(r.book, r.config.Seeds, false) } diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index b37f66411..fc2fe687f 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -107,6 +107,7 @@ func TestPEXReactorRunning(t *testing.T) { func assertSomePeersWithTimeout(t *testing.T, switches []*Switch, checkPeriod, timeout time.Duration) { ticker := time.NewTicker(checkPeriod) + remaining := timeout for { select { case <-ticker.C: @@ -118,16 +119,21 @@ func assertSomePeersWithTimeout(t *testing.T, switches []*Switch, checkPeriod, t allGood = false } } + remaining -= checkPeriod + if remaining < 0 { + remaining = 0 + } if allGood { return } - case <-time.After(timeout): + case <-time.After(remaining): numPeersStr := "" for i, s := range switches { outbound, inbound, _ := s.NumPeers() numPeersStr += fmt.Sprintf("%d => {outbound: %d, inbound: %d}, ", i, outbound, inbound) } t.Errorf("expected all switches to be connected to at least one peer (switches: %s)", numPeersStr) + return } } } @@ -180,6 +186,47 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ListenAddr)) } +func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) // nolint: errcheck + + book := NewAddrBook(dir+"addrbook.json", false) + book.SetLogger(log.TestingLogger()) + + // 1. create seed + seed := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + sw.SetLogger(log.TestingLogger()) + + r := NewPEXReactor(book, &PEXReactorConfig{}) + r.SetLogger(log.TestingLogger()) + r.SetEnsurePeersPeriod(250 * time.Millisecond) + sw.AddReactor("pex", r) + return sw + }) + seed.AddListener(NewDefaultListener("tcp", seed.NodeInfo().ListenAddr, true, log.TestingLogger())) + err = seed.Start() + require.Nil(t, err) + defer seed.Stop() + + // 2. create usual peer + sw := makeSwitch(config, 1, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { + sw.SetLogger(log.TestingLogger()) + + r := NewPEXReactor(book, &PEXReactorConfig{Seeds: []string{seed.NodeInfo().ListenAddr}}) + r.SetLogger(log.TestingLogger()) + r.SetEnsurePeersPeriod(250 * time.Millisecond) + sw.AddReactor("pex", r) + return sw + }) + err = sw.Start() + require.Nil(t, err) + defer sw.Stop() + + // 3. check that peer at least connects to seed + assertSomePeersWithTimeout(t, []*Switch{sw}, 10*time.Millisecond, 10*time.Second) +} + func createRoutableAddr() (addr string, netAddr *NetAddress) { for { addr = cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256) From e2b3b5b58ccec6efff6385ddd64806d71e1fb3f6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 14:50:32 -0500 Subject: [PATCH 16/32] dial_persistent_peers -> dial_peers with persistent option --- docs/specification/rpc.rst | 2 +- docs/using-tendermint.rst | 8 ++++---- p2p/switch.go | 2 +- rpc/client/localclient.go | 4 ++-- rpc/client/mock/client.go | 4 ++-- rpc/core/net.go | 14 +++++++------- rpc/core/routes.go | 2 +- rpc/core/types/responses.go | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/specification/rpc.rst b/docs/specification/rpc.rst index e714cb353..7df394d77 100644 --- a/docs/specification/rpc.rst +++ b/docs/specification/rpc.rst @@ -112,7 +112,7 @@ An HTTP Get request to the root RPC endpoint (e.g. http://localhost:46657/broadcast_tx_sync?tx=_ http://localhost:46657/commit?height=_ http://localhost:46657/dial_seeds?seeds=_ - http://localhost:46657/dial_persistent_peers?persistent_peers=_ + http://localhost:46657/dial_peers?peers=_&persistent=_ http://localhost:46657/subscribe?event=_ http://localhost:46657/tx?hash=_&prove=_ http://localhost:46657/unsafe_start_cpu_profiler?filename=_ diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index 1b60ea5ff..735fce654 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -287,7 +287,7 @@ specify seeds for a running node to connect to: :: - curl --data-urlencode "seeds=[\"1.2.3.4:46656\",\"5.6.7.8:46656\"]" localhost:46657/dial_seeds + curl 'localhost:46657/dial_seeds?seeds=\["1.2.3.4:46656","5.6.7.8:46656"\]' Note, if the peer-exchange protocol (PEX) is enabled (default), you should not normally need seeds after the first start. Peers will be gossipping about known @@ -296,13 +296,13 @@ peers and forming a network, storing peer addresses in the addrbook. If you want Tendermint to connect to specific set of addresses and maintain a persistent connection with each, you can use the ``--p2p.persistent_peers`` flag or the corresponding setting in the ``config.toml`` or the -``/dial_persistent_peers`` RPC endpoint to do it without stopping Tendermint +``/dial_peers`` RPC endpoint to do it without stopping Tendermint core instance. :: tendermint node --p2p.persistent_peers "10.11.12.13:46656,10.11.12.14:46656" - curl --data-urlencode "persistent_peers=[\"10.11.12.13:46656\",\"10.11.12.14:46656\"]" localhost:46657/dial_persistent_peers + curl 'localhost:46657/dial_peers?persistent=true&peers=\["1.2.3.4:46656","5.6.7.8:46656"\]' Adding a Non-Validator ~~~~~~~~~~~~~~~~~~~~~~ @@ -382,7 +382,7 @@ and the new ``priv_validator.json`` to the ``~/.tendermint/config`` on a new machine. Now run ``tendermint node`` on both machines, and use either -``--p2p.persistent_peers`` or the ``/dial_persistent_peers`` to get them to peer up. They +``--p2p.persistent_peers`` or the ``/dial_peers`` to get them to peer up. They should start making blocks, and will only continue to do so as long as both of them are online. diff --git a/p2p/switch.go b/p2p/switch.go index 1b449d7e0..95c632aa7 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -323,7 +323,7 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent } if addrBook != nil { - // add persistent peers to `addrBook` + // add peers to `addrBook` ourAddrS := sw.nodeInfo.ListenAddr ourAddr, _ := NewNetAddressString(ourAddrS) for _, netAddr := range netAddrs { diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index cc23b9449..5e0573a1b 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -88,8 +88,8 @@ func (Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return core.UnsafeDialSeeds(seeds) } -func (Local) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - return core.UnsafeDialPersistentPeers(persistent_peers) +func (Local) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + return core.UnsafeDialPeers(peers, persistent) } func (Local) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index 913812d6d..6c4728986 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -111,8 +111,8 @@ func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return core.UnsafeDialSeeds(seeds) } -func (c Client) DialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - return core.UnsafeDialPersistentPeers(persistent_peers) +func (c Client) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + return core.UnsafeDialPeers(peers, persistent) } func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { diff --git a/rpc/core/net.go b/rpc/core/net.go index b528e1e35..af52c81cc 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -66,17 +66,17 @@ func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil } -func UnsafeDialPersistentPeers(persistent_peers []string) (*ctypes.ResultDialPersistentPeers, error) { - if len(persistent_peers) == 0 { - return &ctypes.ResultDialPersistentPeers{}, errors.New("No persistent peers provided") +func UnsafeDialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + if len(peers) == 0 { + return &ctypes.ResultDialPeers{}, errors.New("No peers provided") } // starts go routines to dial each peer after random delays - logger.Info("DialPersistentPeers", "addrBook", addrBook, "persistent_peers", persistent_peers) - err := p2pSwitch.DialPeersAsync(addrBook, persistent_peers, true) + logger.Info("DialPeers", "addrBook", addrBook, "peers", peers, "persistent", persistent) + err := p2pSwitch.DialPeersAsync(addrBook, peers, persistent) if err != nil { - return &ctypes.ResultDialPersistentPeers{}, err + return &ctypes.ResultDialPeers{}, err } - return &ctypes.ResultDialPersistentPeers{"Dialing persistent peers in progress. See /net_info for details"}, nil + return &ctypes.ResultDialPeers{"Dialing peers in progress. See /net_info for details"}, nil } // Get genesis file. diff --git a/rpc/core/routes.go b/rpc/core/routes.go index d00165e6a..3ea7aa08c 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -39,7 +39,7 @@ var Routes = map[string]*rpc.RPCFunc{ func AddUnsafeRoutes() { // control API Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") - Routes["dial_persistent_peers"] = rpc.NewRPCFunc(UnsafeDialPersistentPeers, "persistent_peers") + Routes["dial_peers"] = rpc.NewRPCFunc(UnsafeDialPeers, "peers,persistent") Routes["unsafe_flush_mempool"] = rpc.NewRPCFunc(UnsafeFlushMempool, "") // profiler API diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index c26defb76..bffdd0281 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -86,7 +86,7 @@ type ResultDialSeeds struct { Log string `json:"log"` } -type ResultDialPersistentPeers struct { +type ResultDialPeers struct { Log string `json:"log"` } From c1e167e330907f5639c32b4099d393ae3492f868 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 15:11:13 -0500 Subject: [PATCH 17/32] note in trust metric test --- p2p/trust/metric_test.go | 2 ++ p2p/trust/ticker.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/trust/metric_test.go b/p2p/trust/metric_test.go index 00219a19e..69c9f8f2a 100644 --- a/p2p/trust/metric_test.go +++ b/p2p/trust/metric_test.go @@ -68,7 +68,9 @@ func TestTrustMetricStopPause(t *testing.T) { tt.NextTick() tm.Pause() + // could be 1 or 2 because Pause and NextTick race first := tm.Copy().numIntervals + // Allow more time to pass and check the intervals are unchanged tt.NextTick() tt.NextTick() diff --git a/p2p/trust/ticker.go b/p2p/trust/ticker.go index bce9fcc24..3f0f30919 100644 --- a/p2p/trust/ticker.go +++ b/p2p/trust/ticker.go @@ -24,7 +24,7 @@ type TestTicker struct { // NewTestTicker returns our ticker used within test routines func NewTestTicker() *TestTicker { - c := make(chan time.Time, 1) + c := make(chan time.Time) return &TestTicker{ C: c, } From 9670519a211f81474dd3a1b42a0d210e0aab54e4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 15:38:40 -0500 Subject: [PATCH 18/32] remove PoW from ID --- node/node.go | 8 ++----- p2p/key.go | 64 ++++++++++++++++++++----------------------------- p2p/key_test.go | 31 ++++++++++++------------ 3 files changed, 44 insertions(+), 59 deletions(-) diff --git a/node/node.go b/node/node.go index 7fecf710c..bd3bd1ca8 100644 --- a/node/node.go +++ b/node/node.go @@ -368,11 +368,8 @@ func (n *Node) OnStart() error { n.sw.AddListener(l) // Generate node PrivKey - // TODO: both the loading function and the target - // 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) + // TODO: the loading function will need to be configurable + nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile()) if err != nil { return err } @@ -381,7 +378,6 @@ func (n *Node) OnStart() error { // Start the switch n.sw.SetNodeInfo(n.makeNodeInfo(nodeKey.PubKey())) n.sw.SetNodeKey(nodeKey) - n.sw.SetPeerIDTarget(target) err = n.sw.Start() if err != nil { return err diff --git a/p2p/key.go b/p2p/key.go index d0031458d..6883c86e2 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "math/big" crypto "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" @@ -42,35 +41,18 @@ func (nodeKey *NodeKey) SatisfiesTarget(target []byte) bool { return bytes.Compare(nodeKey.id(), target) < 0 } -// 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) { +// LoadOrGenNodeKey attempts to load the NodeKey from the given filePath. +// If the file does not exist, it generates and saves a new NodeKey. +func LoadOrGenNodeKey(filePath string) (*NodeKey, error) { if cmn.FileExists(filePath) { nodeKey, err := loadNodeKey(filePath) if err != nil { return nil, err } - if !nodeKey.SatisfiesTarget(target) { - return nil, fmt.Errorf("Loaded ID (%s) 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 genNodeKey(filePath) } - return append(prefix, bytes.Repeat([]byte{255}, 20-len(prefix))...) } func loadNodeKey(filePath string) (*NodeKey, error) { @@ -86,8 +68,8 @@ func loadNodeKey(filePath string) (*NodeKey, error) { return nodeKey, nil } -func genNodeKey(filePath string, target []byte) (*NodeKey, error) { - privKey := genPrivKeyEd25519PoW(target).Wrap() +func genNodeKey(filePath string) (*NodeKey, error) { + privKey := crypto.GenPrivKeyEd25519().Wrap() nodeKey := &NodeKey{ PrivKey: privKey, } @@ -103,20 +85,26 @@ func genNodeKey(filePath string, target []byte) (*NodeKey, error) { 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() +//------------------------------------------------------------------------------ +// MakePoWTarget returns the big-endian encoding of 2^(targetBits - difficulty) - 1. +// It can be used as a Proof of Work target. +// NOTE: targetBits must be a multiple of 8 and difficulty must be less than targetBits. +func MakePoWTarget(difficulty, targetBits uint) []byte { + if targetBits%8 != 0 { + panic(fmt.Sprintf("targetBits (%d) not a multiple of 8", targetBits)) + } + if difficulty >= targetBits { + panic(fmt.Sprintf("difficulty (%d) >= targetBits (%d)", difficulty, targetBits)) + } + targetBytes := targetBits / 8 + zeroPrefixLen := (int(difficulty) / 8) + prefix := bytes.Repeat([]byte{0}, zeroPrefixLen) + mod := (difficulty % 8) + if mod > 0 { + nonZeroPrefix := byte(1<<(8-mod) - 1) + prefix = append(prefix, nonZeroPrefix) } - return privKey + tailLen := int(targetBytes) - len(prefix) + return append(prefix, bytes.Repeat([]byte{0xFF}, tailLen)...) } diff --git a/p2p/key_test.go b/p2p/key_test.go index ef885e55d..c2e1f3e0e 100644 --- a/p2p/key_test.go +++ b/p2p/key_test.go @@ -13,37 +13,38 @@ import ( func TestLoadOrGenNodeKey(t *testing.T) { filePath := filepath.Join(os.TempDir(), cmn.RandStr(12)+"_peer_id.json") - target := MakePoWTarget(2) - nodeKey, err := LoadOrGenNodeKey(filePath, target) + nodeKey, err := LoadOrGenNodeKey(filePath) assert.Nil(t, err) - nodeKey2, err := LoadOrGenNodeKey(filePath, target) + nodeKey2, err := LoadOrGenNodeKey(filePath) assert.Nil(t, err) assert.Equal(t, nodeKey, nodeKey2) } -func repeatBytes(val byte, n int) []byte { - return bytes.Repeat([]byte{val}, n) +//---------------------------------------------------------- + +func padBytes(bz []byte, targetBytes int) []byte { + return append(bz, bytes.Repeat([]byte{0xFF}, targetBytes-len(bz))...) } func TestPoWTarget(t *testing.T) { + targetBytes := 20 cases := []struct { - difficulty uint8 + difficulty uint 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)...)}, + {0, padBytes([]byte{}, targetBytes)}, + {1, padBytes([]byte{127}, targetBytes)}, + {8, padBytes([]byte{0}, targetBytes)}, + {9, padBytes([]byte{0, 127}, targetBytes)}, + {10, padBytes([]byte{0, 63}, targetBytes)}, + {16, padBytes([]byte{0, 0}, targetBytes)}, + {17, padBytes([]byte{0, 0, 127}, targetBytes)}, } for _, c := range cases { - assert.Equal(t, MakePoWTarget(c.difficulty), c.target) + assert.Equal(t, MakePoWTarget(c.difficulty, 20*8), c.target) } - } From e4d52401cfa08355bfbe9abf422d4d795d363dcf Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 16:06:31 -0500 Subject: [PATCH 19/32] some fixes from review --- p2p/addrbook_test.go | 2 +- p2p/key.go | 5 +++++ p2p/netaddress.go | 9 +++++---- p2p/netaddress_test.go | 7 +++++++ p2p/pex_reactor.go | 9 +++++---- p2p/switch.go | 3 ++- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/p2p/addrbook_test.go b/p2p/addrbook_test.go index 07ab3474c..bcef569df 100644 --- a/p2p/addrbook_test.go +++ b/p2p/addrbook_test.go @@ -190,7 +190,7 @@ func randIPv4Address(t *testing.T) *NetAddress { ) port := rand.Intn(65535-1) + 1 addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) - addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) // TODO + addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/key.go b/p2p/key.go index 6883c86e2..5a4ab48f7 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -11,8 +11,13 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) +// ID is a hex-encoded crypto.Address type ID string +// IDByteLength is the length of a crypto.Address. Currently only 20. +// TODO: support other length addresses ? +const IDByteLength = 20 + //------------------------------------------------------------------------------ // Persistent peer ID // TODO: encrypt on disk diff --git a/p2p/netaddress.go b/p2p/netaddress.go index d804e3488..c11d1442e 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -50,8 +50,8 @@ func NewNetAddress(id ID, addr net.Addr) *NetAddress { } // NewNetAddressString returns a new NetAddress using the provided -// address in the form of "IP:Port". Also resolves the host if host -// is not an IP. +// address in the form of "ID@IP:Port", where the ID is optional. +// Also resolves the host if host is not an IP. func NewNetAddressString(addr string) (*NetAddress, error) { addr = removeProtocolIfDefined(addr) @@ -63,8 +63,9 @@ func NewNetAddressString(addr string) (*NetAddress, error) { if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("Address (%s) contains invalid ID", addr)) } - if len(idBytes) != 20 { - return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be 20 hex-encoded bytes", len(idBytes)) + if len(idBytes) != IDByteLength { + return nil, fmt.Errorf("Address (%s) contains ID of invalid length (%d). Should be %d hex-encoded bytes", + addr, len(idBytes), IDByteLength) } id, addr = ID(idStr), spl[1] } diff --git a/p2p/netaddress_test.go b/p2p/netaddress_test.go index 0aa45423c..6c1930a2f 100644 --- a/p2p/netaddress_test.go +++ b/p2p/netaddress_test.go @@ -48,6 +48,13 @@ func TestNewNetAddressString(t *testing.T) { {"tcp://this-isnot-hex@127.0.0.1:8080", "", false}, {"tcp://xxxxbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "", false}, {"tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@127.0.0.1:8080", true}, + + {"tcp://@127.0.0.1:8080", "", false}, + {"tcp://@", "", false}, + {"", "", false}, + {"@", "", false}, + {" @", "", false}, + {" @ ", "", false}, } for _, tc := range testCases { diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 6fd0285d3..fc1cbdd98 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -109,19 +109,20 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { // For outbound peers, the address is already in the books. - // Either it was added in DialPersistentPeers or when we + // Either it was added in DialPeersAsync or when we // received the peer's address in r.Receive if r.book.NeedMoreAddrs() { r.RequestPEX(p) } - } else { // For inbound connections, the peer is its own source - addr, err := NewNetAddressString(p.NodeInfo().ListenAddr) - addr.ID = p.ID() // TODO: handle in NewNetAddress func + } else { + addrStr := fmt.Sprintf("%s@%s", p.ID(), p.NodeInfo().ListenAddr) + addr, err := NewNetAddressString(addrStr) if err != nil { // peer gave us a bad ListenAddr. TODO: punish r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err) return } + // For inbound connections, the peer is its own source r.book.AddAddress(addr, addr) } } diff --git a/p2p/switch.go b/p2p/switch.go index cd353ca86..eca52e982 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -94,6 +94,7 @@ type Switch struct { var ( ErrSwitchDuplicatePeer = errors.New("Duplicate peer") + ErrSwitchConnectToSelf = errors.New("Connect to self") ) func NewSwitch(config *cfg.P2PConfig) *Switch { @@ -241,7 +242,7 @@ func (sw *Switch) OnStop() { func (sw *Switch) addPeer(peer *peer) error { // Avoid self if sw.nodeKey.ID() == peer.ID() { - return errors.New("Ignoring connection from self") + return ErrSwitchConnectToSelf } // Filter peer against white list From 53a5498fc5022a72b27a38e9583160356d584cd1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 16:14:28 -0500 Subject: [PATCH 20/32] more fixes from review --- config/config.go | 2 +- config/toml.go | 3 +++ node/node.go | 2 +- p2p/addrbook.go | 2 +- p2p/addrbook_test.go | 5 +++-- p2p/key.go | 12 +++++------- p2p/netaddress.go | 7 ++++++- p2p/peer.go | 2 +- p2p/pex_reactor.go | 16 ++++------------ p2p/switch.go | 10 +--------- p2p/types.go | 12 ++++++++++++ 11 files changed, 38 insertions(+), 35 deletions(-) diff --git a/config/config.go b/config/config.go index 645b9e10a..f93b79241 100644 --- a/config/config.go +++ b/config/config.go @@ -95,7 +95,7 @@ type BaseConfig struct { PrivValidator string `mapstructure:"priv_validator_file"` // A JSON file containing the private key to use for p2p authenticated encryption - NodeKey string `mapstructure:"node_key"` + NodeKey string `mapstructure:"node_key_file"` // A custom human readable name for this node Moniker string `mapstructure:"moniker"` diff --git a/config/toml.go b/config/toml.go index f979af955..e7bd26103 100644 --- a/config/toml.go +++ b/config/toml.go @@ -87,6 +87,9 @@ genesis_file = "{{ .BaseConfig.Genesis }}" # Path to the JSON file containing the private key to use as a validator in the consensus protocol priv_validator_file = "{{ .BaseConfig.PrivValidator }}" +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "{{ .BaseConfig.NodeKey}}" + # Mechanism to connect to the ABCI application: socket | grpc abci = "{{ .BaseConfig.ABCI }}" diff --git a/node/node.go b/node/node.go index bd3bd1ca8..5535b1e16 100644 --- a/node/node.go +++ b/node/node.go @@ -368,7 +368,7 @@ func (n *Node) OnStart() error { n.sw.AddListener(l) // Generate node PrivKey - // TODO: the loading function will need to be configurable + // TODO: pass in like priv_val nodeKey, err := p2p.LoadOrGenNodeKey(n.config.NodeKeyFile()) if err != nil { return err diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 6ccec61f7..8826ff1e0 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -283,7 +283,7 @@ func (a *AddrBook) RemoveAddress(addr *NetAddress) { if ka == nil { return } - a.Logger.Info("Remove address from book", "addr", ka.Addr) + a.Logger.Info("Remove address from book", "addr", ka.Addr, "ID", ka.ID) a.removeFromAllBuckets(ka) } diff --git a/p2p/addrbook_test.go b/p2p/addrbook_test.go index bcef569df..00051ae1f 100644 --- a/p2p/addrbook_test.go +++ b/p2p/addrbook_test.go @@ -189,8 +189,9 @@ func randIPv4Address(t *testing.T) *NetAddress { rand.Intn(255), ) port := rand.Intn(65535-1) + 1 - addr, err := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port)) - addr.ID = ID(hex.EncodeToString(cmn.RandBytes(20))) + id := ID(hex.EncodeToString(cmn.RandBytes(IDByteLength))) + idAddr := IDAddressString(id, fmt.Sprintf("%v:%v", ip, port)) + addr, err := NewNetAddressString(idAddr) assert.Nil(t, err, "error generating rand network address") if addr.Routable() { return addr diff --git a/p2p/key.go b/p2p/key.go index 5a4ab48f7..ea0f0b071 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -30,11 +30,7 @@ type NodeKey struct { // ID returns the peer's canonical ID - the hash of its public key. func (nodeKey *NodeKey) ID() ID { - return ID(hex.EncodeToString(nodeKey.id())) -} - -func (nodeKey *NodeKey) id() []byte { - return nodeKey.PrivKey.PubKey().Address() + return PubKeyToID(nodeKey.PubKey()) } // PubKey returns the peer's PubKey @@ -42,8 +38,10 @@ func (nodeKey *NodeKey) PubKey() crypto.PubKey { return nodeKey.PrivKey.PubKey() } -func (nodeKey *NodeKey) SatisfiesTarget(target []byte) bool { - return bytes.Compare(nodeKey.id(), target) < 0 +// PubKeyToID returns the ID corresponding to the given PubKey. +// It's the hex-encoding of the pubKey.Address(). +func PubKeyToID(pubKey crypto.PubKey) ID { + return ID(hex.EncodeToString(pubKey.Address())) } // LoadOrGenNodeKey attempts to load the NodeKey from the given filePath. diff --git a/p2p/netaddress.go b/p2p/netaddress.go index c11d1442e..fed5e59d4 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -26,6 +26,11 @@ type NetAddress struct { str string } +// IDAddressString returns id@hostPort. +func IDAddressString(id ID, hostPort string) string { + return fmt.Sprintf("%s@%s", id, hostPort) +} + // NewNetAddress returns a new NetAddress using the provided TCP // address. When testing, other net.Addr (except TCP) will result in // using 0.0.0.0:0. When normal run, other net.Addr (except TCP) will @@ -136,7 +141,7 @@ func (na *NetAddress) String() string { if na.str == "" { addrStr := na.DialString() if na.ID != "" { - addrStr = fmt.Sprintf("%s@%s", na.ID, addrStr) + addrStr = IDAddressString(na.ID, addrStr) } na.str = addrStr } diff --git a/p2p/peer.go b/p2p/peer.go index ecf2efce5..ff8d8d0ed 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -304,7 +304,7 @@ func (p *peer) Set(key string, data interface{}) { p.Data.Set(key, data) } -// Key returns the peer's ID - the hex encoded hash of its pubkey. +// ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { return ID(hex.EncodeToString(p.PubKey().Address())) } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index fc1cbdd98..0816a6e98 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -115,14 +115,8 @@ func (r *PEXReactor) AddPeer(p Peer) { r.RequestPEX(p) } } else { - addrStr := fmt.Sprintf("%s@%s", p.ID(), p.NodeInfo().ListenAddr) - addr, err := NewNetAddressString(addrStr) - if err != nil { - // peer gave us a bad ListenAddr. TODO: punish - r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err) - return - } // For inbound connections, the peer is its own source + addr := p.NodeInfo().NetAddress() r.book.AddAddress(addr, addr) } } @@ -261,7 +255,7 @@ func (r *PEXReactor) ensurePeers() { // NOTE: range here is [10, 90]. Too high ? newBias := cmn.MinInt(numOutPeers, 8)*10 + 10 - toDial := make(map[string]*NetAddress) + toDial := make(map[ID]*NetAddress) // Try maxAttempts times to pick numToDial addresses to dial maxAttempts := numToDial * 3 for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ { @@ -269,19 +263,17 @@ func (r *PEXReactor) ensurePeers() { if try == nil { continue } - if _, selected := toDial[string(try.ID)]; selected { + if _, selected := toDial[try.ID]; selected { continue } if dialling := r.Switch.IsDialing(try.ID); dialling { continue } - // XXX: Should probably use pubkey as peer key ... - // TODO: use the ID correctly if connected := r.Switch.Peers().Has(try.ID); connected { continue } r.Logger.Info("Will dial address", "addr", try) - toDial[string(try.ID)] = try + toDial[try.ID] = try } // Dial picked addresses diff --git a/p2p/switch.go b/p2p/switch.go index eca52e982..da1aa552f 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -84,7 +84,6 @@ type Switch struct { dialing *cmn.CMap nodeInfo *NodeInfo // our node info nodeKey *NodeKey // our node privkey - peerIDTarget []byte filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -194,12 +193,6 @@ func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { } } -// 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. func (sw *Switch) OnStart() error { // Start reactors @@ -460,8 +453,7 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { // If no success after all that, it stops trying, and leaves it // to the PEX/Addrbook to find the peer again func (sw *Switch) reconnectToPeer(peer Peer) { - netAddr, _ := NewNetAddressString(peer.NodeInfo().RemoteAddr) - netAddr.ID = peer.ID() // TODO: handle above + netAddr := peer.NodeInfo().NetAddress() start := time.Now() sw.Logger.Info("Reconnecting to peer", "peer", peer) for i := 0; i < reconnectAttempts; i++ { diff --git a/p2p/types.go b/p2p/types.go index 860132902..287c88b01 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -11,6 +11,8 @@ import ( const maxNodeInfoSize = 10240 // 10Kb +// NodeInfo is the basic node information exchanged +// between two peers during the Tendermint P2P handshake type NodeInfo struct { PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey Moniker string `json:"moniker"` // arbitrary moniker @@ -54,6 +56,16 @@ func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { return nil } +func (info *NodeInfo) NetAddress() *NetAddress { + id := PubKeyToID(info.PubKey) + addr := info.ListenAddr + netAddr, err := NewNetAddressString(IDAddressString(id, addr)) + if err != nil { + panic(err) // everything should be well formed by now + } + return netAddr +} + func (info *NodeInfo) ListenHost() string { host, _, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas return host From 7667e119731271d745814b34379844a5a6fe7a29 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 16:50:03 -0500 Subject: [PATCH 21/32] remove RemoteAddr from NodeInfo --- p2p/peer.go | 2 -- p2p/peer_set_test.go | 1 - p2p/peer_test.go | 9 +++++---- p2p/pex_reactor.go | 24 +++++++++--------------- p2p/pex_reactor_test.go | 5 ++--- p2p/switch.go | 1 - p2p/types.go | 7 +++++-- 7 files changed, 21 insertions(+), 28 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index ff8d8d0ed..2f5dff78d 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -206,8 +206,6 @@ func (p *peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er return errors.Wrap(err, "Error removing deadline") } - peerNodeInfo.RemoteAddr = p.Addr().String() - p.nodeInfo = peerNodeInfo return nil } diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 609c49004..e50bb384b 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -15,7 +15,6 @@ import ( func randPeer() *peer { return &peer{ nodeInfo: &NodeInfo{ - RemoteAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 78a4e2f5b..aafe8d7a9 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -142,10 +142,11 @@ func (p *remotePeer) accept(l net.Listener) { golog.Fatalf("Failed to create a peer: %+v", err) } err = peer.HandshakeTimeout(&NodeInfo{ - PubKey: p.PrivKey.PubKey(), - Moniker: "remote_peer", - Network: "testing", - Version: "123.123.123", + PubKey: p.PrivKey.PubKey(), + Moniker: "remote_peer", + Network: "testing", + Version: "123.123.123", + ListenAddr: l.Addr().String(), }, 1*time.Second) if err != nil { golog.Fatalf("Failed to perform handshake: %+v", err) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 0816a6e98..5aa63db71 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -129,17 +129,11 @@ func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) { // Receive implements Reactor by handling incoming PEX messages. func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { - srcAddrStr := src.NodeInfo().RemoteAddr - srcAddr, err := NewNetAddressString(srcAddrStr) - if err != nil { - // this should never happen. TODO: cancel conn - r.Logger.Error("Error in Receive: invalid peer address", "addr", srcAddrStr, "err", err) - return - } + srcAddr := src.NodeInfo().NetAddress() - r.IncrementMsgCountForPeer(srcAddrStr) - if r.ReachedMaxMsgCountForPeer(srcAddrStr) { - r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddrStr) + r.IncrementMsgCountForPeer(srcAddr.ID) + if r.ReachedMaxMsgCountForPeer(srcAddr.ID) { + r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddr) // TODO remove src from peers? return } @@ -192,19 +186,19 @@ func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) { // ReachedMaxMsgCountForPeer returns true if we received too many // messages from peer with address `addr`. // NOTE: assumes the value in the CMap is non-nil -func (r *PEXReactor) ReachedMaxMsgCountForPeer(addr string) bool { - return r.msgCountByPeer.Get(addr).(uint16) >= r.maxMsgCountByPeer +func (r *PEXReactor) ReachedMaxMsgCountForPeer(peerID ID) bool { + return r.msgCountByPeer.Get(string(peerID)).(uint16) >= r.maxMsgCountByPeer } // Increment or initialize the msg count for the peer in the CMap -func (r *PEXReactor) IncrementMsgCountForPeer(addr string) { +func (r *PEXReactor) IncrementMsgCountForPeer(peerID ID) { var count uint16 - countI := r.msgCountByPeer.Get(addr) + countI := r.msgCountByPeer.Get(string(peerID)) if countI != nil { count = countI.(uint16) } count++ - r.msgCountByPeer.Set(addr, count) + r.msgCountByPeer.Set(string(peerID), count) } // Ensures that sufficient peers are connected. (continuous) diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 2fb616fb9..296b18867 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -184,7 +184,7 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { r.Receive(PexChannel, peer, msg) } - assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ListenAddr)) + assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ID())) } func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { @@ -243,8 +243,7 @@ func createRandomPeer(outbound bool) *peer { addr, netAddr := createRoutableAddr() p := &peer{ nodeInfo: &NodeInfo{ - ListenAddr: addr, - RemoteAddr: netAddr.String(), + ListenAddr: netAddr.String(), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, outbound: outbound, diff --git a/p2p/switch.go b/p2p/switch.go index da1aa552f..484649145 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -615,7 +615,6 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f Moniker: cmn.Fmt("switch%d", i), Network: network, Version: version, - RemoteAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), }) s.SetNodeKey(nodeKey) diff --git a/p2p/types.go b/p2p/types.go index 287c88b01..2aec521b2 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -17,7 +17,6 @@ type NodeInfo struct { PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey Moniker string `json:"moniker"` // arbitrary moniker Network string `json:"network"` // network/chain ID - RemoteAddr string `json:"remote_addr"` // address for the connection ListenAddr string `json:"listen_addr"` // accepting incoming Version string `json:"version"` // major.minor.revision Other []string `json:"other"` // other application specific data @@ -56,6 +55,10 @@ func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { return nil } +func (info *NodeInfo) ID() ID { + return PubKeyToID(info.PubKey) +} + func (info *NodeInfo) NetAddress() *NetAddress { id := PubKeyToID(info.PubKey) addr := info.ListenAddr @@ -81,7 +84,7 @@ func (info *NodeInfo) ListenPort() int { } func (info NodeInfo) String() string { - return fmt.Sprintf("NodeInfo{pk: %v, moniker: %v, network: %v [remote %v, listen %v], version: %v (%v)}", info.PubKey, info.Moniker, info.Network, info.RemoteAddr, info.ListenAddr, info.Version, info.Other) + return fmt.Sprintf("NodeInfo{pk: %v, moniker: %v, network: %v [listen %v], version: %v (%v)}", info.PubKey, info.Moniker, info.Network, info.ListenAddr, info.Version, info.Other) } func splitVersion(version string) (string, string, string, error) { From 452d10f368a626a47bf4f5e9736a6b1166c252e0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 17:25:51 -0500 Subject: [PATCH 22/32] cleanup switch --- p2p/addrbook.go | 13 ++ p2p/base_reactor.go | 35 +++ p2p/peer.go | 2 + p2p/switch.go | 504 +++++++++++++++++--------------------------- p2p/test_util.go | 111 ++++++++++ p2p/util.go | 15 -- 6 files changed, 355 insertions(+), 325 deletions(-) create mode 100644 p2p/base_reactor.go create mode 100644 p2p/test_util.go delete mode 100644 p2p/util.go diff --git a/p2p/addrbook.go b/p2p/addrbook.go index 8826ff1e0..7591c3074 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -5,6 +5,7 @@ package p2p import ( + "crypto/sha256" "encoding/binary" "encoding/json" "fmt" @@ -867,3 +868,15 @@ func (ka *knownAddress) isBad() bool { return false } + +//----------------------------------------------------------------------------- + +// doubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes. +func doubleSha256(b []byte) []byte { + hasher := sha256.New() + hasher.Write(b) // nolint: errcheck, gas + sum := hasher.Sum(nil) + hasher.Reset() + hasher.Write(sum) // nolint: errcheck, gas + return hasher.Sum(nil) +} diff --git a/p2p/base_reactor.go b/p2p/base_reactor.go new file mode 100644 index 000000000..e8107d730 --- /dev/null +++ b/p2p/base_reactor.go @@ -0,0 +1,35 @@ +package p2p + +import cmn "github.com/tendermint/tmlibs/common" + +type Reactor interface { + cmn.Service // Start, Stop + + SetSwitch(*Switch) + GetChannels() []*ChannelDescriptor + AddPeer(peer Peer) + RemovePeer(peer Peer, reason interface{}) + Receive(chID byte, peer Peer, msgBytes []byte) // CONTRACT: msgBytes are not nil +} + +//-------------------------------------- + +type BaseReactor struct { + cmn.BaseService // Provides Start, Stop, .Quit + Switch *Switch +} + +func NewBaseReactor(name string, impl Reactor) *BaseReactor { + return &BaseReactor{ + BaseService: *cmn.NewBaseService(nil, name, impl), + Switch: nil, + } +} + +func (br *BaseReactor) SetSwitch(sw *Switch) { + br.Switch = sw +} +func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil } +func (_ *BaseReactor) AddPeer(peer Peer) {} +func (_ *BaseReactor) RemovePeer(peer Peer, reason interface{}) {} +func (_ *BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} diff --git a/p2p/peer.go b/p2p/peer.go index 2f5dff78d..1f5192d50 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -99,6 +99,8 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] func newInboundPeer(conn net.Conn, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { + // TODO: issue PoW challenge + return newPeerFromConnAndConfig(conn, false, reactorsByCh, chDescs, onPeerError, ourNodePrivKey, config) } diff --git a/p2p/switch.go b/p2p/switch.go index 484649145..4ece1a58c 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -12,7 +12,6 @@ import ( crypto "github.com/tendermint/go-crypto" cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tmlibs/common" - "github.com/tendermint/tmlibs/log" ) const ( @@ -31,46 +30,17 @@ const ( reconnectBackOffBaseSeconds = 3 ) -type Reactor interface { - cmn.Service // Start, Stop - - SetSwitch(*Switch) - GetChannels() []*ChannelDescriptor - AddPeer(peer Peer) - RemovePeer(peer Peer, reason interface{}) - Receive(chID byte, peer Peer, msgBytes []byte) // CONTRACT: msgBytes are not nil -} - -//-------------------------------------- - -type BaseReactor struct { - cmn.BaseService // Provides Start, Stop, .Quit - Switch *Switch -} - -func NewBaseReactor(name string, impl Reactor) *BaseReactor { - return &BaseReactor{ - BaseService: *cmn.NewBaseService(nil, name, impl), - Switch: nil, - } -} - -func (br *BaseReactor) SetSwitch(sw *Switch) { - br.Switch = sw -} -func (_ *BaseReactor) GetChannels() []*ChannelDescriptor { return nil } -func (_ *BaseReactor) AddPeer(peer Peer) {} -func (_ *BaseReactor) RemovePeer(peer Peer, reason interface{}) {} -func (_ *BaseReactor) Receive(chID byte, peer Peer, msgBytes []byte) {} +var ( + ErrSwitchDuplicatePeer = errors.New("Duplicate peer") + ErrSwitchConnectToSelf = errors.New("Connect to self") +) //----------------------------------------------------------------------------- -/* -The `Switch` handles peer connections and exposes an API to receive incoming messages -on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one -or more `Channels`. So while sending outgoing messages is typically performed on the peer, -incoming messages are received on the reactor. -*/ +// `Switch` handles peer connections and exposes an API to receive incoming messages +// on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one +// or more `Channels`. So while sending outgoing messages is typically performed on the peer, +// incoming messages are received on the reactor. type Switch struct { cmn.BaseService @@ -91,11 +61,6 @@ type Switch struct { rng *rand.Rand // seed for randomizing dial times and orders } -var ( - ErrSwitchDuplicatePeer = errors.New("Duplicate peer") - ErrSwitchConnectToSelf = errors.New("Connect to self") -) - func NewSwitch(config *cfg.P2PConfig) *Switch { sw := &Switch{ config: config, @@ -122,6 +87,9 @@ func NewSwitch(config *cfg.P2PConfig) *Switch { return sw } +//--------------------------------------------------------------------- +// Switch setup + // AddReactor adds the given reactor to the switch. // NOTE: Not goroutine safe. func (sw *Switch) AddReactor(name string, reactor Reactor) Reactor { @@ -193,6 +161,9 @@ func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { } } +//--------------------------------------------------------------------- +// Service start/stop + // OnStart implements BaseService. It starts all the reactors, peers, and listeners. func (sw *Switch) OnStart() error { // Start reactors @@ -228,92 +199,125 @@ func (sw *Switch) OnStop() { } } -// addPeer checks the given peer's validity, performs a handshake, and adds the -// peer to the switch and to all registered reactors. -// NOTE: This performs a blocking handshake before the peer is added. -// NOTE: If error is returned, caller is responsible for calling peer.CloseConn() -func (sw *Switch) addPeer(peer *peer) error { - // Avoid self - if sw.nodeKey.ID() == peer.ID() { - return ErrSwitchConnectToSelf - } - - // Filter peer against white list - if err := sw.FilterConnByAddr(peer.Addr()); err != nil { - return err - } - if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil { - return err - } +//--------------------------------------------------------------------- +// Peers - if err := peer.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second)); err != nil { - return err - } - - // Avoid duplicate - if sw.peers.Has(peer.ID()) { - return ErrSwitchDuplicatePeer +// Peers returns the set of peers that are connected to the switch. +func (sw *Switch) Peers() IPeerSet { + return sw.peers +} +// NumPeers returns the count of outbound/inbound and outbound-dialing peers. +func (sw *Switch) NumPeers() (outbound, inbound, dialing int) { + peers := sw.peers.List() + for _, peer := range peers { + if peer.IsOutbound() { + outbound++ + } else { + inbound++ + } } + dialing = sw.dialing.Size() + return +} - // Check version, chain id - if err := sw.nodeInfo.CompatibleWith(peer.NodeInfo()); err != nil { - return err +// Broadcast runs a go routine for each attempted send, which will block +// trying to send for defaultSendTimeoutSeconds. Returns a channel +// which receives success values for each attempted send (false if times out). +// NOTE: Broadcast uses goroutines, so order of broadcast may not be preserved. +// TODO: Something more intelligent. +func (sw *Switch) Broadcast(chID byte, msg interface{}) chan bool { + successChan := make(chan bool, len(sw.peers.List())) + sw.Logger.Debug("Broadcast", "channel", chID, "msg", msg) + for _, peer := range sw.peers.List() { + go func(peer Peer) { + success := peer.Send(chID, msg) + successChan <- success + }(peer) } + return successChan +} - // Start peer - if sw.IsRunning() { - sw.startInitPeer(peer) - } +// StopPeerForError disconnects from a peer due to external error. +// If the peer is persistent, it will attempt to reconnect. +// TODO: make record depending on reason. +func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { + sw.Logger.Error("Stopping peer for error", "peer", peer, "err", reason) + sw.stopAndRemovePeer(peer, reason) - // Add the peer to .peers. - // We start it first so that a peer in the list is safe to Stop. - // It should not err since we already checked peers.Has(). - if err := sw.peers.Add(peer); err != nil { - return err + if peer.IsPersistent() { + go sw.reconnectToPeer(peer) } +} - sw.Logger.Info("Added peer", "peer", peer) - return nil +// StopPeerGracefully disconnects from a peer gracefully. +// TODO: handle graceful disconnects. +func (sw *Switch) StopPeerGracefully(peer Peer) { + sw.Logger.Info("Stopping peer gracefully") + sw.stopAndRemovePeer(peer, nil) } -// FilterConnByAddr returns an error if connecting to the given address is forbidden. -func (sw *Switch) FilterConnByAddr(addr net.Addr) error { - if sw.filterConnByAddr != nil { - return sw.filterConnByAddr(addr) +func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { + sw.peers.Remove(peer) + peer.Stop() + for _, reactor := range sw.reactors { + reactor.RemovePeer(peer, reason) } - return nil } -// FilterConnByPubKey returns an error if connecting to the given public key is forbidden. -func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKey) error { - if sw.filterConnByPubKey != nil { - return sw.filterConnByPubKey(pubkey) - } - return nil +// reconnectToPeer tries to reconnect to the peer, first repeatedly +// with a fixed interval, then with exponential backoff. +// If no success after all that, it stops trying, and leaves it +// to the PEX/Addrbook to find the peer again +func (sw *Switch) reconnectToPeer(peer Peer) { + netAddr := peer.NodeInfo().NetAddress() + start := time.Now() + sw.Logger.Info("Reconnecting to peer", "peer", peer) + for i := 0; i < reconnectAttempts; i++ { + if !sw.IsRunning() { + return + } -} + peer, err := sw.DialPeerWithAddress(netAddr, true) + if err != nil { + sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) + // sleep a set amount + sw.randomSleep(reconnectInterval) + continue + } else { + sw.Logger.Info("Reconnected to peer", "peer", peer) + return + } + } -// SetAddrFilter sets the function for filtering connections by address. -func (sw *Switch) SetAddrFilter(f func(net.Addr) error) { - sw.filterConnByAddr = f -} + sw.Logger.Error("Failed to reconnect to peer. Beginning exponential backoff", + "peer", peer, "elapsed", time.Since(start)) + for i := 0; i < reconnectBackOffAttempts; i++ { + if !sw.IsRunning() { + return + } -// SetPubKeyFilter sets the function for filtering connections by public key. -func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKey) error) { - sw.filterConnByPubKey = f + // sleep an exponentially increasing amount + sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) + sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) + peer, err := sw.DialPeerWithAddress(netAddr, true) + if err != nil { + sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) + continue + } else { + sw.Logger.Info("Reconnected to peer", "peer", peer) + return + } + } + sw.Logger.Error("Failed to reconnect to peer. Giving up", "peer", peer, "elapsed", time.Since(start)) } -func (sw *Switch) startInitPeer(peer *peer) { - err := peer.Start() // spawn send/recv routines - if err != nil { - // Should never happen - sw.Logger.Error("Error starting peer", "peer", peer, "err", err) - } +//--------------------------------------------------------------------- +// Dialing - for _, reactor := range sw.reactors { - reactor.AddPeer(peer) - } +// IsDialing returns true if the switch is currently dialing the given ID. +func (sw *Switch) IsDialing(id ID) bool { + return sw.dialing.Has(string(id)) } // DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). @@ -355,12 +359,6 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent return nil } -// sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] -func (sw *Switch) randomSleep(interval time.Duration) { - r := time.Duration(sw.rng.Int63n(dialRandomizerIntervalMilliseconds)) * time.Millisecond - time.Sleep(r + interval) -} - // DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { @@ -395,121 +393,44 @@ func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, return peer, nil } -// IsDialing returns true if the switch is currently dialing the given ID. -func (sw *Switch) IsDialing(id ID) bool { - return sw.dialing.Has(string(id)) -} - -// Broadcast runs a go routine for each attempted send, which will block -// trying to send for defaultSendTimeoutSeconds. Returns a channel -// which receives success values for each attempted send (false if times out). -// NOTE: Broadcast uses goroutines, so order of broadcast may not be preserved. -// TODO: Something more intelligent. -func (sw *Switch) Broadcast(chID byte, msg interface{}) chan bool { - successChan := make(chan bool, len(sw.peers.List())) - sw.Logger.Debug("Broadcast", "channel", chID, "msg", msg) - for _, peer := range sw.peers.List() { - go func(peer Peer) { - success := peer.Send(chID, msg) - successChan <- success - }(peer) - } - return successChan -} - -// NumPeers returns the count of outbound/inbound and outbound-dialing peers. -func (sw *Switch) NumPeers() (outbound, inbound, dialing int) { - peers := sw.peers.List() - for _, peer := range peers { - if peer.IsOutbound() { - outbound++ - } else { - inbound++ - } - } - dialing = sw.dialing.Size() - return -} - -// Peers returns the set of peers that are connected to the switch. -func (sw *Switch) Peers() IPeerSet { - return sw.peers +// sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] +func (sw *Switch) randomSleep(interval time.Duration) { + r := time.Duration(sw.rng.Int63n(dialRandomizerIntervalMilliseconds)) * time.Millisecond + time.Sleep(r + interval) } -// StopPeerForError disconnects from a peer due to external error. -// If the peer is persistent, it will attempt to reconnect. -// TODO: make record depending on reason. -func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { - sw.Logger.Error("Stopping peer for error", "peer", peer, "err", reason) - sw.stopAndRemovePeer(peer, reason) +//------------------------------------------------------------------------------------ +// Connection filtering - if peer.IsPersistent() { - go sw.reconnectToPeer(peer) +// FilterConnByAddr returns an error if connecting to the given address is forbidden. +func (sw *Switch) FilterConnByAddr(addr net.Addr) error { + if sw.filterConnByAddr != nil { + return sw.filterConnByAddr(addr) } + return nil } -// reconnectToPeer tries to reconnect to the peer, first repeatedly -// with a fixed interval, then with exponential backoff. -// If no success after all that, it stops trying, and leaves it -// to the PEX/Addrbook to find the peer again -func (sw *Switch) reconnectToPeer(peer Peer) { - netAddr := peer.NodeInfo().NetAddress() - start := time.Now() - sw.Logger.Info("Reconnecting to peer", "peer", peer) - for i := 0; i < reconnectAttempts; i++ { - if !sw.IsRunning() { - return - } - - peer, err := sw.DialPeerWithAddress(netAddr, true) - if err != nil { - sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) - // sleep a set amount - sw.randomSleep(reconnectInterval) - continue - } else { - sw.Logger.Info("Reconnected to peer", "peer", peer) - return - } +// FilterConnByPubKey returns an error if connecting to the given public key is forbidden. +func (sw *Switch) FilterConnByPubKey(pubkey crypto.PubKey) error { + if sw.filterConnByPubKey != nil { + return sw.filterConnByPubKey(pubkey) } + return nil - sw.Logger.Error("Failed to reconnect to peer. Beginning exponential backoff", - "peer", peer, "elapsed", time.Since(start)) - for i := 0; i < reconnectBackOffAttempts; i++ { - if !sw.IsRunning() { - return - } - - // sleep an exponentially increasing amount - sleepIntervalSeconds := math.Pow(reconnectBackOffBaseSeconds, float64(i)) - sw.randomSleep(time.Duration(sleepIntervalSeconds) * time.Second) - peer, err := sw.DialPeerWithAddress(netAddr, true) - if err != nil { - sw.Logger.Info("Error reconnecting to peer. Trying again", "tries", i, "err", err, "peer", peer) - continue - } else { - sw.Logger.Info("Reconnected to peer", "peer", peer) - return - } - } - sw.Logger.Error("Failed to reconnect to peer. Giving up", "peer", peer, "elapsed", time.Since(start)) } -// StopPeerGracefully disconnects from a peer gracefully. -// TODO: handle graceful disconnects. -func (sw *Switch) StopPeerGracefully(peer Peer) { - sw.Logger.Info("Stopping peer gracefully") - sw.stopAndRemovePeer(peer, nil) +// SetAddrFilter sets the function for filtering connections by address. +func (sw *Switch) SetAddrFilter(f func(net.Addr) error) { + sw.filterConnByAddr = f } -func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { - sw.peers.Remove(peer) - peer.Stop() - for _, reactor := range sw.reactors { - reactor.RemovePeer(peer, reason) - } +// SetPubKeyFilter sets the function for filtering connections by public key. +func (sw *Switch) SetPubKeyFilter(f func(crypto.PubKey) error) { + sw.filterConnByPubKey = f } +//------------------------------------------------------------------------------------ + func (sw *Switch) listenerRoutine(l Listener) { for { inConn, ok := <-l.Connections() @@ -525,7 +446,7 @@ func (sw *Switch) listenerRoutine(l Listener) { } // New inbound connection! - err := sw.addPeerWithConnectionAndConfig(inConn, sw.peerConfig) + err := sw.addInboundPeerWithConfig(inConn, sw.peerConfig) if err != nil { sw.Logger.Info("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "err", err) continue @@ -539,119 +460,82 @@ func (sw *Switch) listenerRoutine(l Listener) { // cleanup } -//------------------------------------------------------------------ -// Connects switches via arbitrary net.Conn. Used for testing. - -// MakeConnectedSwitches returns n switches, connected according to the connect func. -// If connect==Connect2Switches, the switches will be fully connected. -// initSwitch defines how the i'th switch should be initialized (ie. with what reactors). -// NOTE: panics if any switch fails to start. -func MakeConnectedSwitches(cfg *cfg.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { - switches := make([]*Switch, n) - for i := 0; i < n; i++ { - switches[i] = makeSwitch(cfg, i, "testing", "123.123.123", initSwitch) +func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) error { + peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) + if err != nil { + if err := conn.Close(); err != nil { + sw.Logger.Error("Error closing connection", "err", err) + } + return err + } + peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) + if err = sw.addPeer(peer); err != nil { + peer.CloseConn() + return err } - if err := StartSwitches(switches); err != nil { - panic(err) + return nil +} + +// addPeer checks the given peer's validity, performs a handshake, and adds the +// peer to the switch and to all registered reactors. +// We already have an authenticated SecretConnection with the peer. +// NOTE: This performs a blocking handshake before the peer is added. +// NOTE: If error is returned, caller is responsible for calling peer.CloseConn() +func (sw *Switch) addPeer(peer *peer) error { + // Avoid self + if sw.nodeKey.ID() == peer.ID() { + return ErrSwitchConnectToSelf } - for i := 0; i < n; i++ { - for j := i + 1; j < n; j++ { - connect(switches, i, j) - } + // Filter peer against white list + if err := sw.FilterConnByAddr(peer.Addr()); err != nil { + return err + } + if err := sw.FilterConnByPubKey(peer.PubKey()); err != nil { + return err } - return switches -} + // Exchange NodeInfo with the peer + if err := peer.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.peerConfig.HandshakeTimeout*time.Second)); err != nil { + return err + } -// Connect2Switches will connect switches i and j via net.Pipe(). -// Blocks until a connection is established. -// NOTE: caller ensures i and j are within bounds. -func Connect2Switches(switches []*Switch, i, j int) { - switchI := switches[i] - switchJ := switches[j] - c1, c2 := netPipe() - doneCh := make(chan struct{}) - go func() { - err := switchI.addPeerWithConnection(c1) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - go func() { - err := switchJ.addPeerWithConnection(c2) - if err != nil { - panic(err) - } - doneCh <- struct{}{} - }() - <-doneCh - <-doneCh -} + // Avoid duplicate + if sw.peers.Has(peer.ID()) { + return ErrSwitchDuplicatePeer -// StartSwitches calls sw.Start() for each given switch. -// It returns the first encountered error. -func StartSwitches(switches []*Switch) error { - for _, s := range switches { - err := s.Start() // start switch and reactors - if err != nil { - return err - } } - return nil -} -func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { - // new switch, add reactors - // TODO: let the config be passed in? - nodeKey := &NodeKey{ - PrivKey: crypto.GenPrivKeyEd25519().Wrap(), + // Check version, chain id + if err := sw.nodeInfo.CompatibleWith(peer.NodeInfo()); err != nil { + return err } - s := initSwitch(i, NewSwitch(cfg)) - s.SetNodeInfo(&NodeInfo{ - PubKey: nodeKey.PubKey(), - Moniker: cmn.Fmt("switch%d", i), - Network: network, - Version: version, - ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), - }) - s.SetNodeKey(nodeKey) - s.SetLogger(log.TestingLogger()) - return s -} -func (sw *Switch) addPeerWithConnection(conn net.Conn) error { - peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) - if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } - return err + // Start peer + if sw.IsRunning() { + sw.startInitPeer(peer) } - peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) - if err = sw.addPeer(peer); err != nil { - peer.CloseConn() + + // Add the peer to .peers. + // We start it first so that a peer in the list is safe to Stop. + // It should not err since we already checked peers.Has(). + if err := sw.peers.Add(peer); err != nil { return err } + sw.Logger.Info("Added peer", "peer", peer) return nil } -func (sw *Switch) addPeerWithConnectionAndConfig(conn net.Conn, config *PeerConfig) error { - peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) +func (sw *Switch) startInitPeer(peer *peer) { + err := peer.Start() // spawn send/recv routines if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } - return err - } - peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) - if err = sw.addPeer(peer); err != nil { - peer.CloseConn() - return err + // Should never happen + sw.Logger.Error("Error starting peer", "peer", peer, "err", err) } - return nil + for _, reactor := range sw.reactors { + reactor.AddPeer(peer) + } } diff --git a/p2p/test_util.go b/p2p/test_util.go new file mode 100644 index 000000000..9c6892f16 --- /dev/null +++ b/p2p/test_util.go @@ -0,0 +1,111 @@ +package p2p + +import ( + "math/rand" + "net" + + crypto "github.com/tendermint/go-crypto" + cfg "github.com/tendermint/tendermint/config" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" +) + +//------------------------------------------------------------------ +// Connects switches via arbitrary net.Conn. Used for testing. + +// MakeConnectedSwitches returns n switches, connected according to the connect func. +// If connect==Connect2Switches, the switches will be fully connected. +// initSwitch defines how the i'th switch should be initialized (ie. with what reactors). +// NOTE: panics if any switch fails to start. +func MakeConnectedSwitches(cfg *cfg.P2PConfig, n int, initSwitch func(int, *Switch) *Switch, connect func([]*Switch, int, int)) []*Switch { + switches := make([]*Switch, n) + for i := 0; i < n; i++ { + switches[i] = makeSwitch(cfg, i, "testing", "123.123.123", initSwitch) + } + + if err := StartSwitches(switches); err != nil { + panic(err) + } + + for i := 0; i < n; i++ { + for j := i + 1; j < n; j++ { + connect(switches, i, j) + } + } + + return switches +} + +// Connect2Switches will connect switches i and j via net.Pipe(). +// Blocks until a connection is established. +// NOTE: caller ensures i and j are within bounds. +func Connect2Switches(switches []*Switch, i, j int) { + switchI := switches[i] + switchJ := switches[j] + c1, c2 := netPipe() + doneCh := make(chan struct{}) + go func() { + err := switchI.addPeerWithConnection(c1) + if err != nil { + panic(err) + } + doneCh <- struct{}{} + }() + go func() { + err := switchJ.addPeerWithConnection(c2) + if err != nil { + panic(err) + } + doneCh <- struct{}{} + }() + <-doneCh + <-doneCh +} + +func (sw *Switch) addPeerWithConnection(conn net.Conn) error { + peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) + if err != nil { + if err := conn.Close(); err != nil { + sw.Logger.Error("Error closing connection", "err", err) + } + return err + } + peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) + if err = sw.addPeer(peer); err != nil { + peer.CloseConn() + return err + } + + return nil +} + +// StartSwitches calls sw.Start() for each given switch. +// It returns the first encountered error. +func StartSwitches(switches []*Switch) error { + for _, s := range switches { + err := s.Start() // start switch and reactors + if err != nil { + return err + } + } + return nil +} + +func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch { + // new switch, add reactors + // TODO: let the config be passed in? + nodeKey := &NodeKey{ + PrivKey: crypto.GenPrivKeyEd25519().Wrap(), + } + s := initSwitch(i, NewSwitch(cfg)) + s.SetNodeInfo(&NodeInfo{ + PubKey: nodeKey.PubKey(), + Moniker: cmn.Fmt("switch%d", i), + Network: network, + Version: version, + ListenAddr: cmn.Fmt("%v:%v", network, rand.Intn(64512)+1023), + }) + s.SetNodeKey(nodeKey) + s.SetLogger(log.TestingLogger()) + return s +} diff --git a/p2p/util.go b/p2p/util.go deleted file mode 100644 index a4c3ad58b..000000000 --- a/p2p/util.go +++ /dev/null @@ -1,15 +0,0 @@ -package p2p - -import ( - "crypto/sha256" -) - -// doubleSha256 calculates sha256(sha256(b)) and returns the resulting bytes. -func doubleSha256(b []byte) []byte { - hasher := sha256.New() - hasher.Write(b) // nolint: errcheck, gas - sum := hasher.Sum(nil) - hasher.Reset() - hasher.Write(sum) // nolint: errcheck, gas - return hasher.Sum(nil) -} From 08f84cd712e02fb4c81094e50d2c88112d571a10 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 13 Jan 2018 23:48:41 -0500 Subject: [PATCH 23/32] a little more moving around --- node/node.go | 2 +- p2p/peer.go | 10 ++---- p2p/pex_reactor.go | 4 +-- p2p/switch.go | 86 +++++++++++++++++++++++++--------------------- p2p/types.go | 26 +++++++++++--- 5 files changed, 72 insertions(+), 56 deletions(-) diff --git a/node/node.go b/node/node.go index 5535b1e16..2990ba9d7 100644 --- a/node/node.go +++ b/node/node.go @@ -544,9 +544,9 @@ func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { } nodeInfo := &p2p.NodeInfo{ PubKey: pubKey, - Moniker: n.config.Moniker, Network: n.genesisDoc.ChainID, Version: version.Version, + Moniker: n.config.Moniker, Other: []string{ cmn.Fmt("wire_version=%v", wire.Version), cmn.Fmt("p2p_version=%v", p2p.Version), diff --git a/p2p/peer.go b/p2p/peer.go index 1f5192d50..9e434b975 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -195,19 +195,13 @@ func (p *peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) er return errors.Wrap(err2, "Error during handshake/read") } - if p.config.AuthEnc { - // Check that the professed PubKey matches the sconn's. - if !peerNodeInfo.PubKey.Equals(p.PubKey().Wrap()) { - return fmt.Errorf("Ignoring connection with unmatching pubkey: %v vs %v", - peerNodeInfo.PubKey, p.PubKey()) - } - } - // Remove deadline if err := p.conn.SetDeadline(time.Time{}); err != nil { return errors.Wrap(err, "Error removing deadline") } + // TODO: fix the peerNodeInfo.ListenAddr + p.nodeInfo = peerNodeInfo return nil } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 5aa63db71..1903a460f 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -115,7 +115,8 @@ func (r *PEXReactor) AddPeer(p Peer) { r.RequestPEX(p) } } else { - // For inbound connections, the peer is its own source + // For inbound connections, the peer is its own source, + // and its NodeInfo has already been validated addr := p.NodeInfo().NetAddress() r.book.AddAddress(addr, addr) } @@ -130,7 +131,6 @@ func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) { // Receive implements Reactor by handling incoming PEX messages. func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { srcAddr := src.NodeInfo().NetAddress() - r.IncrementMsgCountForPeer(srcAddr.ID) if r.ReachedMaxMsgCountForPeer(srcAddr.ID) { r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddr) diff --git a/p2p/switch.go b/p2p/switch.go index 4ece1a58c..05edb8c19 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -359,38 +359,12 @@ func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent return nil } -// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects successfully. +// DialPeerWithAddress dials the given peer and runs sw.addPeer if it connects and authenticates successfully. // If `persistent == true`, the switch will always try to reconnect to this peer if the connection ever fails. func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (Peer, error) { sw.dialing.Set(string(addr.ID), addr) defer sw.dialing.Delete(string(addr.ID)) - - sw.Logger.Info("Dialing peer", "address", addr) - peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, sw.peerConfig) - if err != nil { - sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) - return nil, err - } - peer.SetLogger(sw.Logger.With("peer", addr)) - - // authenticate peer - if addr.ID == "" { - peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) - } else if addr.ID != peer.ID() { - return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) - } - - if persistent { - peer.makePersistent() - } - err = sw.addPeer(peer) - if err != nil { - sw.Logger.Error("Failed to add peer", "address", addr, "err", err) - peer.CloseConn() - return nil, err - } - sw.Logger.Info("Dialed and added peer", "address", addr, "peer", peer) - return peer, nil + return sw.addOutboundPeerWithConfig(addr, sw.peerConfig, persistent) } // sleep for interval plus some random amount of ms on [0, dialRandomizerIntervalMilliseconds] @@ -451,10 +425,6 @@ func (sw *Switch) listenerRoutine(l Listener) { sw.Logger.Info("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "err", err) continue } - - // NOTE: We don't yet have the listening port of the - // remote (if they have a listener at all). - // The peerHandshake will handle that. } // cleanup @@ -477,9 +447,40 @@ func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) er return nil } -// addPeer checks the given peer's validity, performs a handshake, and adds the -// peer to the switch and to all registered reactors. -// We already have an authenticated SecretConnection with the peer. +// dial the peer; make secret connection; authenticate against the dialed ID; +// add the peer. +func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) (Peer, error) { + sw.Logger.Info("Dialing peer", "address", addr) + peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) + if err != nil { + sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) + return nil, err + } + peer.SetLogger(sw.Logger.With("peer", addr)) + + // authenticate peer + if addr.ID == "" { + peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) + } else if addr.ID != peer.ID() { + return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) + } + + if persistent { + peer.makePersistent() + } + err = sw.addPeer(peer) + if err != nil { + sw.Logger.Error("Failed to add peer", "address", addr, "err", err) + peer.CloseConn() + return nil, err + } + sw.Logger.Info("Dialed and added peer", "address", addr, "peer", peer) + return peer, nil +} + +// addPeer performs the Tendermint P2P handshake with a peer +// that already has a SecretConnection. If all goes well, +// it starts the peer and adds it to the switch. // NOTE: This performs a blocking handshake before the peer is added. // NOTE: If error is returned, caller is responsible for calling peer.CloseConn() func (sw *Switch) addPeer(peer *peer) error { @@ -488,6 +489,12 @@ func (sw *Switch) addPeer(peer *peer) error { return ErrSwitchConnectToSelf } + // Avoid duplicate + if sw.peers.Has(peer.ID()) { + return ErrSwitchDuplicatePeer + + } + // Filter peer against white list if err := sw.FilterConnByAddr(peer.Addr()); err != nil { return err @@ -501,10 +508,9 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Avoid duplicate - if sw.peers.Has(peer.ID()) { - return ErrSwitchDuplicatePeer - + // Validate the peers nodeInfo against the pubkey + if err := peer.NodeInfo().Validate(peer.PubKey()); err != nil { + return err } // Check version, chain id @@ -512,7 +518,7 @@ func (sw *Switch) addPeer(peer *peer) error { return err } - // Start peer + // All good. Start peer if sw.IsRunning() { sw.startInitPeer(peer) } diff --git a/p2p/types.go b/p2p/types.go index 2aec521b2..d4adb9eea 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -12,14 +12,30 @@ import ( const maxNodeInfoSize = 10240 // 10Kb // NodeInfo is the basic node information exchanged -// between two peers during the Tendermint P2P handshake +// between two peers during the Tendermint P2P handshake. type NodeInfo struct { + // Authenticate PubKey crypto.PubKey `json:"pub_key"` // authenticated pubkey - Moniker string `json:"moniker"` // arbitrary moniker - Network string `json:"network"` // network/chain ID ListenAddr string `json:"listen_addr"` // accepting incoming - Version string `json:"version"` // major.minor.revision - Other []string `json:"other"` // other application specific data + + // Check compatibility + Network string `json:"network"` // network/chain ID + Version string `json:"version"` // major.minor.revision + + // Sanitize + Moniker string `json:"moniker"` // arbitrary moniker + Other []string `json:"other"` // other application specific data +} + +// Validate checks the self-reported NodeInfo is safe. +// It returns an error if the info.PubKey doesn't match the given pubKey. +// TODO: constraints for Moniker/Other? Or is that for the UI ? +func (info *NodeInfo) Validate(pubKey crypto.PubKey) error { + if !info.PubKey.Equals(pubKey) { + return fmt.Errorf("info.PubKey (%v) doesn't match peer.PubKey (%v)", + info.PubKey, pubKey) + } + return nil } // CONTRACT: two nodes are compatible if the major/minor versions match and network match From 8b74a8d6ac945c86a520bb98d415f0e7bec87561 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 00:10:29 -0500 Subject: [PATCH 24/32] NodeInfo not a pointer --- node/node.go | 6 +++--- p2p/peer.go | 27 +++++++++++---------------- p2p/peer_set_test.go | 2 +- p2p/peer_test.go | 4 ++-- p2p/pex_reactor_test.go | 2 +- p2p/switch.go | 13 ++++--------- p2p/test_util.go | 2 +- p2p/types.go | 12 ++++++------ rpc/core/net.go | 2 +- rpc/core/pipe.go | 2 +- rpc/core/types/responses.go | 4 ++-- 11 files changed, 33 insertions(+), 43 deletions(-) diff --git a/node/node.go b/node/node.go index 2990ba9d7..4db7f44d4 100644 --- a/node/node.go +++ b/node/node.go @@ -537,12 +537,12 @@ func (n *Node) ProxyApp() proxy.AppConns { return n.proxyApp } -func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { +func (n *Node) makeNodeInfo(pubKey crypto.PubKey) p2p.NodeInfo { txIndexerStatus := "on" if _, ok := n.txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } - nodeInfo := &p2p.NodeInfo{ + nodeInfo := p2p.NodeInfo{ PubKey: pubKey, Network: n.genesisDoc.ChainID, Version: version.Version, @@ -574,7 +574,7 @@ func (n *Node) makeNodeInfo(pubKey crypto.PubKey) *p2p.NodeInfo { //------------------------------------------------------------------------------ // NodeInfo returns the Node's Info from the Switch. -func (n *Node) NodeInfo() *p2p.NodeInfo { +func (n *Node) NodeInfo() p2p.NodeInfo { return n.sw.NodeInfo() } diff --git a/p2p/peer.go b/p2p/peer.go index 9e434b975..57718b6bf 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -1,7 +1,6 @@ package p2p import ( - "encoding/hex" "fmt" "net" "time" @@ -21,7 +20,7 @@ type Peer interface { ID() ID IsOutbound() bool IsPersistent() bool - NodeInfo() *NodeInfo + NodeInfo() NodeInfo Status() ConnectionStatus Send(byte, interface{}) bool @@ -47,7 +46,7 @@ type peer struct { persistent bool config *PeerConfig - nodeInfo *NodeInfo + nodeInfo NodeInfo Data *cmn.CMap // User data. } @@ -128,7 +127,7 @@ func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[ } } - // Key and NodeInfo are set after Handshake + // NodeInfo is set after Handshake p := &peer{ outbound: outbound, conn: conn, @@ -169,23 +168,23 @@ func (p *peer) IsPersistent() bool { // HandshakeTimeout performs a handshake between a given node and the peer. // NOTE: blocking -func (p *peer) HandshakeTimeout(ourNodeInfo *NodeInfo, timeout time.Duration) error { +func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) error { // Set deadline for handshake so we don't block forever on conn.ReadFull if err := p.conn.SetDeadline(time.Now().Add(timeout)); err != nil { return errors.Wrap(err, "Error setting deadline") } - var peerNodeInfo = new(NodeInfo) + var peerNodeInfo NodeInfo var err1 error var err2 error cmn.Parallel( func() { var n int - wire.WriteBinary(ourNodeInfo, p.conn, &n, &err1) + wire.WriteBinary(&ourNodeInfo, p.conn, &n, &err1) }, func() { var n int - wire.ReadBinary(peerNodeInfo, p.conn, maxNodeInfoSize, &n, &err2) + wire.ReadBinary(&peerNodeInfo, p.conn, maxNodeInfoSize, &n, &err2) p.Logger.Info("Peer handshake", "peerNodeInfo", peerNodeInfo) }) if err1 != nil { @@ -213,7 +212,7 @@ func (p *peer) Addr() net.Addr { // PubKey returns peer's public key. func (p *peer) PubKey() crypto.PubKey { - if p.NodeInfo() != nil { + if !p.nodeInfo.PubKey.Empty() { return p.nodeInfo.PubKey } else if p.config.AuthEnc { return p.conn.(*SecretConnection).RemotePubKey() @@ -300,16 +299,12 @@ func (p *peer) Set(key string, data interface{}) { // ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { - return ID(hex.EncodeToString(p.PubKey().Address())) + return PubKeyToID(p.PubKey()) } // NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() *NodeInfo { - if p.nodeInfo == nil { - return nil - } - n := *p.nodeInfo // copy - return &n +func (p *peer) NodeInfo() NodeInfo { + return p.nodeInfo } // Status returns the peer's ConnectionStatus. diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index e50bb384b..e906eb8e7 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -14,7 +14,7 @@ import ( // Returns an empty dummy peer func randPeer() *peer { return &peer{ - nodeInfo: &NodeInfo{ + nodeInfo: NodeInfo{ ListenAddr: cmn.Fmt("%v.%v.%v.%v:46656", rand.Int()%256, rand.Int()%256, rand.Int()%256, rand.Int()%256), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, diff --git a/p2p/peer_test.go b/p2p/peer_test.go index aafe8d7a9..f4a5363b5 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -90,7 +90,7 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) if err != nil { return nil, err } - err = p.HandshakeTimeout(&NodeInfo{ + err = p.HandshakeTimeout(NodeInfo{ PubKey: pk.PubKey(), Moniker: "host_peer", Network: "testing", @@ -141,7 +141,7 @@ func (p *remotePeer) accept(l net.Listener) { if err != nil { golog.Fatalf("Failed to create a peer: %+v", err) } - err = peer.HandshakeTimeout(&NodeInfo{ + err = peer.HandshakeTimeout(NodeInfo{ PubKey: p.PrivKey.PubKey(), Moniker: "remote_peer", Network: "testing", diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 296b18867..20c8b823a 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -242,7 +242,7 @@ func createRoutableAddr() (addr string, netAddr *NetAddress) { func createRandomPeer(outbound bool) *peer { addr, netAddr := createRoutableAddr() p := &peer{ - nodeInfo: &NodeInfo{ + nodeInfo: NodeInfo{ ListenAddr: netAddr.String(), PubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), }, diff --git a/p2p/switch.go b/p2p/switch.go index 05edb8c19..0ff64dbf9 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -52,8 +52,8 @@ type Switch struct { reactorsByCh map[byte]Reactor peers *PeerSet dialing *cmn.CMap - nodeInfo *NodeInfo // our node info - nodeKey *NodeKey // our node privkey + nodeInfo NodeInfo // our node info + nodeKey *NodeKey // our node privkey filterConnByAddr func(net.Addr) error filterConnByPubKey func(crypto.PubKey) error @@ -70,7 +70,6 @@ func NewSwitch(config *cfg.P2PConfig) *Switch { reactorsByCh: make(map[byte]Reactor), peers: NewPeerSet(), dialing: cmn.NewCMap(), - nodeInfo: nil, } // Ensure we have a completely undeterministic PRNG. cmd.RandInt64() draws @@ -141,24 +140,20 @@ func (sw *Switch) IsListening() bool { // SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes. // NOTE: Not goroutine safe. -func (sw *Switch) SetNodeInfo(nodeInfo *NodeInfo) { +func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) { sw.nodeInfo = nodeInfo } // NodeInfo returns the switch's NodeInfo. // NOTE: Not goroutine safe. -func (sw *Switch) NodeInfo() *NodeInfo { +func (sw *Switch) NodeInfo() NodeInfo { return sw.nodeInfo } // SetNodeKey sets the switch's private key for authenticated encryption. -// NOTE: Overwrites sw.nodeInfo.PubKey. // NOTE: Not goroutine safe. func (sw *Switch) SetNodeKey(nodeKey *NodeKey) { sw.nodeKey = nodeKey - if sw.nodeInfo != nil { - sw.nodeInfo.PubKey = nodeKey.PubKey() - } } //--------------------------------------------------------------------- diff --git a/p2p/test_util.go b/p2p/test_util.go index 9c6892f16..18167e209 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -98,7 +98,7 @@ func makeSwitch(cfg *cfg.P2PConfig, i int, network, version string, initSwitch f PrivKey: crypto.GenPrivKeyEd25519().Wrap(), } s := initSwitch(i, NewSwitch(cfg)) - s.SetNodeInfo(&NodeInfo{ + s.SetNodeInfo(NodeInfo{ PubKey: nodeKey.PubKey(), Moniker: cmn.Fmt("switch%d", i), Network: network, diff --git a/p2p/types.go b/p2p/types.go index d4adb9eea..d93adc9b6 100644 --- a/p2p/types.go +++ b/p2p/types.go @@ -30,7 +30,7 @@ type NodeInfo struct { // Validate checks the self-reported NodeInfo is safe. // It returns an error if the info.PubKey doesn't match the given pubKey. // TODO: constraints for Moniker/Other? Or is that for the UI ? -func (info *NodeInfo) Validate(pubKey crypto.PubKey) error { +func (info NodeInfo) Validate(pubKey crypto.PubKey) error { if !info.PubKey.Equals(pubKey) { return fmt.Errorf("info.PubKey (%v) doesn't match peer.PubKey (%v)", info.PubKey, pubKey) @@ -39,7 +39,7 @@ func (info *NodeInfo) Validate(pubKey crypto.PubKey) error { } // CONTRACT: two nodes are compatible if the major/minor versions match and network match -func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { +func (info NodeInfo) CompatibleWith(other NodeInfo) error { iMajor, iMinor, _, iErr := splitVersion(info.Version) oMajor, oMinor, _, oErr := splitVersion(other.Version) @@ -71,11 +71,11 @@ func (info *NodeInfo) CompatibleWith(other *NodeInfo) error { return nil } -func (info *NodeInfo) ID() ID { +func (info NodeInfo) ID() ID { return PubKeyToID(info.PubKey) } -func (info *NodeInfo) NetAddress() *NetAddress { +func (info NodeInfo) NetAddress() *NetAddress { id := PubKeyToID(info.PubKey) addr := info.ListenAddr netAddr, err := NewNetAddressString(IDAddressString(id, addr)) @@ -85,12 +85,12 @@ func (info *NodeInfo) NetAddress() *NetAddress { return netAddr } -func (info *NodeInfo) ListenHost() string { +func (info NodeInfo) ListenHost() string { host, _, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas return host } -func (info *NodeInfo) ListenPort() int { +func (info NodeInfo) ListenPort() int { _, port, _ := net.SplitHostPort(info.ListenAddr) // nolint: errcheck, gas port_i, err := strconv.Atoi(port) if err != nil { diff --git a/rpc/core/net.go b/rpc/core/net.go index af52c81cc..14e7389d5 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -41,7 +41,7 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { peers := []ctypes.Peer{} for _, peer := range p2pSwitch.Peers().List() { peers = append(peers, ctypes.Peer{ - NodeInfo: *peer.NodeInfo(), + NodeInfo: peer.NodeInfo(), IsOutbound: peer.IsOutbound(), ConnectionStatus: peer.Status(), }) diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 6d67fd898..301977ac3 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -30,7 +30,7 @@ type P2P interface { Listeners() []p2p.Listener Peers() p2p.IPeerSet NumPeers() (outbound, inbound, dialig int) - NodeInfo() *p2p.NodeInfo + NodeInfo() p2p.NodeInfo IsListening() bool DialPeersAsync(*p2p.AddrBook, []string, bool) error } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index a6bb30682..233b44e93 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -54,7 +54,7 @@ func NewResultCommit(header *types.Header, commit *types.Commit, } type ResultStatus struct { - NodeInfo *p2p.NodeInfo `json:"node_info"` + NodeInfo p2p.NodeInfo `json:"node_info"` PubKey crypto.PubKey `json:"pub_key"` LatestBlockHash data.Bytes `json:"latest_block_hash"` LatestAppHash data.Bytes `json:"latest_app_hash"` @@ -64,7 +64,7 @@ type ResultStatus struct { } func (s *ResultStatus) TxIndexEnabled() bool { - if s == nil || s.NodeInfo == nil { + if s == nil { return false } for _, s := range s.NodeInfo.Other { From f9e4f6eb6ba51d0cac2186bccdd7a9f320fe3c0e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 00:44:16 -0500 Subject: [PATCH 25/32] reorder peer.go methods --- p2p/peer.go | 180 ++++++++++++++++++++++----------------------- p2p/peer_test.go | 4 +- p2p/switch.go | 10 +-- p2p/switch_test.go | 5 +- 4 files changed, 95 insertions(+), 104 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 57718b6bf..596b92168 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -17,10 +17,10 @@ import ( type Peer interface { cmn.Service - ID() ID - IsOutbound() bool - IsPersistent() bool - NodeInfo() NodeInfo + ID() ID // peer's cryptographic ID + IsOutbound() bool // did we dial the peer + IsPersistent() bool // do we redial this peer when we disconnect + NodeInfo() NodeInfo // peer's info Status() ConnectionStatus Send(byte, interface{}) bool @@ -30,9 +30,9 @@ type Peer interface { Get(string) interface{} } -// Peer could be marked as persistent, in which case you can use -// Redial function to reconnect. Note that inbound peers can't be -// made persistent. They should be made persistent on the other end. +//---------------------------------------------------------- + +// peer implements Peer. // // Before using a peer, you will need to perform a handshake on connection. type peer struct { @@ -77,7 +77,7 @@ func DefaultPeerConfig() *PeerConfig { } func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, - onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig) (*peer, error) { + onPeerError func(Peer, interface{}), ourNodePrivKey crypto.PrivKey, config *PeerConfig, persistent bool) (*peer, error) { conn, err := dial(addr, config) if err != nil { @@ -91,6 +91,7 @@ func newOutboundPeer(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs [] } return nil, err } + peer.persistent = persistent return peer, nil } @@ -142,23 +143,41 @@ func newPeerFromConnAndConfig(rawConn net.Conn, outbound bool, reactorsByCh map[ return p, nil } +//--------------------------------------------------- +// Implements cmn.Service + +// SetLogger implements BaseService. func (p *peer) SetLogger(l log.Logger) { p.Logger = l p.mconn.SetLogger(l) } -// CloseConn should be used when the peer was created, but never started. -func (p *peer) CloseConn() { - p.conn.Close() // nolint: errcheck +// OnStart implements BaseService. +func (p *peer) OnStart() error { + if err := p.BaseService.OnStart(); err != nil { + return err + } + err := p.mconn.Start() + return err } -// makePersistent marks the peer as persistent. -func (p *peer) makePersistent() { - if !p.outbound { - panic("inbound peers can't be made persistent") - } +// OnStop implements BaseService. +func (p *peer) OnStop() { + p.BaseService.OnStop() + p.mconn.Stop() // stop everything and close the conn +} + +//--------------------------------------------------- +// Implements Peer - p.persistent = true +// ID returns the peer's ID - the hex encoded hash of its pubkey. +func (p *peer) ID() ID { + return PubKeyToID(p.PubKey()) +} + +// IsOutbound returns true if the connection is outbound, false otherwise. +func (p *peer) IsOutbound() bool { + return p.outbound } // IsPersistent returns true if the peer is persitent, false otherwise. @@ -166,7 +185,56 @@ func (p *peer) IsPersistent() bool { return p.persistent } -// HandshakeTimeout performs a handshake between a given node and the peer. +// NodeInfo returns a copy of the peer's NodeInfo. +func (p *peer) NodeInfo() NodeInfo { + return p.nodeInfo +} + +// Status returns the peer's ConnectionStatus. +func (p *peer) Status() ConnectionStatus { + return p.mconn.Status() +} + +// Send msg to the channel identified by chID byte. Returns false if the send +// queue is full after timeout, specified by MConnection. +func (p *peer) Send(chID byte, msg interface{}) bool { + if !p.IsRunning() { + // see Switch#Broadcast, where we fetch the list of peers and loop over + // them - while we're looping, one peer may be removed and stopped. + return false + } + return p.mconn.Send(chID, msg) +} + +// TrySend msg to the channel identified by chID byte. Immediately returns +// false if the send queue is full. +func (p *peer) TrySend(chID byte, msg interface{}) bool { + if !p.IsRunning() { + return false + } + return p.mconn.TrySend(chID, msg) +} + +// Get the data for a given key. +func (p *peer) Get(key string) interface{} { + return p.Data.Get(key) +} + +// Set sets the data for the given key. +func (p *peer) Set(key string, data interface{}) { + p.Data.Set(key, data) +} + +//--------------------------------------------------- +// methods used by the Switch + +// CloseConn should be called by the Switch if the peer was created but never started. +func (p *peer) CloseConn() { + p.conn.Close() // nolint: errcheck +} + +// HandshakeTimeout performs the Tendermint P2P handshake between a given node and the peer +// by exchanging their NodeInfo. It sets the received nodeInfo on the peer. // NOTE: blocking func (p *peer) HandshakeTimeout(ourNodeInfo NodeInfo, timeout time.Duration) error { // Set deadline for handshake so we don't block forever on conn.ReadFull @@ -220,51 +288,6 @@ func (p *peer) PubKey() crypto.PubKey { panic("Attempt to get peer's PubKey before calling Handshake") } -// OnStart implements BaseService. -func (p *peer) OnStart() error { - if err := p.BaseService.OnStart(); err != nil { - return err - } - err := p.mconn.Start() - return err -} - -// OnStop implements BaseService. -func (p *peer) OnStop() { - p.BaseService.OnStop() - p.mconn.Stop() -} - -// Connection returns underlying MConnection. -func (p *peer) Connection() *MConnection { - return p.mconn -} - -// IsOutbound returns true if the connection is outbound, false otherwise. -func (p *peer) IsOutbound() bool { - return p.outbound -} - -// Send msg to the channel identified by chID byte. Returns false if the send -// queue is full after timeout, specified by MConnection. -func (p *peer) Send(chID byte, msg interface{}) bool { - if !p.IsRunning() { - // see Switch#Broadcast, where we fetch the list of peers and loop over - // them - while we're looping, one peer may be removed and stopped. - return false - } - return p.mconn.Send(chID, msg) -} - -// TrySend msg to the channel identified by chID byte. Immediately returns -// false if the send queue is full. -func (p *peer) TrySend(chID byte, msg interface{}) bool { - if !p.IsRunning() { - return false - } - return p.mconn.TrySend(chID, msg) -} - // CanSend returns true if the send queue is not full, false otherwise. func (p *peer) CanSend(chID byte) bool { if !p.IsRunning() { @@ -282,35 +305,8 @@ func (p *peer) String() string { return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) } -// Equals reports whenever 2 peers are actually represent the same node. -func (p *peer) Equals(other Peer) bool { - return p.ID() == other.ID() -} - -// Get the data for a given key. -func (p *peer) Get(key string) interface{} { - return p.Data.Get(key) -} - -// Set sets the data for the given key. -func (p *peer) Set(key string, data interface{}) { - p.Data.Set(key, data) -} - -// ID returns the peer's ID - the hex encoded hash of its pubkey. -func (p *peer) ID() ID { - return PubKeyToID(p.PubKey()) -} - -// NodeInfo returns a copy of the peer's NodeInfo. -func (p *peer) NodeInfo() NodeInfo { - return p.nodeInfo -} - -// Status returns the peer's ConnectionStatus. -func (p *peer) Status() ConnectionStatus { - return p.mconn.Status() -} +//------------------------------------------------------------------ +// helper funcs func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) { conn, err := addr.DialTimeout(config.DialTimeout * time.Second) diff --git a/p2p/peer_test.go b/p2p/peer_test.go index f4a5363b5..d99fff5e4 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -30,7 +30,7 @@ func TestPeerBasic(t *testing.T) { assert.True(p.IsRunning()) assert.True(p.IsOutbound()) assert.False(p.IsPersistent()) - p.makePersistent() + p.persistent = true assert.True(p.IsPersistent()) assert.Equal(rp.Addr().String(), p.Addr().String()) assert.Equal(rp.PubKey(), p.PubKey()) @@ -86,7 +86,7 @@ func createOutboundPeerAndPerformHandshake(addr *NetAddress, config *PeerConfig) } reactorsByCh := map[byte]Reactor{0x01: NewTestReactor(chDescs, true)} pk := crypto.GenPrivKeyEd25519().Wrap() - p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config) + p, err := newOutboundPeer(addr, reactorsByCh, chDescs, func(p Peer, r interface{}) {}, pk, config, false) if err != nil { return nil, err } diff --git a/p2p/switch.go b/p2p/switch.go index 0ff64dbf9..eaf3c22e5 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -428,9 +428,7 @@ func (sw *Switch) listenerRoutine(l Listener) { func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) error { peer, err := newInboundPeer(conn, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) if err != nil { - if err := conn.Close(); err != nil { - sw.Logger.Error("Error closing connection", "err", err) - } + peer.CloseConn() return err } peer.SetLogger(sw.Logger.With("peer", conn.RemoteAddr())) @@ -446,7 +444,7 @@ func (sw *Switch) addInboundPeerWithConfig(conn net.Conn, config *PeerConfig) er // add the peer. func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig, persistent bool) (Peer, error) { sw.Logger.Info("Dialing peer", "address", addr) - peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config) + peer, err := newOutboundPeer(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, config, persistent) if err != nil { sw.Logger.Error("Failed to dial peer", "address", addr, "err", err) return nil, err @@ -457,12 +455,10 @@ func (sw *Switch) addOutboundPeerWithConfig(addr *NetAddress, config *PeerConfig if addr.ID == "" { peer.Logger.Info("Dialed peer with unknown ID - unable to authenticate", "addr", addr) } else if addr.ID != peer.ID() { + peer.CloseConn() return nil, fmt.Errorf("Failed to authenticate peer %v. Connected to peer with ID %s", addr, peer.ID()) } - if persistent { - peer.makePersistent() - } err = sw.addPeer(peer) if err != nil { sw.Logger.Error("Failed to add peer", "address", addr, "err", err) diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 7d61fa39a..a729698e9 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -236,7 +236,7 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig()) + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig(), false) require.Nil(err) err = sw.addPeer(peer) require.Nil(err) @@ -263,8 +263,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) { rp.Start() defer rp.Stop() - peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig()) - peer.makePersistent() + peer, err := newOutboundPeer(rp.Addr(), sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey, DefaultPeerConfig(), true) require.Nil(err) err = sw.addPeer(peer) require.Nil(err) From 68237911ba03cf16d9d20db2dd95c549311f6b33 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 01:11:50 -0500 Subject: [PATCH 26/32] NetAddress.Same checks ID or DialString --- p2p/netaddress.go | 15 ++++++++++++++- p2p/switch.go | 8 +++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/p2p/netaddress.go b/p2p/netaddress.go index fed5e59d4..333d16e5d 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -127,12 +127,25 @@ func NewNetAddressIPPort(ip net.IP, port uint16) *NetAddress { return na } -// Equals reports whether na and other are the same addresses. +// Equals reports whether na and other are the same addresses, +// including their ID, IP, and Port. func (na *NetAddress) Equals(other interface{}) bool { if o, ok := other.(*NetAddress); ok { return na.String() == o.String() } + return false +} +// Same returns true is na has the same non-empty ID or DialString as other. +func (na *NetAddress) Same(other interface{}) bool { + if o, ok := other.(*NetAddress); ok { + if na.DialString() == o.DialString() { + return true + } + if na.ID != "" && na.ID == o.ID { + return true + } + } return false } diff --git a/p2p/switch.go b/p2p/switch.go index eaf3c22e5..3f026556a 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -318,18 +318,16 @@ func (sw *Switch) IsDialing(id ID) bool { // DialPeersAsync dials a list of peers asynchronously in random order (optionally, making them persistent). func (sw *Switch) DialPeersAsync(addrBook *AddrBook, peers []string, persistent bool) error { netAddrs, errs := NewNetAddressStrings(peers) - // TODO: IDs for _, err := range errs { sw.Logger.Error("Error in peer's address", "err", err) } if addrBook != nil { // add peers to `addrBook` - ourAddrS := sw.nodeInfo.ListenAddr - ourAddr, _ := NewNetAddressString(ourAddrS) + ourAddr := sw.nodeInfo.NetAddress() for _, netAddr := range netAddrs { - // do not add ourselves - if netAddr.Equals(ourAddr) { + // do not add our address or ID + if netAddr.Same(ourAddr) { continue } addrBook.AddAddress(netAddr, ourAddr) From 3368eeb03ec82de933c9be33ad906ab83823e351 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 01:17:43 -0500 Subject: [PATCH 27/32] fix tests --- benchmarks/codec_test.go | 12 ++++-------- blockchain/reactor_test.go | 2 +- rpc/core/types/responses_test.go | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index 209fcd3ba..8ac62a247 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -16,11 +16,10 @@ func BenchmarkEncodeStatusWire(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey() status := &ctypes.ResultStatus{ - NodeInfo: &p2p.NodeInfo{ + NodeInfo: p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, @@ -43,11 +42,10 @@ func BenchmarkEncodeStatusWire(b *testing.B) { func BenchmarkEncodeNodeInfoWire(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey() - nodeInfo := &p2p.NodeInfo{ + nodeInfo := p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, @@ -64,11 +62,10 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) { func BenchmarkEncodeNodeInfoBinary(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey() - nodeInfo := &p2p.NodeInfo{ + nodeInfo := p2p.NodeInfo{ PubKey: pubKey, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, @@ -87,11 +84,10 @@ func BenchmarkEncodeNodeInfoProto(b *testing.B) { b.StopTimer() pubKey := crypto.GenPrivKeyEd25519().PubKey().Unwrap().(crypto.PubKeyEd25519) pubKey2 := &proto.PubKey{Ed25519: &proto.PubKeyEd25519{Bytes: pubKey[:]}} - nodeInfo := &proto.NodeInfo{ + nodeInfo := proto.NodeInfo{ PubKey: pubKey2, Moniker: "SOMENAME", Network: "SOMENAME", - RemoteAddr: "SOMEADDR", ListenAddr: "SOMEADDR", Version: "SOMEVER", Other: []string{"SOMESTRING", "OTHERSTRING"}, diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 5bdd28694..06f6c36c5 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -142,7 +142,7 @@ func (tp *bcrTestPeer) TrySend(chID byte, value interface{}) bool { } func (tp *bcrTestPeer) Send(chID byte, data interface{}) bool { return tp.TrySend(chID, data) } -func (tp *bcrTestPeer) NodeInfo() *p2p.NodeInfo { return nil } +func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.NodeInfo{} } func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } func (tp *bcrTestPeer) ID() p2p.ID { return tp.id } func (tp *bcrTestPeer) IsOutbound() bool { return false } diff --git a/rpc/core/types/responses_test.go b/rpc/core/types/responses_test.go index fa0da3fdf..e410d47ae 100644 --- a/rpc/core/types/responses_test.go +++ b/rpc/core/types/responses_test.go @@ -17,7 +17,7 @@ func TestStatusIndexer(t *testing.T) { status = &ResultStatus{} assert.False(status.TxIndexEnabled()) - status.NodeInfo = &p2p.NodeInfo{} + status.NodeInfo = p2p.NodeInfo{} assert.False(status.TxIndexEnabled()) cases := []struct { From 3df5fd21cd58db0c8a29c88320410059e26b80e9 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 03:22:01 -0500 Subject: [PATCH 28/32] better abuse handling in pex --- p2p/pex_reactor.go | 170 ++++++++++++++++++++-------------------- p2p/pex_reactor_test.go | 102 +++++++++++++++++++++--- 2 files changed, 176 insertions(+), 96 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 1903a460f..63862d1a5 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -7,6 +7,7 @@ import ( "reflect" "time" + "github.com/pkg/errors" wire "github.com/tendermint/go-wire" cmn "github.com/tendermint/tmlibs/common" ) @@ -19,10 +20,6 @@ const ( defaultEnsurePeersPeriod = 30 * time.Second minNumOutboundPeers = 10 maxPexMessageSize = 1048576 // 1MB - - // maximum pex messages one peer can send to us during `msgCountByPeerFlushInterval` - defaultMaxMsgCountByPeer = 1000 - msgCountByPeerFlushInterval = 1 * time.Hour ) // PEXReactor handles PEX (peer exchange) and ensures that an @@ -32,15 +29,8 @@ const ( // // ## Preventing abuse // -// For now, it just limits the number of messages from one peer to -// `defaultMaxMsgCountByPeer` messages per `msgCountByPeerFlushInterval` (1000 -// msg/hour). -// -// NOTE [2017-01-17]: -// Limiting is fine for now. Maybe down the road we want to keep track of the -// quality of peer messages so if peerA keeps telling us about peers we can't -// connect to then maybe we should care less about peerA. But I don't think -// that kind of complexity is priority right now. +// Only accept pexAddrsMsg from peers we sent a corresponding pexRequestMsg too. +// Only accept one pexRequestMsg every ~defaultEnsurePeersPeriod. type PEXReactor struct { BaseReactor @@ -48,9 +38,9 @@ type PEXReactor struct { config *PEXReactorConfig ensurePeersPeriod time.Duration - // tracks message count by peer, so we can prevent abuse - msgCountByPeer *cmn.CMap - maxMsgCountByPeer uint16 + // maps to prevent abuse + requestsSent *cmn.CMap // unanswered send requests + lastReceivedRequests *cmn.CMap // last time peer requested from us } // PEXReactorConfig holds reactor specific configuration data. @@ -63,11 +53,11 @@ type PEXReactorConfig struct { // NewPEXReactor creates new PEX reactor. func NewPEXReactor(b *AddrBook, config *PEXReactorConfig) *PEXReactor { r := &PEXReactor{ - book: b, - config: config, - ensurePeersPeriod: defaultEnsurePeersPeriod, - msgCountByPeer: cmn.NewCMap(), - maxMsgCountByPeer: defaultMaxMsgCountByPeer, + book: b, + config: config, + ensurePeersPeriod: defaultEnsurePeersPeriod, + requestsSent: cmn.NewCMap(), + lastReceivedRequests: cmn.NewCMap(), } r.BaseReactor = *NewBaseReactor("PEXReactor", r) return r @@ -83,7 +73,6 @@ func (r *PEXReactor) OnStart() error { return err } go r.ensurePeersRoutine() - go r.flushMsgCountByPeer() return nil } @@ -108,15 +97,17 @@ func (r *PEXReactor) GetChannels() []*ChannelDescriptor { // or by requesting more addresses (if outbound). func (r *PEXReactor) AddPeer(p Peer) { if p.IsOutbound() { - // For outbound peers, the address is already in the books. - // Either it was added in DialPeersAsync or when we - // received the peer's address in r.Receive + // For outbound peers, the address is already in the books - + // either via DialPeersAsync or r.Receive. + // Ask it for more peers if we need. if r.book.NeedMoreAddrs() { r.RequestPEX(p) } } else { - // For inbound connections, the peer is its own source, - // and its NodeInfo has already been validated + // For inbound peers, the peer is its own source, + // and its NodeInfo has already been validated. + // Let the ensurePeersRoutine handle asking for more + // peers when we need - we don't trust inbound peers as much. addr := p.NodeInfo().NetAddress() r.book.AddAddress(addr, addr) } @@ -124,20 +115,13 @@ func (r *PEXReactor) AddPeer(p Peer) { // RemovePeer implements Reactor. func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) { - // If we aren't keeping track of local temp data for each peer here, then we - // don't have to do anything. + id := string(p.ID()) + r.requestsSent.Delete(id) + r.lastReceivedRequests.Delete(id) } // Receive implements Reactor by handling incoming PEX messages. func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { - srcAddr := src.NodeInfo().NetAddress() - r.IncrementMsgCountForPeer(srcAddr.ID) - if r.ReachedMaxMsgCountForPeer(srcAddr.ID) { - r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddr) - // TODO remove src from peers? - return - } - _, msg, err := DecodeMessage(msgBytes) if err != nil { r.Logger.Error("Error decoding message", "err", err) @@ -147,27 +131,81 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { switch msg := msg.(type) { case *pexRequestMessage: - // src requested some peers. - // NOTE: we might send an empty selection + // We received a request for peers from src. + if err := r.receiveRequest(src); err != nil { + r.Switch.StopPeerForError(src, err) + return + } r.SendAddrs(src, r.book.GetSelection()) case *pexAddrsMessage: // We received some peer addresses from src. - // TODO: (We don't want to get spammed with bad peers) - for _, netAddr := range msg.Addrs { - if netAddr != nil { - r.book.AddAddress(netAddr, srcAddr) - } + if err := r.ReceivePEX(msg.Addrs, src); err != nil { + r.Switch.StopPeerForError(src, err) + return } default: r.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) } } -// RequestPEX asks peer for more addresses. +func (r *PEXReactor) receiveRequest(src Peer) error { + id := string(src.ID()) + v := r.lastReceivedRequests.Get(id) + if v == nil { + // initialize with empty time + lastReceived := time.Time{} + r.lastReceivedRequests.Set(id, lastReceived) + return nil + } + + lastReceived := v.(time.Time) + if lastReceived.Equal(time.Time{}) { + // first time gets a free pass. then we start tracking the time + lastReceived := time.Now() + r.lastReceivedRequests.Set(id, lastReceived) + return nil + } + + now := time.Now() + if now.Sub(lastReceived) < r.ensurePeersPeriod/3 { + return fmt.Errorf("Peer (%v) is sending too many PEX requests. Disconnecting", src.ID()) + } + r.lastReceivedRequests.Set(id, now) + return nil +} + +// RequestPEX asks peer for more addresses if we do not already +// have a request out for this peer. func (r *PEXReactor) RequestPEX(p Peer) { + id := string(p.ID()) + if r.requestsSent.Has(id) { + return + } + r.requestsSent.Set(id, struct{}{}) p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}}) } +// ReceivePEX adds the given addrs to the addrbook if theres an open +// request for this peer and deletes the open request. +// If there's no open request for the src peer, it returns an error. +func (r *PEXReactor) ReceivePEX(addrs []*NetAddress, src Peer) error { + id := string(src.ID()) + + if !r.requestsSent.Has(id) { + return errors.New("Received unsolicited pexAddrsMessage") + } + + r.requestsSent.Delete(id) + + srcAddr := src.NodeInfo().NetAddress() + for _, netAddr := range addrs { + if netAddr != nil { + r.book.AddAddress(netAddr, srcAddr) + } + } + return nil +} + // SendAddrs sends addrs to the peer. func (r *PEXReactor) SendAddrs(p Peer, netAddrs []*NetAddress) { p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: netAddrs}}) @@ -178,41 +216,13 @@ func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) { r.ensurePeersPeriod = d } -// SetMaxMsgCountByPeer sets maximum messages one peer can send to us during 'msgCountByPeerFlushInterval'. -func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) { - r.maxMsgCountByPeer = v -} - -// ReachedMaxMsgCountForPeer returns true if we received too many -// messages from peer with address `addr`. -// NOTE: assumes the value in the CMap is non-nil -func (r *PEXReactor) ReachedMaxMsgCountForPeer(peerID ID) bool { - return r.msgCountByPeer.Get(string(peerID)).(uint16) >= r.maxMsgCountByPeer -} - -// Increment or initialize the msg count for the peer in the CMap -func (r *PEXReactor) IncrementMsgCountForPeer(peerID ID) { - var count uint16 - countI := r.msgCountByPeer.Get(string(peerID)) - if countI != nil { - count = countI.(uint16) - } - count++ - r.msgCountByPeer.Set(string(peerID), count) -} - // Ensures that sufficient peers are connected. (continuous) func (r *PEXReactor) ensurePeersRoutine() { // Randomize when routine starts ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6 time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond) - // fire once immediately. - r.ensurePeers() - - // fire periodically ticker := time.NewTicker(r.ensurePeersPeriod) - for { select { case <-ticker.C: @@ -298,20 +308,6 @@ func (r *PEXReactor) ensurePeers() { } } -func (r *PEXReactor) flushMsgCountByPeer() { - ticker := time.NewTicker(msgCountByPeerFlushInterval) - - for { - select { - case <-ticker.C: - r.msgCountByPeer.Clear() - case <-r.Quit: - ticker.Stop() - return - } - } -} - //----------------------------------------------------------------------------- // Messages diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 20c8b823a..0c1c17330 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -153,9 +153,11 @@ func TestPEXReactorReceive(t *testing.T) { peer := createRandomPeer(false) + // we have to send a request to receive responses + r.RequestPEX(peer) + size := book.Size() - netAddr, _ := NewNetAddressString(peer.NodeInfo().ListenAddr) - addrs := []*NetAddress{netAddr} + addrs := []*NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) r.Receive(PexChannel, peer, msg) assert.Equal(size+1, book.Size()) @@ -164,7 +166,7 @@ func TestPEXReactorReceive(t *testing.T) { r.Receive(PexChannel, peer, msg) } -func TestPEXReactorAbuseFromPeer(t *testing.T) { +func TestPEXReactorRequestMessageAbuse(t *testing.T) { assert, require := assert.New(t), require.New(t) dir, err := ioutil.TempDir("", "pex_reactor") @@ -174,17 +176,66 @@ func TestPEXReactorAbuseFromPeer(t *testing.T) { book.SetLogger(log.TestingLogger()) r := NewPEXReactor(book, &PEXReactorConfig{}) + sw := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { return sw }) + sw.SetLogger(log.TestingLogger()) + sw.AddReactor("PEX", r) + r.SetSwitch(sw) r.SetLogger(log.TestingLogger()) - r.SetMaxMsgCountByPeer(5) - peer := createRandomPeer(false) + peer := newMockPeer() + id := string(peer.ID()) msg := wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}}) - for i := 0; i < 10; i++ { - r.Receive(PexChannel, peer, msg) - } - assert.True(r.ReachedMaxMsgCountForPeer(peer.NodeInfo().ID())) + // first time creates the entry + r.Receive(PexChannel, peer, msg) + assert.True(r.lastReceivedRequests.Has(id)) + + // next time sets the last time value + r.Receive(PexChannel, peer, msg) + assert.True(r.lastReceivedRequests.Has(id)) + + // third time is too many too soon - peer is removed + r.Receive(PexChannel, peer, msg) + assert.False(r.lastReceivedRequests.Has(id)) + assert.False(sw.Peers().Has(peer.ID())) + +} + +func TestPEXReactorAddrsMessageAbuse(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(err) + defer os.RemoveAll(dir) // nolint: errcheck + book := NewAddrBook(dir+"addrbook.json", true) + book.SetLogger(log.TestingLogger()) + + r := NewPEXReactor(book, &PEXReactorConfig{}) + sw := makeSwitch(config, 0, "127.0.0.1", "123.123.123", func(i int, sw *Switch) *Switch { return sw }) + sw.SetLogger(log.TestingLogger()) + sw.AddReactor("PEX", r) + r.SetSwitch(sw) + r.SetLogger(log.TestingLogger()) + + peer := newMockPeer() + + id := string(peer.ID()) + + // request addrs from the peer + r.RequestPEX(peer) + assert.True(r.requestsSent.Has(id)) + + addrs := []*NetAddress{peer.NodeInfo().NetAddress()} + msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) + + // receive some addrs. should clear the request + r.Receive(PexChannel, peer, msg) + assert.False(r.requestsSent.Has(id)) + + // receiving more addrs causes a disconnect + r.Receive(PexChannel, peer, msg) + assert.False(sw.Peers().Has(peer.ID())) } func TestPEXReactorUsesSeedsIfNeeded(t *testing.T) { @@ -252,3 +303,36 @@ func createRandomPeer(outbound bool) *peer { p.SetLogger(log.TestingLogger().With("peer", addr)) return p } + +type mockPeer struct { + *cmn.BaseService + pubKey crypto.PubKey + addr *NetAddress + outbound, persistent bool +} + +func newMockPeer() mockPeer { + _, netAddr := createRoutableAddr() + mp := mockPeer{ + addr: netAddr, + pubKey: crypto.GenPrivKeyEd25519().Wrap().PubKey(), + } + mp.BaseService = cmn.NewBaseService(nil, "MockPeer", mp) + mp.Start() + return mp +} + +func (mp mockPeer) ID() ID { return PubKeyToID(mp.pubKey) } +func (mp mockPeer) IsOutbound() bool { return mp.outbound } +func (mp mockPeer) IsPersistent() bool { return mp.persistent } +func (mp mockPeer) NodeInfo() NodeInfo { + return NodeInfo{ + PubKey: mp.pubKey, + ListenAddr: mp.addr.DialString(), + } +} +func (mp mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } +func (mp mockPeer) Send(byte, interface{}) bool { return false } +func (mp mockPeer) TrySend(byte, interface{}) bool { return false } +func (mp mockPeer) Set(string, interface{}) {} +func (mp mockPeer) Get(string) interface{} { return nil } From 17f7a9b510ae575d343d11c4e8dc1991cceb074e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 03:56:15 -0500 Subject: [PATCH 29/32] improve seed dialing logic --- p2p/pex_reactor.go | 49 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 63862d1a5..fd4e35e00 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -72,6 +72,11 @@ func (r *PEXReactor) OnStart() error { if err != nil && err != cmn.ErrAlreadyStarted { return err } + + if err := r.checkSeeds(); err != nil { + return err + } + go r.ensurePeersRoutine() return nil } @@ -222,6 +227,11 @@ func (r *PEXReactor) ensurePeersRoutine() { ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6 time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond) + // fire once immediately. + // ensures we dial the seeds right away if the book is empty + r.ensurePeers() + + // fire periodically ticker := time.NewTicker(r.ensurePeersPeriod) for { select { @@ -301,10 +311,43 @@ func (r *PEXReactor) ensurePeers() { } } - // If we are not connected to nor dialing anybody, fallback to dialing seeds. + // If we are not connected to nor dialing anybody, fallback to dialing a seed. if numOutPeers+numInPeers+numDialing+len(toDial) == 0 { - r.Logger.Info("No addresses to dial nor connected peers. Will dial seeds", "seeds", r.config.Seeds) - r.Switch.DialPeersAsync(r.book, r.config.Seeds, false) + r.Logger.Info("No addresses to dial nor connected peers. Falling back to seeds") + r.dialSeed() + } +} + +func (r *PEXReactor) checkSeeds() error { + lSeeds := len(r.config.Seeds) + if lSeeds > 0 { + seedAddrs, errs := NewNetAddressStrings(r.config.Seeds) + for _, err := range errs { + if err != nil { + return err + } + } + } +} + +func (r *PEXReactor) dialSeed() error { + lSeeds := len(r.config.Seeds) + if lSeeds > 0 { + seedAddrs, _ := NewNetAddressStrings(r.config.Seeds) + + perm := r.Switch.rng.Perm(lSeeds) + for _, i := range perm { + // dial a random seed + seedAddr := seedAddrs[i] + peer, err := sw.DialPeerWithAddress(seedAddr, false) + if err != nil { + sw.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr) + } else { + sw.Logger.Info("Connected to seed", "peer", peer) + return + } + } + sw.Logger.Error("Couldn't connect to any seeds") } } From fc7915ab4c1e4780d07dc5380bcc0e638ac87b6f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 13:03:57 -0500 Subject: [PATCH 30/32] fixes from review --- p2p/pex_reactor.go | 56 +++++++++++++++++++++++------------------ p2p/pex_reactor_test.go | 9 ++++++- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index fd4e35e00..93fddc111 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -39,8 +39,8 @@ type PEXReactor struct { ensurePeersPeriod time.Duration // maps to prevent abuse - requestsSent *cmn.CMap // unanswered send requests - lastReceivedRequests *cmn.CMap // last time peer requested from us + requestsSent *cmn.CMap // ID->struct{}: unanswered send requests + lastReceivedRequests *cmn.CMap // ID->time.Time: last time peer requested from us } // PEXReactorConfig holds reactor specific configuration data. @@ -73,6 +73,7 @@ func (r *PEXReactor) OnStart() error { return err } + // return err if user provided a bad seed address if err := r.checkSeeds(); err != nil { return err } @@ -166,7 +167,7 @@ func (r *PEXReactor) receiveRequest(src Peer) error { lastReceived := v.(time.Time) if lastReceived.Equal(time.Time{}) { // first time gets a free pass. then we start tracking the time - lastReceived := time.Now() + lastReceived = time.Now() r.lastReceivedRequests.Set(id, lastReceived) return nil } @@ -318,37 +319,42 @@ func (r *PEXReactor) ensurePeers() { } } +// check seed addresses are well formed func (r *PEXReactor) checkSeeds() error { lSeeds := len(r.config.Seeds) - if lSeeds > 0 { - seedAddrs, errs := NewNetAddressStrings(r.config.Seeds) - for _, err := range errs { - if err != nil { - return err - } + if lSeeds == 0 { + return nil + } + _, errs := NewNetAddressStrings(r.config.Seeds) + for _, err := range errs { + if err != nil { + return err } } + return nil } -func (r *PEXReactor) dialSeed() error { +// randomly dial seeds until we connect to one or exhaust them +func (r *PEXReactor) dialSeed() { lSeeds := len(r.config.Seeds) - if lSeeds > 0 { - seedAddrs, _ := NewNetAddressStrings(r.config.Seeds) - - perm := r.Switch.rng.Perm(lSeeds) - for _, i := range perm { - // dial a random seed - seedAddr := seedAddrs[i] - peer, err := sw.DialPeerWithAddress(seedAddr, false) - if err != nil { - sw.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr) - } else { - sw.Logger.Info("Connected to seed", "peer", peer) - return - } + if lSeeds == 0 { + return + } + seedAddrs, _ := NewNetAddressStrings(r.config.Seeds) + + perm := r.Switch.rng.Perm(lSeeds) + for _, i := range perm { + // dial a random seed + seedAddr := seedAddrs[i] + peer, err := r.Switch.DialPeerWithAddress(seedAddr, false) + if err != nil { + r.Switch.Logger.Error("Error dialing seed", "err", err, "seed", seedAddr) + } else { + r.Switch.Logger.Info("Connected to seed", "peer", peer) + return } - sw.Logger.Error("Couldn't connect to any seeds") } + r.Switch.Logger.Error("Couldn't connect to any seeds") } //----------------------------------------------------------------------------- diff --git a/p2p/pex_reactor_test.go b/p2p/pex_reactor_test.go index 0c1c17330..c0681586a 100644 --- a/p2p/pex_reactor_test.go +++ b/p2p/pex_reactor_test.go @@ -183,6 +183,8 @@ func TestPEXReactorRequestMessageAbuse(t *testing.T) { r.SetLogger(log.TestingLogger()) peer := newMockPeer() + sw.peers.Add(peer) + assert.True(sw.Peers().Has(peer.ID())) id := string(peer.ID()) msg := wire.BinaryBytes(struct{ PexMessage }{&pexRequestMessage{}}) @@ -190,16 +192,17 @@ func TestPEXReactorRequestMessageAbuse(t *testing.T) { // first time creates the entry r.Receive(PexChannel, peer, msg) assert.True(r.lastReceivedRequests.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) // next time sets the last time value r.Receive(PexChannel, peer, msg) assert.True(r.lastReceivedRequests.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) // third time is too many too soon - peer is removed r.Receive(PexChannel, peer, msg) assert.False(r.lastReceivedRequests.Has(id)) assert.False(sw.Peers().Has(peer.ID())) - } func TestPEXReactorAddrsMessageAbuse(t *testing.T) { @@ -219,12 +222,15 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { r.SetLogger(log.TestingLogger()) peer := newMockPeer() + sw.peers.Add(peer) + assert.True(sw.Peers().Has(peer.ID())) id := string(peer.ID()) // request addrs from the peer r.RequestPEX(peer) assert.True(r.requestsSent.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) addrs := []*NetAddress{peer.NodeInfo().NetAddress()} msg := wire.BinaryBytes(struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}}) @@ -232,6 +238,7 @@ func TestPEXReactorAddrsMessageAbuse(t *testing.T) { // receive some addrs. should clear the request r.Receive(PexChannel, peer, msg) assert.False(r.requestsSent.Has(id)) + assert.True(sw.Peers().Has(peer.ID())) // receiving more addrs causes a disconnect r.Receive(PexChannel, peer, msg) From 620c957a444e51bcb6124d985f059b43b0273a37 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 14 Jan 2018 13:24:43 -0500 Subject: [PATCH 31/32] fix test --- node/node.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node/node.go b/node/node.go index 4db7f44d4..6c816a9ef 100644 --- a/node/node.go +++ b/node/node.go @@ -251,8 +251,12 @@ func NewNode(config *cfg.Config, trustMetricStore = trust.NewTrustMetricStore(trustHistoryDB, trust.DefaultConfig()) trustMetricStore.SetLogger(p2pLogger) + var seeds []string + if config.P2P.Seeds != "" { + seeds = strings.Split(config.P2P.Seeds, ",") + } pexReactor := p2p.NewPEXReactor(addrBook, - &p2p.PEXReactorConfig{Seeds: strings.Split(config.P2P.Seeds, ",")}) + &p2p.PEXReactorConfig{Seeds: seeds}) pexReactor.SetLogger(p2pLogger) sw.AddReactor("PEX", pexReactor) } From f57f97c4bd30a35946a0c59cb1157c36e126ceb5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Jan 2018 17:40:12 -0500 Subject: [PATCH 32/32] fix bash tests --- .../pex/{dial_persistent_peers.sh => dial_peers.sh} | 13 +++++++------ test/p2p/pex/test.sh | 4 ++-- ..._dial_persistent_peers.sh => test_dial_peers.sh} | 10 +++++----- 3 files changed, 14 insertions(+), 13 deletions(-) rename test/p2p/pex/{dial_persistent_peers.sh => dial_peers.sh} (57%) rename test/p2p/pex/{test_dial_persistent_peers.sh => test_dial_peers.sh} (78%) diff --git a/test/p2p/pex/dial_persistent_peers.sh b/test/p2p/pex/dial_peers.sh similarity index 57% rename from test/p2p/pex/dial_persistent_peers.sh rename to test/p2p/pex/dial_peers.sh index 95c1d6e9c..ddda7dbe7 100644 --- a/test/p2p/pex/dial_persistent_peers.sh +++ b/test/p2p/pex/dial_peers.sh @@ -19,13 +19,14 @@ for i in `seq 1 $N`; do done set -e -# persistent_peers need quotes -persistent_peers="\"$(test/p2p/ip.sh 1):46656\"" +# peers need quotes +peers="\"$(test/p2p/ip.sh 1):46656\"" for i in `seq 2 $N`; do - persistent_peers="$persistent_peers,\"$(test/p2p/ip.sh $i):46656\"" + peers="$peers,\"$(test/p2p/ip.sh $i):46656\"" done -echo $persistent_peers +echo $peers -echo $persistent_peers +echo $peers IP=$(test/p2p/ip.sh 1) -curl --data-urlencode "persistent_peers=[$persistent_peers]" "$IP:46657/dial_persistent_peers" +curl "$IP:46657/dial_peers?persistent=true&peers=\[$peers\]" + diff --git a/test/p2p/pex/test.sh b/test/p2p/pex/test.sh index 06d40c3ed..ffecd6510 100644 --- a/test/p2p/pex/test.sh +++ b/test/p2p/pex/test.sh @@ -11,5 +11,5 @@ cd "$GOPATH/src/github.com/tendermint/tendermint" echo "Test reconnecting from the address book" bash test/p2p/pex/test_addrbook.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" -echo "Test connecting via /dial_persistent_peers" -bash test/p2p/pex/test_dial_persistent_peers.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" +echo "Test connecting via /dial_peers" +bash test/p2p/pex/test_dial_peers.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$N" "$PROXY_APP" diff --git a/test/p2p/pex/test_dial_persistent_peers.sh b/test/p2p/pex/test_dial_peers.sh similarity index 78% rename from test/p2p/pex/test_dial_persistent_peers.sh rename to test/p2p/pex/test_dial_peers.sh index 7dda62b13..d0b042342 100644 --- a/test/p2p/pex/test_dial_persistent_peers.sh +++ b/test/p2p/pex/test_dial_peers.sh @@ -11,7 +11,7 @@ ID=1 cd $GOPATH/src/github.com/tendermint/tendermint echo "----------------------------------------------------------------------" -echo "Testing full network connection using one /dial_persistent_peers call" +echo "Testing full network connection using one /dial_peers call" echo "(assuming peers are started with pex enabled)" # stop the existing testnet and remove local network @@ -26,11 +26,11 @@ bash test/p2p/local_testnet_start.sh $DOCKER_IMAGE $NETWORK_NAME $N $PROXY_APP $ -# dial persistent_peers from one node -CLIENT_NAME="dial_persistent_peers" -bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_persistent_peers.sh $N" +# dial peers from one node +CLIENT_NAME="dial_peers" +bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/pex/dial_peers.sh $N" # test basic connectivity and consensus # start client container and check the num peers and height for all nodes -CLIENT_NAME="dial_persistent_peers_basic" +CLIENT_NAME="dial_peers_basic" bash test/p2p/client.sh $DOCKER_IMAGE $NETWORK_NAME $CLIENT_NAME "test/p2p/basic/test.sh $N"