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", } }