p2p: Integrate new Transport
We are swapping the exisiting listener implementation with the newly introduced Transport and its default implementation MultiplexTransport, removing a large chunk of old connection setup and handling scattered over the Peer and Switch code. The Switch requires a Transport now and handles externally passed Peer filters.
This commit is contained in:
parent
be5d68ea4f
commit
bdd01310a0
|
@ -42,7 +42,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe
|
||||||
bcReactor.SetLogger(logger.With("module", "blockchain"))
|
bcReactor.SetLogger(logger.With("module", "blockchain"))
|
||||||
|
|
||||||
// Next: we need to set a switch in order for peers to be added in
|
// Next: we need to set a switch in order for peers to be added in
|
||||||
bcReactor.Switch = p2p.NewSwitch(cfg.DefaultP2PConfig())
|
bcReactor.Switch = p2p.NewSwitch(cfg.DefaultP2PConfig(), nil)
|
||||||
|
|
||||||
// Lastly: let's add some blocks in
|
// Lastly: let's add some blocks in
|
||||||
for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
|
for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
|
||||||
|
|
|
@ -39,7 +39,13 @@ func TestByzantine(t *testing.T) {
|
||||||
switches := make([]*p2p.Switch, N)
|
switches := make([]*p2p.Switch, N)
|
||||||
p2pLogger := logger.With("module", "p2p")
|
p2pLogger := logger.With("module", "p2p")
|
||||||
for i := 0; i < N; i++ {
|
for i := 0; i < N; i++ {
|
||||||
switches[i] = p2p.NewSwitch(config.P2P)
|
switches[i] = p2p.MakeSwitch(
|
||||||
|
config.P2P,
|
||||||
|
i,
|
||||||
|
"foo", "1.0.0",
|
||||||
|
func(i int, sw *p2p.Switch) *p2p.Switch {
|
||||||
|
return sw
|
||||||
|
})
|
||||||
switches[i].SetLogger(p2pLogger.With("validator", i))
|
switches[i].SetLogger(p2pLogger.With("validator", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
270
node/node.go
270
node/node.go
|
@ -126,9 +126,12 @@ type Node struct {
|
||||||
privValidator types.PrivValidator // local node's validator key
|
privValidator types.PrivValidator // local node's validator key
|
||||||
|
|
||||||
// network
|
// network
|
||||||
|
transport *p2p.MultiplexTransport
|
||||||
sw *p2p.Switch // p2p connections
|
sw *p2p.Switch // p2p connections
|
||||||
addrBook pex.AddrBook // known peers
|
addrBook pex.AddrBook // known peers
|
||||||
|
nodeInfo p2p.NodeInfo
|
||||||
nodeKey *p2p.NodeKey // our node privkey
|
nodeKey *p2p.NodeKey // our node privkey
|
||||||
|
isListening bool
|
||||||
|
|
||||||
// services
|
// services
|
||||||
eventBus *types.EventBus // pub/sub for services
|
eventBus *types.EventBus // pub/sub for services
|
||||||
|
@ -301,70 +304,6 @@ func NewNode(config *cfg.Config,
|
||||||
consensusReactor := cs.NewConsensusReactor(consensusState, fastSync)
|
consensusReactor := cs.NewConsensusReactor(consensusState, fastSync)
|
||||||
consensusReactor.SetLogger(consensusLogger)
|
consensusReactor.SetLogger(consensusLogger)
|
||||||
|
|
||||||
p2pLogger := logger.With("module", "p2p")
|
|
||||||
|
|
||||||
sw := p2p.NewSwitch(config.P2P, p2p.WithMetrics(p2pMetrics))
|
|
||||||
sw.SetLogger(p2pLogger)
|
|
||||||
sw.AddReactor("MEMPOOL", mempoolReactor)
|
|
||||||
sw.AddReactor("BLOCKCHAIN", bcReactor)
|
|
||||||
sw.AddReactor("CONSENSUS", consensusReactor)
|
|
||||||
sw.AddReactor("EVIDENCE", evidenceReactor)
|
|
||||||
p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile())
|
|
||||||
|
|
||||||
// Optionally, start the pex reactor
|
|
||||||
//
|
|
||||||
// TODO:
|
|
||||||
//
|
|
||||||
// We need to set Seeds and PersistentPeers on the switch,
|
|
||||||
// since it needs to be able to use these (and their DNS names)
|
|
||||||
// even if the PEX is off. We can include the DNS name in the NetAddress,
|
|
||||||
// but it would still be nice to have a clear list of the current "PersistentPeers"
|
|
||||||
// somewhere that we can return with net_info.
|
|
||||||
//
|
|
||||||
// If PEX is on, it should handle dialing the seeds. Otherwise the switch does it.
|
|
||||||
// Note we currently use the addrBook regardless at least for AddOurAddress
|
|
||||||
addrBook := pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict)
|
|
||||||
addrBook.SetLogger(p2pLogger.With("book", config.P2P.AddrBookFile()))
|
|
||||||
if config.P2P.PexReactor {
|
|
||||||
// TODO persistent peers ? so we can have their DNS addrs saved
|
|
||||||
pexReactor := pex.NewPEXReactor(addrBook,
|
|
||||||
&pex.PEXReactorConfig{
|
|
||||||
Seeds: splitAndTrimEmpty(config.P2P.Seeds, ",", " "),
|
|
||||||
SeedMode: config.P2P.SeedMode,
|
|
||||||
})
|
|
||||||
pexReactor.SetLogger(p2pLogger)
|
|
||||||
sw.AddReactor("PEX", pexReactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
sw.SetAddrBook(addrBook)
|
|
||||||
|
|
||||||
// Filter peers by addr or pubkey with an ABCI query.
|
|
||||||
// If the query return code is OK, add peer.
|
|
||||||
// XXX: Query format subject to change
|
|
||||||
if config.FilterPeers {
|
|
||||||
// NOTE: addr is ip:port
|
|
||||||
sw.SetAddrFilter(func(addr net.Addr) error {
|
|
||||||
resQuery, err := proxyApp.Query().QuerySync(abci.RequestQuery{Path: fmt.Sprintf("/p2p/filter/addr/%s", addr.String())})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resQuery.IsErr() {
|
|
||||||
return fmt.Errorf("Error querying abci app: %v", resQuery)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
sw.SetIDFilter(func(id p2p.ID) error {
|
|
||||||
resQuery, err := proxyApp.Query().QuerySync(abci.RequestQuery{Path: fmt.Sprintf("/p2p/filter/id/%s", id)})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resQuery.IsErr() {
|
|
||||||
return fmt.Errorf("Error querying abci app: %v", resQuery)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
eventBus := types.NewEventBus()
|
eventBus := types.NewEventBus()
|
||||||
eventBus.SetLogger(logger.With("module", "events"))
|
eventBus.SetLogger(logger.With("module", "events"))
|
||||||
|
|
||||||
|
@ -394,6 +333,113 @@ func NewNode(config *cfg.Config,
|
||||||
indexerService := txindex.NewIndexerService(txIndexer, eventBus)
|
indexerService := txindex.NewIndexerService(txIndexer, eventBus)
|
||||||
indexerService.SetLogger(logger.With("module", "txindex"))
|
indexerService.SetLogger(logger.With("module", "txindex"))
|
||||||
|
|
||||||
|
var (
|
||||||
|
p2pLogger = logger.With("module", "p2p")
|
||||||
|
nodeInfo = makeNodeInfo(config, nodeKey.ID(), txIndexer, genDoc.ChainID)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup Transport.
|
||||||
|
var (
|
||||||
|
transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey)
|
||||||
|
connFilters = []p2p.ConnFilterFunc{}
|
||||||
|
peerFilters = []p2p.PeerFilterFunc{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if !config.P2P.AllowDuplicateIP {
|
||||||
|
connFilters = append(connFilters, p2p.ConnDuplicateIPFilter())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter peers by addr or pubkey with an ABCI query.
|
||||||
|
// If the query return code is OK, add peer.
|
||||||
|
// XXX: Query format subject to change
|
||||||
|
if config.FilterPeers {
|
||||||
|
connFilters = append(
|
||||||
|
connFilters,
|
||||||
|
// ABCI query for address filtering.
|
||||||
|
func(_ p2p.ConnSet, c net.Conn, _ []net.IP) error {
|
||||||
|
res, err := proxyApp.Query().QuerySync(abci.RequestQuery{
|
||||||
|
Path: fmt.Sprintf("/p2p/filter/addr/%s", c.RemoteAddr().String()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res.IsErr() {
|
||||||
|
return fmt.Errorf("Error querying abci app: %v", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
peerFilters = append(
|
||||||
|
peerFilters,
|
||||||
|
// ABCI query for ID filtering.
|
||||||
|
func(_ p2p.IPeerSet, p p2p.Peer) error {
|
||||||
|
res, err := proxyApp.Query().QuerySync(abci.RequestQuery{
|
||||||
|
Path: fmt.Sprintf("/p2p/filter/id/%s", p.ID()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res.IsErr() {
|
||||||
|
return fmt.Errorf("Error querying abci app: %v", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
p2p.MultiplexTransportConnFilters(connFilters...)(transport)
|
||||||
|
|
||||||
|
// Setup Switch.
|
||||||
|
sw := p2p.NewSwitch(
|
||||||
|
config.P2P,
|
||||||
|
transport,
|
||||||
|
p2p.WithMetrics(p2pMetrics),
|
||||||
|
p2p.SwitchPeerFilters(peerFilters...),
|
||||||
|
)
|
||||||
|
sw.SetLogger(p2pLogger)
|
||||||
|
sw.AddReactor("MEMPOOL", mempoolReactor)
|
||||||
|
sw.AddReactor("BLOCKCHAIN", bcReactor)
|
||||||
|
sw.AddReactor("CONSENSUS", consensusReactor)
|
||||||
|
sw.AddReactor("EVIDENCE", evidenceReactor)
|
||||||
|
sw.SetNodeInfo(nodeInfo)
|
||||||
|
sw.SetNodeKey(nodeKey)
|
||||||
|
|
||||||
|
p2pLogger.Info("P2P Node ID", "ID", nodeKey.ID(), "file", config.NodeKeyFile())
|
||||||
|
|
||||||
|
// Optionally, start the pex reactor
|
||||||
|
//
|
||||||
|
// TODO:
|
||||||
|
//
|
||||||
|
// We need to set Seeds and PersistentPeers on the switch,
|
||||||
|
// since it needs to be able to use these (and their DNS names)
|
||||||
|
// even if the PEX is off. We can include the DNS name in the NetAddress,
|
||||||
|
// but it would still be nice to have a clear list of the current "PersistentPeers"
|
||||||
|
// somewhere that we can return with net_info.
|
||||||
|
//
|
||||||
|
// If PEX is on, it should handle dialing the seeds. Otherwise the switch does it.
|
||||||
|
// Note we currently use the addrBook regardless at least for AddOurAddress
|
||||||
|
addrBook := pex.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict)
|
||||||
|
|
||||||
|
// Add ourselves to addrbook to prevent dialing ourselves
|
||||||
|
addrBook.AddOurAddress(nodeInfo.NetAddress())
|
||||||
|
|
||||||
|
addrBook.SetLogger(p2pLogger.With("book", config.P2P.AddrBookFile()))
|
||||||
|
if config.P2P.PexReactor {
|
||||||
|
// TODO persistent peers ? so we can have their DNS addrs saved
|
||||||
|
pexReactor := pex.NewPEXReactor(addrBook,
|
||||||
|
&pex.PEXReactorConfig{
|
||||||
|
Seeds: splitAndTrimEmpty(config.P2P.Seeds, ",", " "),
|
||||||
|
SeedMode: config.P2P.SeedMode,
|
||||||
|
})
|
||||||
|
pexReactor.SetLogger(p2pLogger)
|
||||||
|
sw.AddReactor("PEX", pexReactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.SetAddrBook(addrBook)
|
||||||
|
|
||||||
// run the profile server
|
// run the profile server
|
||||||
profileHost := config.ProfListenAddress
|
profileHost := config.ProfListenAddress
|
||||||
if profileHost != "" {
|
if profileHost != "" {
|
||||||
|
@ -407,8 +453,10 @@ func NewNode(config *cfg.Config,
|
||||||
genesisDoc: genDoc,
|
genesisDoc: genDoc,
|
||||||
privValidator: privValidator,
|
privValidator: privValidator,
|
||||||
|
|
||||||
|
transport: transport,
|
||||||
sw: sw,
|
sw: sw,
|
||||||
addrBook: addrBook,
|
addrBook: addrBook,
|
||||||
|
nodeInfo: nodeInfo,
|
||||||
nodeKey: nodeKey,
|
nodeKey: nodeKey,
|
||||||
|
|
||||||
stateDB: stateDB,
|
stateDB: stateDB,
|
||||||
|
@ -441,21 +489,6 @@ func (n *Node) OnStart() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create & add listener
|
|
||||||
l := p2p.NewDefaultListener(
|
|
||||||
n.config.P2P.ListenAddress,
|
|
||||||
n.config.P2P.ExternalAddress,
|
|
||||||
n.config.P2P.UPNP,
|
|
||||||
n.Logger.With("module", "p2p"))
|
|
||||||
n.sw.AddListener(l)
|
|
||||||
|
|
||||||
nodeInfo := n.makeNodeInfo(n.nodeKey.ID())
|
|
||||||
n.sw.SetNodeInfo(nodeInfo)
|
|
||||||
n.sw.SetNodeKey(n.nodeKey)
|
|
||||||
|
|
||||||
// Add ourselves to addrbook to prevent dialing ourselves
|
|
||||||
n.addrBook.AddOurAddress(nodeInfo.NetAddress())
|
|
||||||
|
|
||||||
// Add private IDs to addrbook to block those peers being added
|
// Add private IDs to addrbook to block those peers being added
|
||||||
n.addrBook.AddPrivateIDs(splitAndTrimEmpty(n.config.P2P.PrivatePeerIDs, ",", " "))
|
n.addrBook.AddPrivateIDs(splitAndTrimEmpty(n.config.P2P.PrivatePeerIDs, ",", " "))
|
||||||
|
|
||||||
|
@ -474,6 +507,17 @@ func (n *Node) OnStart() error {
|
||||||
n.prometheusSrv = n.startPrometheusServer(n.config.Instrumentation.PrometheusListenAddr)
|
n.prometheusSrv = n.startPrometheusServer(n.config.Instrumentation.PrometheusListenAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the transport.
|
||||||
|
addr, err := p2p.NewNetAddressStringWithOptionalID(n.config.P2P.ListenAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := n.transport.Listen(*addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n.isListening = true
|
||||||
|
|
||||||
// Start the switch (the P2P server).
|
// Start the switch (the P2P server).
|
||||||
err = n.sw.Start()
|
err = n.sw.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -506,6 +550,12 @@ func (n *Node) OnStop() {
|
||||||
// TODO: gracefully disconnect from peers.
|
// TODO: gracefully disconnect from peers.
|
||||||
n.sw.Stop()
|
n.sw.Stop()
|
||||||
|
|
||||||
|
if err := n.transport.Close(); err != nil {
|
||||||
|
n.Logger.Error("Error closing transport", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.isListening = false
|
||||||
|
|
||||||
// finally stop the listeners / external services
|
// finally stop the listeners / external services
|
||||||
for _, l := range n.rpcListeners {
|
for _, l := range n.rpcListeners {
|
||||||
n.Logger.Info("Closing rpc listener", "listener", l)
|
n.Logger.Info("Closing rpc listener", "listener", l)
|
||||||
|
@ -536,13 +586,6 @@ func (n *Node) RunForever() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddListener adds a listener to accept inbound peer connections.
|
|
||||||
// It should be called before starting the Node.
|
|
||||||
// The first listener is the primary listener (in NodeInfo)
|
|
||||||
func (n *Node) AddListener(l p2p.Listener) {
|
|
||||||
n.sw.AddListener(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigureRPC sets all variables in rpccore so they will serve
|
// ConfigureRPC sets all variables in rpccore so they will serve
|
||||||
// rpc calls from this node
|
// rpc calls from this node
|
||||||
func (n *Node) ConfigureRPC() {
|
func (n *Node) ConfigureRPC() {
|
||||||
|
@ -551,7 +594,8 @@ func (n *Node) ConfigureRPC() {
|
||||||
rpccore.SetConsensusState(n.consensusState)
|
rpccore.SetConsensusState(n.consensusState)
|
||||||
rpccore.SetMempool(n.mempoolReactor.Mempool)
|
rpccore.SetMempool(n.mempoolReactor.Mempool)
|
||||||
rpccore.SetEvidencePool(n.evidencePool)
|
rpccore.SetEvidencePool(n.evidencePool)
|
||||||
rpccore.SetSwitch(n.sw)
|
rpccore.SetP2PPeers(n.sw)
|
||||||
|
rpccore.SetP2PTransport(n)
|
||||||
rpccore.SetPubKey(n.privValidator.GetPubKey())
|
rpccore.SetPubKey(n.privValidator.GetPubKey())
|
||||||
rpccore.SetGenesisDoc(n.genesisDoc)
|
rpccore.SetGenesisDoc(n.genesisDoc)
|
||||||
rpccore.SetAddrBook(n.addrBook)
|
rpccore.SetAddrBook(n.addrBook)
|
||||||
|
@ -683,14 +727,36 @@ func (n *Node) ProxyApp() proxy.AppConns {
|
||||||
return n.proxyApp
|
return n.proxyApp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) makeNodeInfo(nodeID p2p.ID) p2p.NodeInfo {
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (n *Node) Listeners() []string {
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("Listener(@%v)", n.config.P2P.ExternalAddress),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) IsListening() bool {
|
||||||
|
return n.isListening
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeInfo returns the Node's Info from the Switch.
|
||||||
|
func (n *Node) NodeInfo() p2p.NodeInfo {
|
||||||
|
return n.nodeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeNodeInfo(
|
||||||
|
config *cfg.Config,
|
||||||
|
nodeID p2p.ID,
|
||||||
|
txIndexer txindex.TxIndexer,
|
||||||
|
chainID string,
|
||||||
|
) p2p.NodeInfo {
|
||||||
txIndexerStatus := "on"
|
txIndexerStatus := "on"
|
||||||
if _, ok := n.txIndexer.(*null.TxIndex); ok {
|
if _, ok := txIndexer.(*null.TxIndex); ok {
|
||||||
txIndexerStatus = "off"
|
txIndexerStatus = "off"
|
||||||
}
|
}
|
||||||
nodeInfo := p2p.NodeInfo{
|
nodeInfo := p2p.NodeInfo{
|
||||||
ID: nodeID,
|
ID: nodeID,
|
||||||
Network: n.genesisDoc.ChainID,
|
Network: chainID,
|
||||||
Version: version.Version,
|
Version: version.Version,
|
||||||
Channels: []byte{
|
Channels: []byte{
|
||||||
bc.BlockchainChannel,
|
bc.BlockchainChannel,
|
||||||
|
@ -698,7 +764,7 @@ func (n *Node) makeNodeInfo(nodeID p2p.ID) p2p.NodeInfo {
|
||||||
mempl.MempoolChannel,
|
mempl.MempoolChannel,
|
||||||
evidence.EvidenceChannel,
|
evidence.EvidenceChannel,
|
||||||
},
|
},
|
||||||
Moniker: n.config.Moniker,
|
Moniker: config.Moniker,
|
||||||
Other: p2p.NodeInfoOther{
|
Other: p2p.NodeInfoOther{
|
||||||
AminoVersion: amino.Version,
|
AminoVersion: amino.Version,
|
||||||
P2PVersion: p2p.Version,
|
P2PVersion: p2p.Version,
|
||||||
|
@ -708,34 +774,26 @@ func (n *Node) makeNodeInfo(nodeID p2p.ID) p2p.NodeInfo {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.config.P2P.PexReactor {
|
if config.P2P.PexReactor {
|
||||||
nodeInfo.Channels = append(nodeInfo.Channels, pex.PexChannel)
|
nodeInfo.Channels = append(nodeInfo.Channels, pex.PexChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcListenAddr := n.config.RPC.ListenAddress
|
rpcListenAddr := config.RPC.ListenAddress
|
||||||
nodeInfo.Other.RPCAddress = rpcListenAddr
|
nodeInfo.Other.RPCAddress = rpcListenAddr
|
||||||
|
|
||||||
if !n.sw.IsListening() {
|
lAddr := config.P2P.ExternalAddress
|
||||||
return nodeInfo
|
|
||||||
|
if lAddr == "" {
|
||||||
|
lAddr = config.P2P.ListenAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
p2pListener := n.sw.Listeners()[0]
|
nodeInfo.ListenAddr = lAddr
|
||||||
p2pHost := p2pListener.ExternalAddressHost()
|
|
||||||
p2pPort := p2pListener.ExternalAddress().Port
|
|
||||||
nodeInfo.ListenAddr = fmt.Sprintf("%v:%v", p2pHost, p2pPort)
|
|
||||||
|
|
||||||
return nodeInfo
|
return nodeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
// NodeInfo returns the Node's Info from the Switch.
|
|
||||||
func (n *Node) NodeInfo() p2p.NodeInfo {
|
|
||||||
return n.sw.NodeInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
genesisDocKey = []byte("genesisDoc")
|
genesisDocKey = []byte("genesisDoc")
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,98 @@ import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrFilterTimeout indicates that a filter operation timed out.
|
||||||
|
type ErrFilterTimeout struct{}
|
||||||
|
|
||||||
|
func (e ErrFilterTimeout) Error() string {
|
||||||
|
return "filter timed out"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRejected indicates that a Peer was rejected carrying additional
|
||||||
|
// information as to the reason.
|
||||||
|
type ErrRejected struct {
|
||||||
|
addr NetAddress
|
||||||
|
conn net.Conn
|
||||||
|
err error
|
||||||
|
id ID
|
||||||
|
isAuthFailure bool
|
||||||
|
isDuplicate bool
|
||||||
|
isFiltered bool
|
||||||
|
isIncompatible bool
|
||||||
|
isNodeInfoInvalid bool
|
||||||
|
isSelf bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the NetAddress for the rejected Peer.
|
||||||
|
func (e ErrRejected) Addr() NetAddress {
|
||||||
|
return e.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrRejected) Error() string {
|
||||||
|
if e.isAuthFailure {
|
||||||
|
return fmt.Sprintf("auth failure: %s", e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.isDuplicate {
|
||||||
|
if e.conn != nil {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"duplicate CONN<%s>: %s",
|
||||||
|
e.conn.RemoteAddr().String(),
|
||||||
|
e.err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if e.id != "" {
|
||||||
|
return fmt.Sprintf("duplicate ID<%v>: %s", e.id, e.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.isFiltered {
|
||||||
|
if e.conn != nil {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"filtered CONN<%s>: %s",
|
||||||
|
e.conn.RemoteAddr().String(),
|
||||||
|
e.err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.id != "" {
|
||||||
|
return fmt.Sprintf("filtered ID<%v>: %s", e.id, e.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.isIncompatible {
|
||||||
|
return fmt.Sprintf("incompatible: %s", e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.isNodeInfoInvalid {
|
||||||
|
return fmt.Sprintf("invalid NodeInfo: %s", e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.isSelf {
|
||||||
|
return fmt.Sprintf("self ID<%v>", e.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s", e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAuthFailure when Peer authentication was unsuccessful.
|
||||||
|
func (e ErrRejected) IsAuthFailure() bool { return e.isAuthFailure }
|
||||||
|
|
||||||
|
// IsDuplicate when Peer ID or IP are present already.
|
||||||
|
func (e ErrRejected) IsDuplicate() bool { return e.isDuplicate }
|
||||||
|
|
||||||
|
// IsFiltered when Peer ID or IP was filtered.
|
||||||
|
func (e ErrRejected) IsFiltered() bool { return e.isFiltered }
|
||||||
|
|
||||||
|
// IsIncompatible when Peer NodeInfo is not compatible with our own.
|
||||||
|
func (e ErrRejected) IsIncompatible() bool { return e.isIncompatible }
|
||||||
|
|
||||||
|
// IsNodeInfoInvalid when the sent NodeInfo is not valid.
|
||||||
|
func (e ErrRejected) IsNodeInfoInvalid() bool { return e.isNodeInfoInvalid }
|
||||||
|
|
||||||
|
// IsSelf when Peer is our own node.
|
||||||
|
func (e ErrRejected) IsSelf() bool { return e.isSelf }
|
||||||
|
|
||||||
// ErrSwitchDuplicatePeerID to be raised when a peer is connecting with a known
|
// ErrSwitchDuplicatePeerID to be raised when a peer is connecting with a known
|
||||||
// ID.
|
// ID.
|
||||||
type ErrSwitchDuplicatePeerID struct {
|
type ErrSwitchDuplicatePeerID struct {
|
||||||
|
@ -47,6 +139,13 @@ func (e ErrSwitchAuthenticationFailure) Error() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrTransportClosed is raised when the Transport has been closed.
|
||||||
|
type ErrTransportClosed struct{}
|
||||||
|
|
||||||
|
func (e ErrTransportClosed) Error() string {
|
||||||
|
return "transport has been closed"
|
||||||
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
|
||||||
type ErrNetAddressNoID struct {
|
type ErrNetAddressNoID struct {
|
||||||
|
|
286
p2p/listener.go
286
p2p/listener.go
|
@ -1,286 +0,0 @@
|
||||||
package p2p
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
|
||||||
"github.com/tendermint/tendermint/p2p/upnp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Listener is a network listener for stream-oriented protocols, providing
|
|
||||||
// convenient methods to get listener's internal and external addresses.
|
|
||||||
// Clients are supposed to read incoming connections from a channel, returned
|
|
||||||
// by Connections() method.
|
|
||||||
type Listener interface {
|
|
||||||
Connections() <-chan net.Conn
|
|
||||||
InternalAddress() *NetAddress
|
|
||||||
ExternalAddress() *NetAddress
|
|
||||||
ExternalAddressHost() string
|
|
||||||
String() string
|
|
||||||
Stop() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultListener is a cmn.Service, running net.Listener underneath.
|
|
||||||
// Optionally, UPnP is used upon calling NewDefaultListener to resolve external
|
|
||||||
// address.
|
|
||||||
type DefaultListener struct {
|
|
||||||
cmn.BaseService
|
|
||||||
|
|
||||||
listener net.Listener
|
|
||||||
intAddr *NetAddress
|
|
||||||
extAddr *NetAddress
|
|
||||||
connections chan net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Listener = (*DefaultListener)(nil)
|
|
||||||
|
|
||||||
const (
|
|
||||||
numBufferedConnections = 10
|
|
||||||
defaultExternalPort = 8770
|
|
||||||
tryListenSeconds = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
func splitHostPort(addr string) (host string, port int) {
|
|
||||||
host, portStr, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
port, err = strconv.Atoi(portStr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return host, port
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultListener creates a new DefaultListener on lAddr, optionally trying
|
|
||||||
// to determine external address using UPnP.
|
|
||||||
func NewDefaultListener(
|
|
||||||
fullListenAddrString string,
|
|
||||||
externalAddrString string,
|
|
||||||
useUPnP bool,
|
|
||||||
logger log.Logger) Listener {
|
|
||||||
|
|
||||||
// Split protocol, address, and port.
|
|
||||||
protocol, lAddr := cmn.ProtocolAndAddress(fullListenAddrString)
|
|
||||||
lAddrIP, lAddrPort := splitHostPort(lAddr)
|
|
||||||
|
|
||||||
// Create listener
|
|
||||||
var listener net.Listener
|
|
||||||
var err error
|
|
||||||
for i := 0; i < tryListenSeconds; i++ {
|
|
||||||
listener, err = net.Listen(protocol, lAddr)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
} else if i < tryListenSeconds-1 {
|
|
||||||
time.Sleep(time.Second * 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Actual listener local IP & port
|
|
||||||
listenerIP, listenerPort := splitHostPort(listener.Addr().String())
|
|
||||||
logger.Info("Local listener", "ip", listenerIP, "port", listenerPort)
|
|
||||||
|
|
||||||
// Determine internal address...
|
|
||||||
var intAddr *NetAddress
|
|
||||||
intAddr, err = NewNetAddressStringWithOptionalID(lAddr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
inAddrAny := lAddrIP == "" || lAddrIP == "0.0.0.0"
|
|
||||||
|
|
||||||
// Determine external address.
|
|
||||||
var extAddr *NetAddress
|
|
||||||
|
|
||||||
if externalAddrString != "" {
|
|
||||||
var err error
|
|
||||||
extAddr, err = NewNetAddressStringWithOptionalID(externalAddrString)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Error in ExternalAddress: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the lAddrIP is INADDR_ANY, try UPnP.
|
|
||||||
if extAddr == nil && useUPnP && inAddrAny {
|
|
||||||
extAddr = getUPNPExternalAddress(lAddrPort, listenerPort, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise just use the local address.
|
|
||||||
if extAddr == nil {
|
|
||||||
defaultToIPv4 := inAddrAny
|
|
||||||
extAddr = getNaiveExternalAddress(defaultToIPv4, listenerPort, false, logger)
|
|
||||||
}
|
|
||||||
if extAddr == nil {
|
|
||||||
panic("Could not determine external address!")
|
|
||||||
}
|
|
||||||
|
|
||||||
dl := &DefaultListener{
|
|
||||||
listener: listener,
|
|
||||||
intAddr: intAddr,
|
|
||||||
extAddr: extAddr,
|
|
||||||
connections: make(chan net.Conn, numBufferedConnections),
|
|
||||||
}
|
|
||||||
dl.BaseService = *cmn.NewBaseService(logger, "DefaultListener", dl)
|
|
||||||
err = dl.Start() // Started upon construction
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Error starting base service", "err", err)
|
|
||||||
}
|
|
||||||
return dl
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnStart implements cmn.Service by spinning a goroutine, listening for new
|
|
||||||
// connections.
|
|
||||||
func (l *DefaultListener) OnStart() error {
|
|
||||||
if err := l.BaseService.OnStart(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go l.listenRoutine()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnStop implements cmn.Service by closing the listener.
|
|
||||||
func (l *DefaultListener) OnStop() {
|
|
||||||
l.BaseService.OnStop()
|
|
||||||
l.listener.Close() // nolint: errcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept connections and pass on the channel.
|
|
||||||
func (l *DefaultListener) listenRoutine() {
|
|
||||||
for {
|
|
||||||
conn, err := l.listener.Accept()
|
|
||||||
|
|
||||||
if !l.IsRunning() {
|
|
||||||
break // Go to cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
// listener wasn't stopped,
|
|
||||||
// yet we encountered an error.
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.connections <- conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
close(l.connections)
|
|
||||||
for range l.connections {
|
|
||||||
// Drain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connections returns a channel of inbound connections.
|
|
||||||
// It gets closed when the listener closes.
|
|
||||||
// It is the callers responsibility to close any connections received
|
|
||||||
// over this channel.
|
|
||||||
func (l *DefaultListener) Connections() <-chan net.Conn {
|
|
||||||
return l.connections
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalAddress returns the internal NetAddress (address used for
|
|
||||||
// listening).
|
|
||||||
func (l *DefaultListener) InternalAddress() *NetAddress {
|
|
||||||
return l.intAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExternalAddress returns the external NetAddress (publicly available,
|
|
||||||
// determined using either UPnP or local resolver).
|
|
||||||
func (l *DefaultListener) ExternalAddress() *NetAddress {
|
|
||||||
return l.extAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExternalAddressHost returns the external NetAddress IP string. If an IP is
|
|
||||||
// IPv6, it's wrapped in brackets ("[2001:db8:1f70::999:de8:7648:6e8]").
|
|
||||||
func (l *DefaultListener) ExternalAddressHost() string {
|
|
||||||
ip := l.ExternalAddress().IP
|
|
||||||
if isIpv6(ip) {
|
|
||||||
// Means it's ipv6, so format it with brackets
|
|
||||||
return "[" + ip.String() + "]"
|
|
||||||
}
|
|
||||||
return ip.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *DefaultListener) String() string {
|
|
||||||
return fmt.Sprintf("Listener(@%v)", l.extAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* external address helpers */
|
|
||||||
|
|
||||||
// UPNP external address discovery & port mapping
|
|
||||||
func getUPNPExternalAddress(externalPort, internalPort int, logger log.Logger) *NetAddress {
|
|
||||||
logger.Info("Getting UPNP external address")
|
|
||||||
nat, err := upnp.Discover()
|
|
||||||
if err != nil {
|
|
||||||
logger.Info("Could not perform UPNP discover", "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ext, err := nat.GetExternalAddress()
|
|
||||||
if err != nil {
|
|
||||||
logger.Info("Could not get UPNP external address", "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UPnP can't seem to get the external port, so let's just be explicit.
|
|
||||||
if externalPort == 0 {
|
|
||||||
externalPort = defaultExternalPort
|
|
||||||
}
|
|
||||||
|
|
||||||
externalPort, err = nat.AddPortMapping("tcp", externalPort, internalPort, "tendermint", 0)
|
|
||||||
if err != nil {
|
|
||||||
logger.Info("Could not add UPNP port mapping", "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Got UPNP external address", "address", ext)
|
|
||||||
return NewNetAddressIPPort(ext, uint16(externalPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIpv6(ip net.IP) bool {
|
|
||||||
v4 := ip.To4()
|
|
||||||
if v4 != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
ipString := ip.String()
|
|
||||||
|
|
||||||
// Extra check just to be sure it's IPv6
|
|
||||||
return (strings.Contains(ipString, ":") && !strings.Contains(ipString, "."))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use syscalls: see issue #712
|
|
||||||
func getNaiveExternalAddress(defaultToIPv4 bool, port int, settleForLocal bool, logger log.Logger) *NetAddress {
|
|
||||||
addrs, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Could not fetch interface addresses: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range addrs {
|
|
||||||
ipnet, ok := a.(*net.IPNet)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if defaultToIPv4 || !isIpv6(ipnet.IP) {
|
|
||||||
v4 := ipnet.IP.To4()
|
|
||||||
if v4 == nil || (!settleForLocal && v4[0] == 127) {
|
|
||||||
// loopback
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if !settleForLocal && ipnet.IP.IsLoopback() {
|
|
||||||
// IPv6, check for loopback
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return NewNetAddressIPPort(ipnet.IP, uint16(port))
|
|
||||||
}
|
|
||||||
|
|
||||||
// try again, but settle for local
|
|
||||||
logger.Info("Node may not be connected to internet. Settling for local address")
|
|
||||||
return getNaiveExternalAddress(defaultToIPv4, port, true, logger)
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
package p2p
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestListener(t *testing.T) {
|
|
||||||
// Create a listener
|
|
||||||
l := NewDefaultListener("tcp://:8001", "", false, log.TestingLogger())
|
|
||||||
|
|
||||||
// Dial the listener
|
|
||||||
lAddr := l.ExternalAddress()
|
|
||||||
connOut, err := lAddr.Dial()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not connect to listener address %v", lAddr)
|
|
||||||
} else {
|
|
||||||
t.Logf("Created a connection to listener address %v", lAddr)
|
|
||||||
}
|
|
||||||
connIn, ok := <-l.Connections()
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Could not get inbound connection from listener")
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := []byte("hi!")
|
|
||||||
go func() {
|
|
||||||
_, err := connIn.Write(msg)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
b := make([]byte, 32)
|
|
||||||
n, err := connOut.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error reading off connection: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b = b[:n]
|
|
||||||
if !bytes.Equal(msg, b) {
|
|
||||||
t.Fatalf("Got %s, expected %s", b, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the server, no longer needed.
|
|
||||||
l.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExternalAddress(t *testing.T) {
|
|
||||||
{
|
|
||||||
// Create a listener with no external addr. Should default
|
|
||||||
// to local ipv4.
|
|
||||||
l := NewDefaultListener("tcp://:8001", "", false, log.TestingLogger())
|
|
||||||
lAddr := l.ExternalAddress().String()
|
|
||||||
_, _, err := net.SplitHostPort(lAddr)
|
|
||||||
require.Nil(t, err)
|
|
||||||
spl := strings.Split(lAddr, ".")
|
|
||||||
require.Equal(t, len(spl), 4)
|
|
||||||
l.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Create a listener with set external ipv4 addr.
|
|
||||||
setExAddr := "8.8.8.8:8080"
|
|
||||||
l := NewDefaultListener("tcp://:8001", setExAddr, false, log.TestingLogger())
|
|
||||||
lAddr := l.ExternalAddress().String()
|
|
||||||
require.Equal(t, lAddr, setExAddr)
|
|
||||||
l.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Invalid external addr causes panic
|
|
||||||
setExAddr := "awrlsckjnal:8080"
|
|
||||||
require.Panics(t, func() { NewDefaultListener("tcp://:8001", setExAddr, false, log.TestingLogger()) })
|
|
||||||
}
|
|
||||||
}
|
|
94
p2p/peer.go
94
p2p/peer.go
|
@ -6,7 +6,6 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
crypto "github.com/tendermint/tendermint/crypto"
|
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
|
@ -130,87 +129,6 @@ func newPeer(
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOutboundPeerConn(
|
|
||||||
addr *NetAddress,
|
|
||||||
config *config.P2PConfig,
|
|
||||||
persistent bool,
|
|
||||||
ourNodePrivKey crypto.PrivKey,
|
|
||||||
) (peerConn, error) {
|
|
||||||
conn, err := dial(addr, config)
|
|
||||||
if err != nil {
|
|
||||||
return peerConn{}, cmn.ErrorWrap(err, "Error creating peer")
|
|
||||||
}
|
|
||||||
|
|
||||||
pc, err := newPeerConn(conn, config, true, persistent, ourNodePrivKey, addr)
|
|
||||||
if err != nil {
|
|
||||||
if cerr := conn.Close(); cerr != nil {
|
|
||||||
return peerConn{}, cmn.ErrorWrap(err, cerr.Error())
|
|
||||||
}
|
|
||||||
return peerConn{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure dialed ID matches connection ID
|
|
||||||
if addr.ID != pc.ID() {
|
|
||||||
if cerr := conn.Close(); cerr != nil {
|
|
||||||
return peerConn{}, cmn.ErrorWrap(err, cerr.Error())
|
|
||||||
}
|
|
||||||
return peerConn{}, ErrSwitchAuthenticationFailure{addr, pc.ID()}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInboundPeerConn(
|
|
||||||
conn net.Conn,
|
|
||||||
config *config.P2PConfig,
|
|
||||||
ourNodePrivKey crypto.PrivKey,
|
|
||||||
) (peerConn, error) {
|
|
||||||
|
|
||||||
// TODO: issue PoW challenge
|
|
||||||
|
|
||||||
return newPeerConn(conn, config, false, false, ourNodePrivKey, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPeerConn(
|
|
||||||
rawConn net.Conn,
|
|
||||||
cfg *config.P2PConfig,
|
|
||||||
outbound, persistent bool,
|
|
||||||
ourNodePrivKey crypto.PrivKey,
|
|
||||||
originalAddr *NetAddress,
|
|
||||||
) (pc peerConn, err error) {
|
|
||||||
conn := rawConn
|
|
||||||
|
|
||||||
// Fuzz connection
|
|
||||||
if cfg.TestFuzz {
|
|
||||||
// so we have time to do peer handshakes and get set up
|
|
||||||
conn = FuzzConnAfterFromConfig(conn, 10*time.Second, cfg.TestFuzzConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set deadline for secret handshake
|
|
||||||
dl := time.Now().Add(cfg.HandshakeTimeout)
|
|
||||||
if err := conn.SetDeadline(dl); err != nil {
|
|
||||||
return pc, cmn.ErrorWrap(
|
|
||||||
err,
|
|
||||||
"Error setting deadline while encrypting connection",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt connection
|
|
||||||
conn, err = tmconn.MakeSecretConnection(conn, ourNodePrivKey)
|
|
||||||
if err != nil {
|
|
||||||
return pc, cmn.ErrorWrap(err, "Error creating peer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only the information we already have
|
|
||||||
return peerConn{
|
|
||||||
config: cfg,
|
|
||||||
outbound: outbound,
|
|
||||||
persistent: persistent,
|
|
||||||
conn: conn,
|
|
||||||
originalAddr: originalAddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------------------
|
//---------------------------------------------------
|
||||||
// Implements cmn.Service
|
// Implements cmn.Service
|
||||||
|
|
||||||
|
@ -399,18 +317,6 @@ func (p *peer) String() string {
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
// helper funcs
|
// helper funcs
|
||||||
|
|
||||||
func dial(addr *NetAddress, cfg *config.P2PConfig) (net.Conn, error) {
|
|
||||||
if cfg.TestDialFail {
|
|
||||||
return nil, fmt.Errorf("dial err (peerConfig.DialFail == true)")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := addr.DialTimeout(cfg.DialTimeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createMConnection(
|
func createMConnection(
|
||||||
conn net.Conn,
|
conn net.Conn,
|
||||||
p *peer,
|
p *peer,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package p2p
|
package p2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
golog "log"
|
golog "log"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -76,7 +77,7 @@ func createOutboundPeerAndPerformHandshake(
|
||||||
}
|
}
|
||||||
reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)}
|
reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)}
|
||||||
pk := ed25519.GenPrivKey()
|
pk := ed25519.GenPrivKey()
|
||||||
pc, err := newOutboundPeerConn(addr, config, false, pk)
|
pc, err := testOutboundPeerConn(addr, config, false, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -96,6 +97,48 @@ func createOutboundPeerAndPerformHandshake(
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDial(addr *NetAddress, cfg *config.P2PConfig) (net.Conn, error) {
|
||||||
|
if cfg.TestDialFail {
|
||||||
|
return nil, fmt.Errorf("dial err (peerConfig.DialFail == true)")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := addr.DialTimeout(cfg.DialTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOutboundPeerConn(
|
||||||
|
addr *NetAddress,
|
||||||
|
config *config.P2PConfig,
|
||||||
|
persistent bool,
|
||||||
|
ourNodePrivKey crypto.PrivKey,
|
||||||
|
) (peerConn, error) {
|
||||||
|
conn, err := testDial(addr, config)
|
||||||
|
if err != nil {
|
||||||
|
return peerConn{}, cmn.ErrorWrap(err, "Error creating peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
pc, err := testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr)
|
||||||
|
if err != nil {
|
||||||
|
if cerr := conn.Close(); cerr != nil {
|
||||||
|
return peerConn{}, cmn.ErrorWrap(err, cerr.Error())
|
||||||
|
}
|
||||||
|
return peerConn{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure dialed ID matches connection ID
|
||||||
|
if addr.ID != pc.ID() {
|
||||||
|
if cerr := conn.Close(); cerr != nil {
|
||||||
|
return peerConn{}, cmn.ErrorWrap(err, cerr.Error())
|
||||||
|
}
|
||||||
|
return peerConn{}, ErrSwitchAuthenticationFailure{addr, pc.ID()}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
|
||||||
type remotePeer struct {
|
type remotePeer struct {
|
||||||
PrivKey crypto.PrivKey
|
PrivKey crypto.PrivKey
|
||||||
Config *config.P2PConfig
|
Config *config.P2PConfig
|
||||||
|
@ -143,19 +186,19 @@ func (rp *remotePeer) accept(l net.Listener) {
|
||||||
golog.Fatalf("Failed to accept conn: %+v", err)
|
golog.Fatalf("Failed to accept conn: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pc, err := newInboundPeerConn(conn, rp.Config, rp.PrivKey)
|
pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
golog.Fatalf("Failed to create a peer: %+v", err)
|
golog.Fatalf("Failed to create a peer: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = pc.HandshakeTimeout(NodeInfo{
|
_, err = handshake(pc.conn, time.Second, NodeInfo{
|
||||||
ID: rp.Addr().ID,
|
ID: rp.Addr().ID,
|
||||||
Moniker: "remote_peer",
|
Moniker: "remote_peer",
|
||||||
Network: "testing",
|
Network: "testing",
|
||||||
Version: "123.123.123",
|
Version: "123.123.123",
|
||||||
ListenAddr: l.Addr().String(),
|
ListenAddr: l.Addr().String(),
|
||||||
Channels: rp.channels,
|
Channels: rp.channels,
|
||||||
}, 1*time.Second)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
golog.Fatalf("Failed to perform handshake: %+v", err)
|
golog.Fatalf("Failed to perform handshake: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,9 +109,7 @@ func TestPEXReactorRunning(t *testing.T) {
|
||||||
addOtherNodeAddrToAddrBook(1, 0)
|
addOtherNodeAddrToAddrBook(1, 0)
|
||||||
addOtherNodeAddrToAddrBook(2, 1)
|
addOtherNodeAddrToAddrBook(2, 1)
|
||||||
|
|
||||||
for i, sw := range switches {
|
for _, sw := range switches {
|
||||||
sw.AddListener(p2p.NewDefaultListener("tcp://"+sw.NodeInfo().ListenAddr, "", false, logger.With("pex", i)))
|
|
||||||
|
|
||||||
err := sw.Start() // start switch and reactors
|
err := sw.Start() // start switch and reactors
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
@ -474,9 +472,6 @@ func testCreatePeerWithConfig(dir string, id int, config *PEXReactorConfig) *p2p
|
||||||
return sw
|
return sw
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
peer.AddListener(
|
|
||||||
p2p.NewDefaultListener("tcp://"+peer.NodeInfo().ListenAddr, "", false, log.TestingLogger()),
|
|
||||||
)
|
|
||||||
return peer
|
return peer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,9 +505,6 @@ func testCreateSeed(dir string, id int, knownAddrs, srcAddrs []*p2p.NetAddress)
|
||||||
return sw
|
return sw
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
seed.AddListener(
|
|
||||||
p2p.NewDefaultListener("tcp://"+seed.NodeInfo().ListenAddr, "", false, log.TestingLogger()),
|
|
||||||
)
|
|
||||||
return seed
|
return seed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
340
p2p/switch.go
340
p2p/switch.go
|
@ -3,7 +3,6 @@ package p2p
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -42,6 +41,10 @@ type AddrBook interface {
|
||||||
Save()
|
Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeerFilterFunc to be implemented by filter hooks after a new Peer has been
|
||||||
|
// fully setup.
|
||||||
|
type PeerFilterFunc func(IPeerSet, Peer) error
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Switch handles peer connections and exposes an API to receive incoming messages
|
// Switch handles peer connections and exposes an API to receive incoming messages
|
||||||
|
@ -52,7 +55,6 @@ type Switch struct {
|
||||||
cmn.BaseService
|
cmn.BaseService
|
||||||
|
|
||||||
config *config.P2PConfig
|
config *config.P2PConfig
|
||||||
listeners []Listener
|
|
||||||
reactors map[string]Reactor
|
reactors map[string]Reactor
|
||||||
chDescs []*conn.ChannelDescriptor
|
chDescs []*conn.ChannelDescriptor
|
||||||
reactorsByCh map[byte]Reactor
|
reactorsByCh map[byte]Reactor
|
||||||
|
@ -63,8 +65,10 @@ type Switch struct {
|
||||||
nodeKey *NodeKey // our node privkey
|
nodeKey *NodeKey // our node privkey
|
||||||
addrBook AddrBook
|
addrBook AddrBook
|
||||||
|
|
||||||
filterConnByAddr func(net.Addr) error
|
transport Transport
|
||||||
filterConnByID func(ID) error
|
|
||||||
|
filterTimeout time.Duration
|
||||||
|
peerFilters []PeerFilterFunc
|
||||||
|
|
||||||
mConfig conn.MConnConfig
|
mConfig conn.MConnConfig
|
||||||
|
|
||||||
|
@ -77,7 +81,11 @@ type Switch struct {
|
||||||
type SwitchOption func(*Switch)
|
type SwitchOption func(*Switch)
|
||||||
|
|
||||||
// NewSwitch creates a new Switch with the given config.
|
// NewSwitch creates a new Switch with the given config.
|
||||||
func NewSwitch(cfg *config.P2PConfig, options ...SwitchOption) *Switch {
|
func NewSwitch(
|
||||||
|
cfg *config.P2PConfig,
|
||||||
|
transport Transport,
|
||||||
|
options ...SwitchOption,
|
||||||
|
) *Switch {
|
||||||
sw := &Switch{
|
sw := &Switch{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
reactors: make(map[string]Reactor),
|
reactors: make(map[string]Reactor),
|
||||||
|
@ -87,6 +95,8 @@ func NewSwitch(cfg *config.P2PConfig, options ...SwitchOption) *Switch {
|
||||||
dialing: cmn.NewCMap(),
|
dialing: cmn.NewCMap(),
|
||||||
reconnecting: cmn.NewCMap(),
|
reconnecting: cmn.NewCMap(),
|
||||||
metrics: NopMetrics(),
|
metrics: NopMetrics(),
|
||||||
|
transport: transport,
|
||||||
|
filterTimeout: defaultFilterTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we have a completely undeterministic PRNG.
|
// Ensure we have a completely undeterministic PRNG.
|
||||||
|
@ -109,6 +119,16 @@ func NewSwitch(cfg *config.P2PConfig, options ...SwitchOption) *Switch {
|
||||||
return sw
|
return sw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SwitchFilterTimeout sets the timeout used for peer filters.
|
||||||
|
func SwitchFilterTimeout(timeout time.Duration) SwitchOption {
|
||||||
|
return func(sw *Switch) { sw.filterTimeout = timeout }
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchPeerFilters sets the filters for rejection of new peers.
|
||||||
|
func SwitchPeerFilters(filters ...PeerFilterFunc) SwitchOption {
|
||||||
|
return func(sw *Switch) { sw.peerFilters = filters }
|
||||||
|
}
|
||||||
|
|
||||||
// WithMetrics sets the metrics.
|
// WithMetrics sets the metrics.
|
||||||
func WithMetrics(metrics *Metrics) SwitchOption {
|
func WithMetrics(metrics *Metrics) SwitchOption {
|
||||||
return func(sw *Switch) { sw.metrics = metrics }
|
return func(sw *Switch) { sw.metrics = metrics }
|
||||||
|
@ -148,24 +168,6 @@ func (sw *Switch) Reactor(name string) Reactor {
|
||||||
return sw.reactors[name]
|
return sw.reactors[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddListener adds the given listener to the switch for listening to incoming peer connections.
|
|
||||||
// NOTE: Not goroutine safe.
|
|
||||||
func (sw *Switch) AddListener(l Listener) {
|
|
||||||
sw.listeners = append(sw.listeners, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listeners returns the list of listeners the switch listens on.
|
|
||||||
// NOTE: Not goroutine safe.
|
|
||||||
func (sw *Switch) Listeners() []Listener {
|
|
||||||
return sw.listeners
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsListening returns true if the switch has at least one listener.
|
|
||||||
// NOTE: Not goroutine safe.
|
|
||||||
func (sw *Switch) IsListening() bool {
|
|
||||||
return len(sw.listeners) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes.
|
// SetNodeInfo sets the switch's NodeInfo for checking compatibility and handshaking with other nodes.
|
||||||
// NOTE: Not goroutine safe.
|
// NOTE: Not goroutine safe.
|
||||||
func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) {
|
func (sw *Switch) SetNodeInfo(nodeInfo NodeInfo) {
|
||||||
|
@ -187,7 +189,7 @@ func (sw *Switch) SetNodeKey(nodeKey *NodeKey) {
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
// Service start/stop
|
// Service start/stop
|
||||||
|
|
||||||
// OnStart implements BaseService. It starts all the reactors, peers, and listeners.
|
// OnStart implements BaseService. It starts all the reactors and peers.
|
||||||
func (sw *Switch) OnStart() error {
|
func (sw *Switch) OnStart() error {
|
||||||
// Start reactors
|
// Start reactors
|
||||||
for _, reactor := range sw.reactors {
|
for _, reactor := range sw.reactors {
|
||||||
|
@ -196,25 +198,21 @@ func (sw *Switch) OnStart() error {
|
||||||
return cmn.ErrorWrap(err, "failed to start %v", reactor)
|
return cmn.ErrorWrap(err, "failed to start %v", reactor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Start listeners
|
|
||||||
for _, listener := range sw.listeners {
|
// Start accepting Peers.
|
||||||
go sw.listenerRoutine(listener)
|
go sw.acceptRoutine()
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStop implements BaseService. It stops all listeners, peers, and reactors.
|
// OnStop implements BaseService. It stops all peers and reactors.
|
||||||
func (sw *Switch) OnStop() {
|
func (sw *Switch) OnStop() {
|
||||||
// Stop listeners
|
|
||||||
for _, listener := range sw.listeners {
|
|
||||||
listener.Stop()
|
|
||||||
}
|
|
||||||
sw.listeners = nil
|
|
||||||
// Stop peers
|
// Stop peers
|
||||||
for _, peer := range sw.peers.List() {
|
for _, p := range sw.peers.List() {
|
||||||
peer.Stop()
|
p.Stop()
|
||||||
sw.peers.Remove(peer)
|
sw.peers.Remove(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop reactors
|
// Stop reactors
|
||||||
sw.Logger.Debug("Switch: Stopping reactors")
|
sw.Logger.Debug("Switch: Stopping reactors")
|
||||||
for _, reactor := range sw.reactors {
|
for _, reactor := range sw.reactors {
|
||||||
|
@ -459,42 +457,46 @@ func (sw *Switch) IsDialingOrExistingAddress(addr *NetAddress) bool {
|
||||||
(!sw.config.AllowDuplicateIP && sw.peers.HasIP(addr.IP))
|
(!sw.config.AllowDuplicateIP && sw.peers.HasIP(addr.IP))
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
func (sw *Switch) acceptRoutine() {
|
||||||
// Connection filtering
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterConnByID returns an error if connecting to the given peer ID is forbidden.
|
|
||||||
func (sw *Switch) FilterConnByID(id ID) error {
|
|
||||||
if sw.filterConnByID != nil {
|
|
||||||
return sw.filterConnByID(id)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAddrFilter sets the function for filtering connections by address.
|
|
||||||
func (sw *Switch) SetAddrFilter(f func(net.Addr) error) {
|
|
||||||
sw.filterConnByAddr = f
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetIDFilter sets the function for filtering connections by peer ID.
|
|
||||||
func (sw *Switch) SetIDFilter(f func(ID) error) {
|
|
||||||
sw.filterConnByID = f
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func (sw *Switch) listenerRoutine(l Listener) {
|
|
||||||
for {
|
for {
|
||||||
inConn, ok := <-l.Connections()
|
p, err := sw.transport.Accept(peerConfig{
|
||||||
if !ok {
|
chDescs: sw.chDescs,
|
||||||
|
onPeerError: sw.StopPeerForError,
|
||||||
|
reactorsByCh: sw.reactorsByCh,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case ErrRejected:
|
||||||
|
rErr := err.(ErrRejected)
|
||||||
|
|
||||||
|
if rErr.IsSelf() {
|
||||||
|
// Remove the given address from the address book and add to our addresses
|
||||||
|
// to avoid dialing in the future.
|
||||||
|
addr := rErr.Addr()
|
||||||
|
sw.addrBook.RemoveAddress(&addr)
|
||||||
|
sw.addrBook.AddOurAddress(&addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Logger.Info(
|
||||||
|
"Inbound Peer rejected",
|
||||||
|
"err", err,
|
||||||
|
"numPeers", sw.peers.Size(),
|
||||||
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
|
case *ErrTransportClosed:
|
||||||
|
sw.Logger.Error(
|
||||||
|
"Stopped accept routine, as transport is closed",
|
||||||
|
"numPeers", sw.peers.Size(),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
sw.Logger.Error(
|
||||||
|
"Accept on transport errored",
|
||||||
|
"err", err,
|
||||||
|
"numPeers", sw.peers.Size(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,41 +505,25 @@ func (sw *Switch) listenerRoutine(l Listener) {
|
||||||
if in >= sw.config.MaxNumInboundPeers {
|
if in >= sw.config.MaxNumInboundPeers {
|
||||||
sw.Logger.Info(
|
sw.Logger.Info(
|
||||||
"Ignoring inbound connection: already have enough inbound peers",
|
"Ignoring inbound connection: already have enough inbound peers",
|
||||||
"address", inConn.RemoteAddr().String(),
|
"address", p.NodeInfo().NetAddress().String(),
|
||||||
"have", in,
|
"have", in,
|
||||||
"max", sw.config.MaxNumInboundPeers,
|
"max", sw.config.MaxNumInboundPeers,
|
||||||
)
|
)
|
||||||
inConn.Close()
|
|
||||||
|
_ = p.Stop()
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// New inbound connection!
|
if err := sw.addPeer(p); err != nil {
|
||||||
err := sw.addInboundPeerWithConfig(inConn, sw.config)
|
_ = p.Stop()
|
||||||
if err != nil {
|
sw.Logger.Info(
|
||||||
sw.Logger.Info("Ignoring inbound connection: error while adding peer", "address", inConn.RemoteAddr().String(), "err", err)
|
"Ignoring inbound connection: error while adding peer",
|
||||||
continue
|
"err", err,
|
||||||
|
"id", p.ID(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
// closes conn if err is returned
|
|
||||||
func (sw *Switch) addInboundPeerWithConfig(
|
|
||||||
conn net.Conn,
|
|
||||||
config *config.P2PConfig,
|
|
||||||
) error {
|
|
||||||
peerConn, err := newInboundPeerConn(conn, config, sw.nodeKey.PrivKey)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close() // peer is nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = sw.addPeer(peerConn); err != nil {
|
|
||||||
peerConn.CloseConn()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dial the peer; make secret connection; authenticate against the dialed ID;
|
// dial the peer; make secret connection; authenticate against the dialed ID;
|
||||||
|
@ -547,109 +533,88 @@ func (sw *Switch) addInboundPeerWithConfig(
|
||||||
// StopPeerForError is called
|
// StopPeerForError is called
|
||||||
func (sw *Switch) addOutboundPeerWithConfig(
|
func (sw *Switch) addOutboundPeerWithConfig(
|
||||||
addr *NetAddress,
|
addr *NetAddress,
|
||||||
config *config.P2PConfig,
|
cfg *config.P2PConfig,
|
||||||
persistent bool,
|
persistent bool,
|
||||||
) error {
|
) error {
|
||||||
sw.Logger.Info("Dialing peer", "address", addr)
|
sw.Logger.Info("Dialing peer", "address", addr)
|
||||||
peerConn, err := newOutboundPeerConn(
|
|
||||||
addr,
|
// XXX(xla): Remove the leakage of test concerns in implementation.
|
||||||
config,
|
if cfg.TestDialFail {
|
||||||
persistent,
|
|
||||||
sw.nodeKey.PrivKey,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if persistent {
|
|
||||||
go sw.reconnectToPeer(addr)
|
go sw.reconnectToPeer(addr)
|
||||||
}
|
return fmt.Errorf("dial err (peerConfig.DialFail == true)")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sw.addPeer(peerConn); err != nil {
|
p, err := sw.transport.Dial(*addr, peerConfig{
|
||||||
peerConn.CloseConn()
|
chDescs: sw.chDescs,
|
||||||
return err
|
onPeerError: sw.StopPeerForError,
|
||||||
}
|
persistent: persistent,
|
||||||
return nil
|
reactorsByCh: sw.reactorsByCh,
|
||||||
}
|
})
|
||||||
|
|
||||||
// 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(pc peerConn) error {
|
|
||||||
|
|
||||||
addr := pc.conn.RemoteAddr()
|
|
||||||
if err := sw.FilterConnByAddr(addr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exchange NodeInfo on the conn
|
|
||||||
peerNodeInfo, err := pc.HandshakeTimeout(sw.nodeInfo, time.Duration(sw.config.HandshakeTimeout))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
switch e := err.(type) {
|
||||||
}
|
case ErrRejected:
|
||||||
|
if e.IsSelf() {
|
||||||
peerID := peerNodeInfo.ID
|
// Remove the given address from the address book and add to our addresses
|
||||||
|
// to avoid dialing in the future.
|
||||||
// ensure connection key matches self reported key
|
|
||||||
connID := pc.ID()
|
|
||||||
|
|
||||||
if peerID != connID {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"nodeInfo.ID() (%v) doesn't match conn.ID() (%v)",
|
|
||||||
peerID,
|
|
||||||
connID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the peers nodeInfo
|
|
||||||
if err := peerNodeInfo.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid self
|
|
||||||
if sw.nodeKey.ID() == peerID {
|
|
||||||
addr := peerNodeInfo.NetAddress()
|
|
||||||
// remove the given address from the address book
|
|
||||||
// and add to our addresses to avoid dialing again
|
|
||||||
if sw.addrBook != nil {
|
|
||||||
sw.addrBook.RemoveAddress(addr)
|
sw.addrBook.RemoveAddress(addr)
|
||||||
sw.addrBook.AddOurAddress(addr)
|
sw.addrBook.AddOurAddress(addr)
|
||||||
}
|
}
|
||||||
return ErrSwitchConnectToSelf{addr}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if persistent {
|
||||||
|
go sw.reconnectToPeer(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sw.addPeer(p); err != nil {
|
||||||
|
_ = p.Stop()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *Switch) filterPeer(p Peer) error {
|
||||||
// Avoid duplicate
|
// Avoid duplicate
|
||||||
if sw.peers.Has(peerID) {
|
if sw.peers.Has(p.ID()) {
|
||||||
return ErrSwitchDuplicatePeerID{peerID}
|
return ErrRejected{id: p.ID(), isDuplicate: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate connection or peer info IP.
|
errc := make(chan error, len(sw.peerFilters))
|
||||||
if !sw.config.AllowDuplicateIP &&
|
|
||||||
(sw.peers.HasIP(pc.RemoteIP()) ||
|
for _, f := range sw.peerFilters {
|
||||||
sw.peers.HasIP(peerNodeInfo.NetAddress().IP)) {
|
go func(f PeerFilterFunc, p Peer, errc chan<- error) {
|
||||||
return ErrSwitchDuplicatePeerIP{pc.RemoteIP()}
|
errc <- f(sw.peers, p)
|
||||||
|
}(f, p, errc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter peer against ID white list
|
for i := 0; i < cap(errc); i++ {
|
||||||
if err := sw.FilterConnByID(peerID); err != nil {
|
select {
|
||||||
|
case err := <-errc:
|
||||||
|
if err != nil {
|
||||||
|
return ErrRejected{id: p.ID(), err: err, isFiltered: true}
|
||||||
|
}
|
||||||
|
case <-time.After(sw.filterTimeout):
|
||||||
|
return ErrFilterTimeout{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addPeer starts up the Peer and adds it to the Switch.
|
||||||
|
func (sw *Switch) addPeer(p Peer) error {
|
||||||
|
if err := sw.filterPeer(p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check version, chain id
|
p.SetLogger(sw.Logger.With("peer", p.NodeInfo().NetAddress().String))
|
||||||
if err := sw.nodeInfo.CompatibleWith(peerNodeInfo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
peer := newPeer(pc, sw.mConfig, peerNodeInfo, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError)
|
|
||||||
peer.SetLogger(sw.Logger.With("peer", addr))
|
|
||||||
|
|
||||||
peer.Logger.Info("Successful handshake with peer", "peerNodeInfo", peerNodeInfo)
|
|
||||||
|
|
||||||
// All good. Start peer
|
// All good. Start peer
|
||||||
if sw.IsRunning() {
|
if sw.IsRunning() {
|
||||||
if err = sw.startInitPeer(peer); err != nil {
|
if err := sw.startInitPeer(p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,25 +622,30 @@ func (sw *Switch) addPeer(pc peerConn) error {
|
||||||
// Add the peer to .peers.
|
// Add the peer to .peers.
|
||||||
// We start it first so that a peer in the list is safe to Stop.
|
// 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().
|
// It should not err since we already checked peers.Has().
|
||||||
if err := sw.peers.Add(peer); err != nil {
|
if err := sw.peers.Add(p); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sw.Logger.Info("Added peer", "peer", p)
|
||||||
sw.metrics.Peers.Add(float64(1))
|
sw.metrics.Peers.Add(float64(1))
|
||||||
|
|
||||||
sw.Logger.Info("Added peer", "peer", peer)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sw *Switch) startInitPeer(peer *peer) error {
|
func (sw *Switch) startInitPeer(p Peer) error {
|
||||||
err := peer.Start() // spawn send/recv routines
|
err := p.Start() // spawn send/recv routines
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Should never happen
|
// Should never happen
|
||||||
sw.Logger.Error("Error starting peer", "peer", peer, "err", err)
|
sw.Logger.Error(
|
||||||
|
"Error starting peer",
|
||||||
|
"err", err,
|
||||||
|
"peer", p,
|
||||||
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, reactor := range sw.reactors {
|
for _, reactor := range sw.reactors {
|
||||||
reactor.AddPeer(peer)
|
reactor.AddPeer(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -3,7 +3,6 @@ package p2p
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -11,10 +10,9 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/config"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/config"
|
|
||||||
"github.com/tendermint/tendermint/p2p/conn"
|
"github.com/tendermint/tendermint/p2p/conn"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -151,35 +149,6 @@ func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnAddrFilter(t *testing.T) {
|
|
||||||
s1 := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc)
|
|
||||||
s2 := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc)
|
|
||||||
defer s1.Stop()
|
|
||||||
defer s2.Stop()
|
|
||||||
|
|
||||||
c1, c2 := conn.NetPipe()
|
|
||||||
|
|
||||||
s1.SetAddrFilter(func(addr net.Addr) error {
|
|
||||||
if addr.String() == c1.RemoteAddr().String() {
|
|
||||||
return fmt.Errorf("Error: pipe is blacklisted")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// connect to good peer
|
|
||||||
go func() {
|
|
||||||
err := s1.addPeerWithConnection(c1)
|
|
||||||
assert.NotNil(t, err, "expected err")
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
err := s2.addPeerWithConnection(c2)
|
|
||||||
assert.NotNil(t, err, "expected err")
|
|
||||||
}()
|
|
||||||
|
|
||||||
assertNoPeersAfterTimeout(t, s1, 400*time.Millisecond)
|
|
||||||
assertNoPeersAfterTimeout(t, s2, 400*time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwitchFiltersOutItself(t *testing.T) {
|
func TestSwitchFiltersOutItself(t *testing.T) {
|
||||||
s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc)
|
s1 := MakeSwitch(cfg, 1, "127.0.0.1", "123.123.123", initSwitchFunc)
|
||||||
// addr := s1.NodeInfo().NetAddress()
|
// addr := s1.NodeInfo().NetAddress()
|
||||||
|
@ -194,11 +163,16 @@ func TestSwitchFiltersOutItself(t *testing.T) {
|
||||||
// addr should be rejected in addPeer based on the same ID
|
// addr should be rejected in addPeer based on the same ID
|
||||||
err := s1.DialPeerWithAddress(rp.Addr(), false)
|
err := s1.DialPeerWithAddress(rp.Addr(), false)
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, ErrSwitchConnectToSelf{rp.Addr()}.Error(), err.Error())
|
if err, ok := err.(ErrRejected); ok {
|
||||||
|
if !err.IsSelf() {
|
||||||
|
t.Errorf("expected self to be rejected")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("expected ErrRejected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.True(t, s1.addrBook.OurAddress(rp.Addr()))
|
assert.True(t, s1.addrBook.OurAddress(rp.Addr()))
|
||||||
|
|
||||||
assert.False(t, s1.addrBook.HasAddress(rp.Addr()))
|
assert.False(t, s1.addrBook.HasAddress(rp.Addr()))
|
||||||
|
|
||||||
rp.Stop()
|
rp.Stop()
|
||||||
|
@ -206,6 +180,119 @@ func TestSwitchFiltersOutItself(t *testing.T) {
|
||||||
assertNoPeersAfterTimeout(t, s1, 100*time.Millisecond)
|
assertNoPeersAfterTimeout(t, s1, 100*time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSwitchPeerFilter(t *testing.T) {
|
||||||
|
var (
|
||||||
|
filters = []PeerFilterFunc{
|
||||||
|
func(_ IPeerSet, _ Peer) error { return nil },
|
||||||
|
func(_ IPeerSet, _ Peer) error { return fmt.Errorf("denied!") },
|
||||||
|
func(_ IPeerSet, _ Peer) error { return nil },
|
||||||
|
}
|
||||||
|
sw = MakeSwitch(
|
||||||
|
cfg,
|
||||||
|
1,
|
||||||
|
"testing",
|
||||||
|
"123.123.123",
|
||||||
|
initSwitchFunc,
|
||||||
|
SwitchPeerFilters(filters...),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
defer sw.Stop()
|
||||||
|
|
||||||
|
// simulate remote peer
|
||||||
|
rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg}
|
||||||
|
rp.Start()
|
||||||
|
defer rp.Stop()
|
||||||
|
|
||||||
|
p, err := sw.transport.Dial(*rp.Addr(), peerConfig{
|
||||||
|
chDescs: sw.chDescs,
|
||||||
|
onPeerError: sw.StopPeerForError,
|
||||||
|
reactorsByCh: sw.reactorsByCh,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sw.addPeer(p)
|
||||||
|
if err, ok := err.(ErrRejected); ok {
|
||||||
|
if !err.IsFiltered() {
|
||||||
|
t.Errorf("expected peer to be filtered")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("expected ErrRejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwitchPeerFilterTimeout(t *testing.T) {
|
||||||
|
var (
|
||||||
|
filters = []PeerFilterFunc{
|
||||||
|
func(_ IPeerSet, _ Peer) error {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sw = MakeSwitch(
|
||||||
|
cfg,
|
||||||
|
1,
|
||||||
|
"testing",
|
||||||
|
"123.123.123",
|
||||||
|
initSwitchFunc,
|
||||||
|
SwitchFilterTimeout(5*time.Millisecond),
|
||||||
|
SwitchPeerFilters(filters...),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
defer sw.Stop()
|
||||||
|
|
||||||
|
// simulate remote peer
|
||||||
|
rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg}
|
||||||
|
rp.Start()
|
||||||
|
defer rp.Stop()
|
||||||
|
|
||||||
|
p, err := sw.transport.Dial(*rp.Addr(), peerConfig{
|
||||||
|
chDescs: sw.chDescs,
|
||||||
|
onPeerError: sw.StopPeerForError,
|
||||||
|
reactorsByCh: sw.reactorsByCh,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sw.addPeer(p)
|
||||||
|
if _, ok := err.(ErrFilterTimeout); !ok {
|
||||||
|
t.Errorf("expected ErrFilterTimeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwitchPeerFilterDuplicate(t *testing.T) {
|
||||||
|
sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc)
|
||||||
|
|
||||||
|
// simulate remote peer
|
||||||
|
rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg}
|
||||||
|
rp.Start()
|
||||||
|
defer rp.Stop()
|
||||||
|
|
||||||
|
p, err := sw.transport.Dial(*rp.Addr(), peerConfig{
|
||||||
|
chDescs: sw.chDescs,
|
||||||
|
onPeerError: sw.StopPeerForError,
|
||||||
|
reactorsByCh: sw.reactorsByCh,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sw.addPeer(p); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sw.addPeer(p)
|
||||||
|
if err, ok := err.(ErrRejected); ok {
|
||||||
|
if !err.IsDuplicate() {
|
||||||
|
t.Errorf("expected peer to be duplicate")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("expected ErrRejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) {
|
func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration) {
|
||||||
time.Sleep(timeout)
|
time.Sleep(timeout)
|
||||||
if sw.Peers().Size() != 0 {
|
if sw.Peers().Size() != 0 {
|
||||||
|
@ -213,41 +300,6 @@ func assertNoPeersAfterTimeout(t *testing.T, sw *Switch, timeout time.Duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnIDFilter(t *testing.T) {
|
|
||||||
s1 := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc)
|
|
||||||
s2 := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc)
|
|
||||||
defer s1.Stop()
|
|
||||||
defer s2.Stop()
|
|
||||||
|
|
||||||
c1, c2 := conn.NetPipe()
|
|
||||||
|
|
||||||
s1.SetIDFilter(func(id ID) error {
|
|
||||||
if id == s2.nodeInfo.ID {
|
|
||||||
return fmt.Errorf("Error: pipe is blacklisted")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
s2.SetIDFilter(func(id ID) error {
|
|
||||||
if id == s1.nodeInfo.ID {
|
|
||||||
return fmt.Errorf("Error: pipe is blacklisted")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
err := s1.addPeerWithConnection(c1)
|
|
||||||
assert.NotNil(t, err, "expected error")
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
err := s2.addPeerWithConnection(c2)
|
|
||||||
assert.NotNil(t, err, "expected error")
|
|
||||||
}()
|
|
||||||
|
|
||||||
assertNoPeersAfterTimeout(t, s1, 400*time.Millisecond)
|
|
||||||
assertNoPeersAfterTimeout(t, s2, 400*time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) {
|
func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) {
|
||||||
assert, require := assert.New(t), require.New(t)
|
assert, require := assert.New(t), require.New(t)
|
||||||
|
|
||||||
|
@ -263,19 +315,23 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) {
|
||||||
rp.Start()
|
rp.Start()
|
||||||
defer rp.Stop()
|
defer rp.Stop()
|
||||||
|
|
||||||
pc, err := newOutboundPeerConn(rp.Addr(), cfg, false, sw.nodeKey.PrivKey)
|
p, err := sw.transport.Dial(*rp.Addr(), peerConfig{
|
||||||
require.Nil(err)
|
chDescs: sw.chDescs,
|
||||||
err = sw.addPeer(pc)
|
onPeerError: sw.StopPeerForError,
|
||||||
|
reactorsByCh: sw.reactorsByCh,
|
||||||
|
})
|
||||||
require.Nil(err)
|
require.Nil(err)
|
||||||
|
|
||||||
peer := sw.Peers().Get(rp.ID())
|
err = sw.addPeer(p)
|
||||||
require.NotNil(peer)
|
require.Nil(err)
|
||||||
|
|
||||||
|
require.NotNil(sw.Peers().Get(rp.ID()))
|
||||||
|
|
||||||
// simulate failure by closing connection
|
// simulate failure by closing connection
|
||||||
pc.CloseConn()
|
p.(*peer).CloseConn()
|
||||||
|
|
||||||
assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond)
|
assertNoPeersAfterTimeout(t, sw, 100*time.Millisecond)
|
||||||
assert.False(peer.IsRunning())
|
assert.False(p.IsRunning())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
||||||
|
@ -293,17 +349,20 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
||||||
rp.Start()
|
rp.Start()
|
||||||
defer rp.Stop()
|
defer rp.Stop()
|
||||||
|
|
||||||
pc, err := newOutboundPeerConn(rp.Addr(), cfg, true, sw.nodeKey.PrivKey)
|
p, err := sw.transport.Dial(*rp.Addr(), peerConfig{
|
||||||
// sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodeKey.PrivKey,
|
chDescs: sw.chDescs,
|
||||||
|
onPeerError: sw.StopPeerForError,
|
||||||
|
persistent: true,
|
||||||
|
reactorsByCh: sw.reactorsByCh,
|
||||||
|
})
|
||||||
require.Nil(err)
|
require.Nil(err)
|
||||||
|
|
||||||
require.Nil(sw.addPeer(pc))
|
require.Nil(sw.addPeer(p))
|
||||||
|
|
||||||
peer := sw.Peers().Get(rp.ID())
|
require.NotNil(sw.Peers().Get(rp.ID()))
|
||||||
require.NotNil(peer)
|
|
||||||
|
|
||||||
// simulate failure by closing connection
|
// simulate failure by closing connection
|
||||||
pc.CloseConn()
|
p.(*peer).CloseConn()
|
||||||
|
|
||||||
// TODO: remove sleep, detect the disconnection, wait for reconnect
|
// TODO: remove sleep, detect the disconnection, wait for reconnect
|
||||||
npeers := sw.Peers().Size()
|
npeers := sw.Peers().Size()
|
||||||
|
@ -315,7 +374,7 @@ func TestSwitchReconnectsToPersistentPeer(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.NotZero(npeers)
|
assert.NotZero(npeers)
|
||||||
assert.False(peer.IsRunning())
|
assert.False(p.IsRunning())
|
||||||
|
|
||||||
// simulate another remote peer
|
// simulate another remote peer
|
||||||
rp = &remotePeer{
|
rp = &remotePeer{
|
||||||
|
|
112
p2p/test_util.go
112
p2p/test_util.go
|
@ -3,7 +3,9 @@ package p2p
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
crypto "github.com/tendermint/tendermint/crypto"
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||||
cmn "github.com/tendermint/tendermint/libs/common"
|
cmn "github.com/tendermint/tendermint/libs/common"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
@ -104,14 +106,32 @@ func Connect2Switches(switches []*Switch, i, j int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sw *Switch) addPeerWithConnection(conn net.Conn) error {
|
func (sw *Switch) addPeerWithConnection(conn net.Conn) error {
|
||||||
pc, err := newInboundPeerConn(conn, sw.config, sw.nodeKey.PrivKey)
|
pc, err := testInboundPeerConn(conn, sw.config, sw.nodeKey.PrivKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err := conn.Close(); err != nil {
|
if err := conn.Close(); err != nil {
|
||||||
sw.Logger.Error("Error closing connection", "err", err)
|
sw.Logger.Error("Error closing connection", "err", err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = sw.addPeer(pc); err != nil {
|
|
||||||
|
ni, err := handshake(conn, 50*time.Millisecond, sw.nodeInfo)
|
||||||
|
if err != nil {
|
||||||
|
if err := conn.Close(); err != nil {
|
||||||
|
sw.Logger.Error("Error closing connection", "err", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := newPeer(
|
||||||
|
pc,
|
||||||
|
sw.mConfig,
|
||||||
|
ni,
|
||||||
|
sw.reactorsByCh,
|
||||||
|
sw.chDescs,
|
||||||
|
sw.StopPeerForError,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err = sw.addPeer(p); err != nil {
|
||||||
pc.CloseConn()
|
pc.CloseConn()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -131,35 +151,99 @@ func StartSwitches(switches []*Switch) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeSwitch(cfg *config.P2PConfig, i int, network, version string, initSwitch func(int, *Switch) *Switch) *Switch {
|
func MakeSwitch(
|
||||||
// new switch, add reactors
|
cfg *config.P2PConfig,
|
||||||
// TODO: let the config be passed in?
|
i int,
|
||||||
nodeKey := &NodeKey{
|
network, version string,
|
||||||
|
initSwitch func(int, *Switch) *Switch,
|
||||||
|
opts ...SwitchOption,
|
||||||
|
) *Switch {
|
||||||
|
var (
|
||||||
|
nodeKey = NodeKey{
|
||||||
PrivKey: ed25519.GenPrivKey(),
|
PrivKey: ed25519.GenPrivKey(),
|
||||||
}
|
}
|
||||||
sw := NewSwitch(cfg)
|
ni = NodeInfo{
|
||||||
sw.SetLogger(log.TestingLogger())
|
|
||||||
sw = initSwitch(i, sw)
|
|
||||||
addr := fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023)
|
|
||||||
ni := NodeInfo{
|
|
||||||
ID: nodeKey.ID(),
|
ID: nodeKey.ID(),
|
||||||
Moniker: fmt.Sprintf("switch%d", i),
|
Moniker: fmt.Sprintf("switch%d", i),
|
||||||
Network: network,
|
Network: network,
|
||||||
Version: version,
|
Version: version,
|
||||||
ListenAddr: addr,
|
ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023),
|
||||||
Other: NodeInfoOther{
|
Other: NodeInfoOther{
|
||||||
AminoVersion: "1.0",
|
AminoVersion: "1.0",
|
||||||
P2PVersion: "1.0",
|
P2PVersion: "1.0",
|
||||||
ConsensusVersion: "1.0",
|
ConsensusVersion: "1.0",
|
||||||
RPCVersion: "1.0",
|
RPCVersion: "1.0",
|
||||||
TxIndex: "off",
|
TxIndex: "off",
|
||||||
RPCAddress: addr,
|
RPCAddress: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
addr, err := NewNetAddressStringWithOptionalID(
|
||||||
|
IDAddressString(nodeKey.ID(), ni.ListenAddr),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := NewMultiplexTransport(ni, nodeKey)
|
||||||
|
|
||||||
|
if err := t.Listen(*addr); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: let the config be passed in?
|
||||||
|
sw := initSwitch(i, NewSwitch(cfg, t, opts...))
|
||||||
|
sw.SetLogger(log.TestingLogger())
|
||||||
|
sw.SetNodeKey(&nodeKey)
|
||||||
|
|
||||||
for ch := range sw.reactorsByCh {
|
for ch := range sw.reactorsByCh {
|
||||||
ni.Channels = append(ni.Channels, ch)
|
ni.Channels = append(ni.Channels, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We need to setup reactors ahead of time so the NodeInfo is properly
|
||||||
|
// populated and we don't have to do those awkward overrides and setters.
|
||||||
|
t.nodeInfo = ni
|
||||||
sw.SetNodeInfo(ni)
|
sw.SetNodeInfo(ni)
|
||||||
sw.SetNodeKey(nodeKey)
|
|
||||||
return sw
|
return sw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInboundPeerConn(
|
||||||
|
conn net.Conn,
|
||||||
|
config *config.P2PConfig,
|
||||||
|
ourNodePrivKey crypto.PrivKey,
|
||||||
|
) (peerConn, error) {
|
||||||
|
return testPeerConn(conn, config, false, false, ourNodePrivKey, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPeerConn(
|
||||||
|
rawConn net.Conn,
|
||||||
|
cfg *config.P2PConfig,
|
||||||
|
outbound, persistent bool,
|
||||||
|
ourNodePrivKey crypto.PrivKey,
|
||||||
|
originalAddr *NetAddress,
|
||||||
|
) (pc peerConn, err error) {
|
||||||
|
conn := rawConn
|
||||||
|
|
||||||
|
// Fuzz connection
|
||||||
|
if cfg.TestFuzz {
|
||||||
|
// so we have time to do peer handshakes and get set up
|
||||||
|
conn = FuzzConnAfterFromConfig(conn, 10*time.Second, cfg.TestFuzzConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt connection
|
||||||
|
conn, err = upgradeSecretConn(conn, cfg.HandshakeTimeout, ourNodePrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return pc, cmn.ErrorWrap(err, "Error creating peer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the information we already have
|
||||||
|
return peerConn{
|
||||||
|
config: cfg,
|
||||||
|
outbound: outbound,
|
||||||
|
persistent: persistent,
|
||||||
|
conn: conn,
|
||||||
|
originalAddr: originalAddr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -191,7 +191,7 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) {
|
||||||
// ```
|
// ```
|
||||||
func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
|
||||||
// Get Peer consensus states.
|
// Get Peer consensus states.
|
||||||
peers := p2pSwitch.Peers().List()
|
peers := p2pPeers.Peers().List()
|
||||||
peerStates := make([]ctypes.PeerStateInfo, len(peers))
|
peerStates := make([]ctypes.PeerStateInfo, len(peers))
|
||||||
for i, peer := range peers {
|
for i, peer := range peers {
|
||||||
peerState := peer.Get(types.PeerStateKey).(*cm.PeerState)
|
peerState := peer.Get(types.PeerStateKey).(*cm.PeerState)
|
||||||
|
|
|
@ -35,13 +35,8 @@ import (
|
||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
func NetInfo() (*ctypes.ResultNetInfo, error) {
|
func NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||||
listening := p2pSwitch.IsListening()
|
|
||||||
listeners := []string{}
|
|
||||||
for _, listener := range p2pSwitch.Listeners() {
|
|
||||||
listeners = append(listeners, listener.String())
|
|
||||||
}
|
|
||||||
peers := []ctypes.Peer{}
|
peers := []ctypes.Peer{}
|
||||||
for _, peer := range p2pSwitch.Peers().List() {
|
for _, peer := range p2pPeers.Peers().List() {
|
||||||
peers = append(peers, ctypes.Peer{
|
peers = append(peers, ctypes.Peer{
|
||||||
NodeInfo: peer.NodeInfo(),
|
NodeInfo: peer.NodeInfo(),
|
||||||
IsOutbound: peer.IsOutbound(),
|
IsOutbound: peer.IsOutbound(),
|
||||||
|
@ -52,8 +47,8 @@ func NetInfo() (*ctypes.ResultNetInfo, error) {
|
||||||
// PRO: useful info
|
// PRO: useful info
|
||||||
// CON: privacy
|
// CON: privacy
|
||||||
return &ctypes.ResultNetInfo{
|
return &ctypes.ResultNetInfo{
|
||||||
Listening: listening,
|
Listening: p2pTransport.IsListening(),
|
||||||
Listeners: listeners,
|
Listeners: p2pTransport.Listeners(),
|
||||||
NPeers: len(peers),
|
NPeers: len(peers),
|
||||||
Peers: peers,
|
Peers: peers,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -65,7 +60,7 @@ func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) {
|
||||||
}
|
}
|
||||||
// starts go routines to dial each peer after random delays
|
// starts go routines to dial each peer after random delays
|
||||||
logger.Info("DialSeeds", "addrBook", addrBook, "seeds", seeds)
|
logger.Info("DialSeeds", "addrBook", addrBook, "seeds", seeds)
|
||||||
err := p2pSwitch.DialPeersAsync(addrBook, seeds, false)
|
err := p2pPeers.DialPeersAsync(addrBook, seeds, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ctypes.ResultDialSeeds{}, err
|
return &ctypes.ResultDialSeeds{}, err
|
||||||
}
|
}
|
||||||
|
@ -78,7 +73,7 @@ func UnsafeDialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers,
|
||||||
}
|
}
|
||||||
// starts go routines to dial each peer after random delays
|
// starts go routines to dial each peer after random delays
|
||||||
logger.Info("DialPeers", "addrBook", addrBook, "peers", peers, "persistent", persistent)
|
logger.Info("DialPeers", "addrBook", addrBook, "peers", peers, "persistent", persistent)
|
||||||
err := p2pSwitch.DialPeersAsync(addrBook, peers, persistent)
|
err := p2pPeers.DialPeersAsync(addrBook, peers, persistent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ctypes.ResultDialPeers{}, err
|
return &ctypes.ResultDialPeers{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,16 @@ type Consensus interface {
|
||||||
GetRoundStateSimpleJSON() ([]byte, error)
|
GetRoundStateSimpleJSON() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type P2P interface {
|
type transport interface {
|
||||||
Listeners() []p2p.Listener
|
Listeners() []string
|
||||||
Peers() p2p.IPeerSet
|
|
||||||
NumPeers() (outbound, inbound, dialig int)
|
|
||||||
NodeInfo() p2p.NodeInfo
|
|
||||||
IsListening() bool
|
IsListening() bool
|
||||||
|
NodeInfo() p2p.NodeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type peers interface {
|
||||||
DialPeersAsync(p2p.AddrBook, []string, bool) error
|
DialPeersAsync(p2p.AddrBook, []string, bool) error
|
||||||
|
NumPeers() (outbound, inbound, dialig int)
|
||||||
|
Peers() p2p.IPeerSet
|
||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------
|
//----------------------------------------------
|
||||||
|
@ -56,7 +59,8 @@ var (
|
||||||
blockStore sm.BlockStore
|
blockStore sm.BlockStore
|
||||||
evidencePool sm.EvidencePool
|
evidencePool sm.EvidencePool
|
||||||
consensusState Consensus
|
consensusState Consensus
|
||||||
p2pSwitch P2P
|
p2pPeers peers
|
||||||
|
p2pTransport transport
|
||||||
|
|
||||||
// objects
|
// objects
|
||||||
pubKey crypto.PubKey
|
pubKey crypto.PubKey
|
||||||
|
@ -90,8 +94,12 @@ func SetConsensusState(cs Consensus) {
|
||||||
consensusState = cs
|
consensusState = cs
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetSwitch(sw P2P) {
|
func SetP2PPeers(p peers) {
|
||||||
p2pSwitch = sw
|
p2pPeers = p
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetP2PTransport(t transport) {
|
||||||
|
p2pTransport = t
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetPubKey(pk crypto.PubKey) {
|
func SetPubKey(pk crypto.PubKey) {
|
||||||
|
|
|
@ -91,7 +91,7 @@ func Status() (*ctypes.ResultStatus, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &ctypes.ResultStatus{
|
result := &ctypes.ResultStatus{
|
||||||
NodeInfo: p2pSwitch.NodeInfo(),
|
NodeInfo: p2pTransport.NodeInfo(),
|
||||||
SyncInfo: ctypes.SyncInfo{
|
SyncInfo: ctypes.SyncInfo{
|
||||||
LatestBlockHash: latestBlockHash,
|
LatestBlockHash: latestBlockHash,
|
||||||
LatestAppHash: latestAppHash,
|
LatestAppHash: latestAppHash,
|
||||||
|
|
Loading…
Reference in New Issue