package p2p
|
|
|
|
import (
|
|
"fmt"
|
|
golog "log"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
"github.com/tendermint/tendermint/libs/bytes"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
"github.com/tendermint/tendermint/config"
|
|
tmconn "github.com/tendermint/tendermint/p2p/conn"
|
|
)
|
|
|
|
func TestPeerIDFromString(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
input string
|
|
expectedID PeerID
|
|
expectErr bool
|
|
}{
|
|
"empty peer ID string": {"", PeerID{}, false},
|
|
"invalid peer ID string": {"foo", nil, true},
|
|
"valid peer ID string": {"ff", PeerID{0xFF}, false},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
pID, err := PeerIDFromString(tc.input)
|
|
require.Equal(t, tc.expectErr, err != nil, err)
|
|
require.Equal(t, tc.expectedID, pID)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPeerID_String(t *testing.T) {
|
|
require.Equal(t, "", PeerID{}.String())
|
|
require.Equal(t, "ff", PeerID{0xFF}.String())
|
|
}
|
|
|
|
func TestPeerID_Equal(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
idA PeerID
|
|
idB PeerID
|
|
equal bool
|
|
}{
|
|
"empty IDs": {PeerID{}, PeerID{}, true},
|
|
"not equal": {PeerID{0xFF}, PeerID{0xAA}, false},
|
|
"equal": {PeerID{0xFF}, PeerID{0xFF}, true},
|
|
}
|
|
|
|
for name, tc := range testCases {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
require.Equal(t, tc.equal, tc.idA.Equal(tc.idB))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPeerBasic(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
// simulate remote peer
|
|
rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg}
|
|
rp.Start()
|
|
t.Cleanup(rp.Stop)
|
|
|
|
p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), cfg, tmconn.DefaultMConnConfig())
|
|
require.Nil(err)
|
|
|
|
err = p.Start()
|
|
require.Nil(err)
|
|
t.Cleanup(func() {
|
|
if err := p.Stop(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
|
|
assert.True(p.IsRunning())
|
|
assert.True(p.IsOutbound())
|
|
assert.False(p.IsPersistent())
|
|
p.persistent = true
|
|
assert.True(p.IsPersistent())
|
|
assert.Equal(rp.Addr().DialString(), p.RemoteAddr().String())
|
|
assert.Equal(rp.ID(), p.ID())
|
|
}
|
|
|
|
func TestPeerSend(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
|
|
config := cfg
|
|
|
|
// simulate remote peer
|
|
rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: config}
|
|
rp.Start()
|
|
t.Cleanup(rp.Stop)
|
|
|
|
p, err := createOutboundPeerAndPerformHandshake(rp.Addr(), config, tmconn.DefaultMConnConfig())
|
|
require.Nil(err)
|
|
|
|
err = p.Start()
|
|
require.Nil(err)
|
|
|
|
t.Cleanup(func() {
|
|
if err := p.Stop(); err != nil {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
|
|
assert.True(p.CanSend(testCh))
|
|
assert.True(p.Send(testCh, []byte("Asylum")))
|
|
}
|
|
|
|
func createOutboundPeerAndPerformHandshake(
|
|
addr *NetAddress,
|
|
config *config.P2PConfig,
|
|
mConfig tmconn.MConnConfig,
|
|
) (*peer, error) {
|
|
chDescs := []*tmconn.ChannelDescriptor{
|
|
{ID: testCh, Priority: 1},
|
|
}
|
|
reactorsByCh := map[byte]Reactor{testCh: NewTestReactor(chDescs, true)}
|
|
pk := ed25519.GenPrivKey()
|
|
pc, err := testOutboundPeerConn(addr, config, false, pk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
timeout := 1 * time.Second
|
|
ourNodeInfo := testNodeInfo(addr.ID, "host_peer")
|
|
peerNodeInfo, err := handshake(pc.conn, timeout, ourNodeInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := newPeer(pc, mConfig, peerNodeInfo, reactorsByCh, chDescs, func(p Peer, r interface{}) {})
|
|
p.SetLogger(log.TestingLogger().With("peer", addr))
|
|
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) {
|
|
|
|
var pc peerConn
|
|
conn, err := testDial(addr, config)
|
|
if err != nil {
|
|
return pc, fmt.Errorf("error creating peer: %w", err)
|
|
}
|
|
|
|
pc, err = testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr)
|
|
if err != nil {
|
|
if cerr := conn.Close(); cerr != nil {
|
|
return pc, fmt.Errorf("%v: %w", cerr.Error(), err)
|
|
}
|
|
return pc, err
|
|
}
|
|
|
|
// ensure dialed ID matches connection ID
|
|
if addr.ID != pc.ID() {
|
|
if cerr := conn.Close(); cerr != nil {
|
|
return pc, fmt.Errorf("%v: %w", cerr.Error(), err)
|
|
}
|
|
return pc, ErrSwitchAuthenticationFailure{addr, pc.ID()}
|
|
}
|
|
|
|
return pc, nil
|
|
}
|
|
|
|
type remotePeer struct {
|
|
PrivKey crypto.PrivKey
|
|
Config *config.P2PConfig
|
|
addr *NetAddress
|
|
channels bytes.HexBytes
|
|
listenAddr string
|
|
listener net.Listener
|
|
}
|
|
|
|
func (rp *remotePeer) Addr() *NetAddress {
|
|
return rp.addr
|
|
}
|
|
|
|
func (rp *remotePeer) ID() ID {
|
|
return PubKeyToID(rp.PrivKey.PubKey())
|
|
}
|
|
|
|
func (rp *remotePeer) Start() {
|
|
if rp.listenAddr == "" {
|
|
rp.listenAddr = "127.0.0.1:0"
|
|
}
|
|
|
|
l, e := net.Listen("tcp", rp.listenAddr) // any available address
|
|
if e != nil {
|
|
golog.Fatalf("net.Listen tcp :0: %+v", e)
|
|
}
|
|
rp.listener = l
|
|
rp.addr = NewNetAddress(PubKeyToID(rp.PrivKey.PubKey()), l.Addr())
|
|
if rp.channels == nil {
|
|
rp.channels = []byte{testCh}
|
|
}
|
|
go rp.accept()
|
|
}
|
|
|
|
func (rp *remotePeer) Stop() {
|
|
rp.listener.Close()
|
|
}
|
|
|
|
func (rp *remotePeer) Dial(addr *NetAddress) (net.Conn, error) {
|
|
conn, err := addr.DialTimeout(1 * time.Second)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = handshake(pc.conn, time.Second, rp.nodeInfo())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return conn, err
|
|
}
|
|
|
|
func (rp *remotePeer) accept() {
|
|
conns := []net.Conn{}
|
|
|
|
for {
|
|
conn, err := rp.listener.Accept()
|
|
if err != nil {
|
|
golog.Printf("Failed to accept conn: %+v", err)
|
|
for _, conn := range conns {
|
|
_ = conn.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey)
|
|
if err != nil {
|
|
golog.Fatalf("Failed to create a peer: %+v", err)
|
|
}
|
|
|
|
_, err = handshake(pc.conn, time.Second, rp.nodeInfo())
|
|
if err != nil {
|
|
golog.Fatalf("Failed to perform handshake: %+v", err)
|
|
}
|
|
|
|
conns = append(conns, conn)
|
|
}
|
|
}
|
|
|
|
func (rp *remotePeer) nodeInfo() NodeInfo {
|
|
return DefaultNodeInfo{
|
|
ProtocolVersion: defaultProtocolVersion,
|
|
DefaultNodeID: rp.Addr().ID,
|
|
ListenAddr: rp.listener.Addr().String(),
|
|
Network: "testing",
|
|
Version: "1.2.3-rc0-deadbeef",
|
|
Channels: rp.channels,
|
|
Moniker: "remote_peer",
|
|
}
|
|
}
|