package p2p_test
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/fortytw2/leaktest"
|
|
gogotypes "github.com/gogo/protobuf/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
"github.com/tendermint/tendermint/p2p"
|
|
)
|
|
|
|
type TestMessage = gogotypes.StringValue
|
|
|
|
func generateNode() (p2p.NodeInfo, crypto.PrivKey) {
|
|
privKey := ed25519.GenPrivKey()
|
|
nodeID := p2p.NodeIDFromPubKey(privKey.PubKey())
|
|
nodeInfo := p2p.NodeInfo{
|
|
NodeID: nodeID,
|
|
// FIXME: We have to fake a ListenAddr for now.
|
|
ListenAddr: "127.0.0.1:1234",
|
|
Moniker: "foo",
|
|
}
|
|
return nodeInfo, privKey
|
|
}
|
|
|
|
func echoReactor(channel *p2p.Channel) {
|
|
for {
|
|
select {
|
|
case envelope := <-channel.In():
|
|
channel.Out() <- p2p.Envelope{
|
|
To: envelope.From,
|
|
Message: &TestMessage{Value: envelope.Message.(*TestMessage).Value},
|
|
}
|
|
case <-channel.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRouter(t *testing.T) {
|
|
defer leaktest.Check(t)()
|
|
|
|
logger := log.TestingLogger()
|
|
network := p2p.NewMemoryNetwork(logger)
|
|
nodeInfo, privKey := generateNode()
|
|
transport := network.CreateTransport(nodeInfo.NodeID)
|
|
defer transport.Close()
|
|
chID := p2p.ChannelID(1)
|
|
|
|
// Start some other in-memory network nodes to communicate with, running
|
|
// a simple echo reactor that returns received messages.
|
|
peers := []p2p.NodeAddress{}
|
|
for i := 0; i < 3; i++ {
|
|
peerManager, err := p2p.NewPeerManager(dbm.NewMemDB(), p2p.PeerManagerOptions{})
|
|
require.NoError(t, err)
|
|
peerInfo, peerKey := generateNode()
|
|
peerTransport := network.CreateTransport(peerInfo.NodeID)
|
|
defer peerTransport.Close()
|
|
peerRouter, err := p2p.NewRouter(
|
|
logger.With("peerID", i),
|
|
peerInfo,
|
|
peerKey,
|
|
peerManager,
|
|
[]p2p.Transport{peerTransport},
|
|
p2p.RouterOptions{},
|
|
)
|
|
require.NoError(t, err)
|
|
peers = append(peers, peerTransport.Endpoints()[0].NodeAddress(peerInfo.NodeID))
|
|
|
|
channel, err := peerRouter.OpenChannel(chID, &TestMessage{})
|
|
require.NoError(t, err)
|
|
defer channel.Close()
|
|
go echoReactor(channel)
|
|
|
|
err = peerRouter.Start()
|
|
require.NoError(t, err)
|
|
defer func() { require.NoError(t, peerRouter.Stop()) }()
|
|
}
|
|
|
|
// Start the main router and connect it to the peers above.
|
|
peerManager, err := p2p.NewPeerManager(dbm.NewMemDB(), p2p.PeerManagerOptions{})
|
|
require.NoError(t, err)
|
|
defer peerManager.Close()
|
|
for _, address := range peers {
|
|
err := peerManager.Add(address)
|
|
require.NoError(t, err)
|
|
}
|
|
peerUpdates := peerManager.Subscribe()
|
|
defer peerUpdates.Close()
|
|
|
|
router, err := p2p.NewRouter(logger, nodeInfo, privKey, peerManager, []p2p.Transport{transport}, p2p.RouterOptions{})
|
|
require.NoError(t, err)
|
|
channel, err := router.OpenChannel(chID, &TestMessage{})
|
|
require.NoError(t, err)
|
|
defer channel.Close()
|
|
|
|
err = router.Start()
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
// Since earlier defers are closed after this, and we have to make sure
|
|
// we close channels and subscriptions before the router, we explicitly
|
|
// close them here to.
|
|
peerUpdates.Close()
|
|
channel.Close()
|
|
require.NoError(t, router.Stop())
|
|
}()
|
|
|
|
// Wait for peers to come online, and ping them as they do.
|
|
for i := 0; i < len(peers); i++ {
|
|
peerUpdate := <-peerUpdates.Updates()
|
|
peerID := peerUpdate.PeerID
|
|
require.Equal(t, p2p.PeerUpdate{
|
|
PeerID: peerID,
|
|
Status: p2p.PeerStatusUp,
|
|
}, peerUpdate)
|
|
|
|
channel.Out() <- p2p.Envelope{To: peerID, Message: &TestMessage{Value: "hi!"}}
|
|
assert.Equal(t, p2p.Envelope{
|
|
From: peerID,
|
|
Message: &TestMessage{Value: "hi!"},
|
|
}, (<-channel.In()).Strip())
|
|
}
|
|
|
|
// We now send a broadcast, which we should return back from all peers.
|
|
channel.Out() <- p2p.Envelope{
|
|
Broadcast: true,
|
|
Message: &TestMessage{Value: "broadcast"},
|
|
}
|
|
for i := 0; i < len(peers); i++ {
|
|
envelope := <-channel.In()
|
|
require.Equal(t, &TestMessage{Value: "broadcast"}, envelope.Message)
|
|
}
|
|
|
|
// We then submit an error for a peer, and watch it get disconnected.
|
|
channel.Error() <- p2p.PeerError{
|
|
PeerID: peers[0].NodeID,
|
|
Err: errors.New("test error"),
|
|
Severity: p2p.PeerErrorSeverityCritical,
|
|
}
|
|
peerUpdate := <-peerUpdates.Updates()
|
|
require.Equal(t, p2p.PeerUpdate{
|
|
PeerID: peers[0].NodeID,
|
|
Status: p2p.PeerStatusDown,
|
|
}, peerUpdate)
|
|
|
|
// The peer manager will automatically reconnect the peer, so we wait
|
|
// for that to happen.
|
|
peerUpdate = <-peerUpdates.Updates()
|
|
require.Equal(t, p2p.PeerUpdate{
|
|
PeerID: peers[0].NodeID,
|
|
Status: p2p.PeerStatusUp,
|
|
}, peerUpdate)
|
|
}
|