peer_test.go 8.93 KB
Newer Older
1
// Copyright 2014 The go-ethereum Authors
2
// This file is part of the go-ethereum library.
3
//
4
// The go-ethereum library is free software: you can redistribute it and/or modify
5 6 7 8
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
9
// The go-ethereum library is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 13 14
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
15
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16

zelig's avatar
zelig committed
17 18
package p2p

19
import (
20
	"errors"
21
	"fmt"
22
	"math/rand"
23
	"net"
Felix Lange's avatar
Felix Lange committed
24
	"reflect"
25 26 27 28 29 30 31 32 33 34 35 36 37
	"testing"
	"time"
)

var discard = Protocol{
	Name:   "discard",
	Length: 1,
	Run: func(p *Peer, rw MsgReadWriter) error {
		for {
			msg, err := rw.ReadMsg()
			if err != nil {
				return err
			}
38
			fmt.Printf("discarding %d\n", msg.Code)
39 40 41 42 43 44 45
			if err = msg.Discard(); err != nil {
				return err
			}
		}
	},
}

46
func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) {
47
	fd1, fd2 := net.Pipe()
48 49
	c1 := &conn{fd: fd1, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd1)}
	c2 := &conn{fd: fd2, node: newNode(randomID(), nil), transport: newTestTransport(&newkey().PublicKey, fd2)}
50
	for _, p := range protos {
51 52
		c1.caps = append(c1.caps, p.cap())
		c2.caps = append(c2.caps, p.cap())
53 54
	}

55
	peer := newPeer(c1, protos)
56 57 58 59 60
	errc := make(chan error, 1)
	go func() {
		_, err := peer.run()
		errc <- err
	}()
61

62 63
	closer := func() { c2.close(errors.New("close func called")) }
	return closer, c2, peer, errc
64 65 66 67 68 69 70
}

func TestPeerProtoReadMsg(t *testing.T) {
	proto := Protocol{
		Name:   "a",
		Length: 5,
		Run: func(peer *Peer, rw MsgReadWriter) error {
71
			if err := ExpectMsg(rw, 2, []uint{1}); err != nil {
72
				t.Error(err)
73
			}
74
			if err := ExpectMsg(rw, 3, []uint{2}); err != nil {
75
				t.Error(err)
76
			}
77
			if err := ExpectMsg(rw, 4, []uint{3}); err != nil {
78
				t.Error(err)
79 80 81 82 83
			}
			return nil
		},
	}

84
	closer, rw, _, errc := testPeer([]Protocol{proto})
85
	defer closer()
86

87 88 89
	Send(rw, baseProtocolLength+2, []uint{1})
	Send(rw, baseProtocolLength+3, []uint{2})
	Send(rw, baseProtocolLength+4, []uint{3})
90

91 92
	select {
	case err := <-errc:
93 94 95
		if err != errProtocolReturned {
			t.Errorf("peer returned error: %v", err)
		}
96 97 98 99 100 101 102 103 104 105
	case <-time.After(2 * time.Second):
		t.Errorf("receive timeout")
	}
}

func TestPeerProtoEncodeMsg(t *testing.T) {
	proto := Protocol{
		Name:   "a",
		Length: 2,
		Run: func(peer *Peer, rw MsgReadWriter) error {
106
			if err := SendItems(rw, 2); err == nil {
107 108
				t.Error("expected error for out-of-range msg code, got nil")
			}
109
			if err := SendItems(rw, 1, "foo", "bar"); err != nil {
110 111 112 113 114
				t.Errorf("write error: %v", err)
			}
			return nil
		},
	}
115
	closer, rw, _, _ := testPeer([]Protocol{proto})
116
	defer closer()
117

118
	if err := ExpectMsg(rw, 17, []string{"foo", "bar"}); err != nil {
119
		t.Error(err)
obscuren's avatar
obscuren committed
120
	}
121 122
}

123
func TestPeerPing(t *testing.T) {
124
	closer, rw, _, _ := testPeer(nil)
125
	defer closer()
126
	if err := SendItems(rw, pingMsg); err != nil {
127 128
		t.Fatal(err)
	}
129
	if err := ExpectMsg(rw, pongMsg, nil); err != nil {
130 131 132
		t.Error(err)
	}
}
133

134
func TestPeerDisconnect(t *testing.T) {
135
	closer, rw, _, disc := testPeer(nil)
136
	defer closer()
137
	if err := SendItems(rw, discMsg, DiscQuitting); err != nil {
138
		t.Fatal(err)
139
	}
140 141
	select {
	case reason := <-disc:
142 143
		if reason != DiscQuitting {
			t.Errorf("run returned wrong reason: got %v, want %v", reason, DiscQuitting)
144 145 146
		}
	case <-time.After(500 * time.Millisecond):
		t.Error("peer did not return")
147 148
	}
}
Felix Lange's avatar
Felix Lange committed
149

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
// This test is supposed to verify that Peer can reliably handle
// multiple causes of disconnection occurring at the same time.
func TestPeerDisconnectRace(t *testing.T) {
	maybe := func() bool { return rand.Intn(1) == 1 }

	for i := 0; i < 1000; i++ {
		protoclose := make(chan error)
		protodisc := make(chan DiscReason)
		closer, rw, p, disc := testPeer([]Protocol{
			{
				Name:   "closereq",
				Run:    func(p *Peer, rw MsgReadWriter) error { return <-protoclose },
				Length: 1,
			},
			{
				Name:   "disconnect",
				Run:    func(p *Peer, rw MsgReadWriter) error { p.Disconnect(<-protodisc); return nil },
				Length: 1,
			},
		})

		// Simulate incoming messages.
		go SendItems(rw, baseProtocolLength+1)
		go SendItems(rw, baseProtocolLength+2)
		// Close the network connection.
		go closer()
		// Make protocol "closereq" return.
		protoclose <- errors.New("protocol closed")
		// Make protocol "disconnect" call peer.Disconnect
		protodisc <- DiscAlreadyConnected
		// In some cases, simulate something else calling peer.Disconnect.
		if maybe() {
			go p.Disconnect(DiscInvalidIdentity)
		}
		// In some cases, simulate remote requesting a disconnect.
		if maybe() {
			go SendItems(rw, discMsg, DiscQuitting)
		}

		select {
		case <-disc:
		case <-time.After(2 * time.Second):
			// Peer.run should return quickly. If it doesn't the Peer
			// goroutines are probably deadlocked. Call panic in order to
			// show the stacks.
			panic("Peer.run took to long to return.")
		}
	}
}

200 201 202 203 204 205 206
func TestNewPeer(t *testing.T) {
	name := "nodename"
	caps := []Cap{{"foo", 2}, {"bar", 3}}
	id := randomID()
	p := NewPeer(id, name, caps)
	if p.ID() != id {
		t.Errorf("ID mismatch: got %v, expected %v", p.ID(), id)
207
	}
208 209
	if p.Name() != name {
		t.Errorf("Name mismatch: got %v, expected %v", p.Name(), name)
210
	}
211 212
	if !reflect.DeepEqual(p.Caps(), caps) {
		t.Errorf("Caps mismatch: got %v, expected %v", p.Caps(), caps)
213 214
	}

215 216
	p.Disconnect(DiscAlreadyConnected) // Should not hang
}
217 218 219

func TestMatchProtocols(t *testing.T) {
	tests := []struct {
220 221
		Remote []Cap
		Local  []Protocol
222 223 224
		Match  map[string]protoRW
	}{
		{
225 226
			// No remote capabilities
			Local: []Protocol{{Name: "a"}},
227 228
		},
		{
229 230
			// No local protocols
			Remote: []Cap{{Name: "a"}},
231 232 233
		},
		{
			// No mutual protocols
234 235
			Remote: []Cap{{Name: "a"}},
			Local:  []Protocol{{Name: "b"}},
236 237 238
		},
		{
			// Some matches, some differences
239 240
			Remote: []Cap{{Name: "local"}, {Name: "match1"}, {Name: "match2"}},
			Local:  []Protocol{{Name: "match1"}, {Name: "match2"}, {Name: "remote"}},
241 242 243 244
			Match:  map[string]protoRW{"match1": {Protocol: Protocol{Name: "match1"}}, "match2": {Protocol: Protocol{Name: "match2"}}},
		},
		{
			// Various alphabetical ordering
245 246
			Remote: []Cap{{Name: "aa"}, {Name: "ab"}, {Name: "bb"}, {Name: "ba"}},
			Local:  []Protocol{{Name: "ba"}, {Name: "bb"}, {Name: "ab"}, {Name: "aa"}},
247 248 249 250
			Match:  map[string]protoRW{"aa": {Protocol: Protocol{Name: "aa"}}, "ab": {Protocol: Protocol{Name: "ab"}}, "ba": {Protocol: Protocol{Name: "ba"}}, "bb": {Protocol: Protocol{Name: "bb"}}},
		},
		{
			// No mutual versions
251 252
			Remote: []Cap{{Version: 1}},
			Local:  []Protocol{{Version: 2}},
253 254 255
		},
		{
			// Multiple versions, single common
256 257
			Remote: []Cap{{Version: 1}, {Version: 2}},
			Local:  []Protocol{{Version: 2}, {Version: 3}},
258 259 260 261
			Match:  map[string]protoRW{"": {Protocol: Protocol{Version: 2}}},
		},
		{
			// Multiple versions, multiple common
262 263
			Remote: []Cap{{Version: 1}, {Version: 2}, {Version: 3}, {Version: 4}},
			Local:  []Protocol{{Version: 2}, {Version: 3}},
264 265 266 267
			Match:  map[string]protoRW{"": {Protocol: Protocol{Version: 3}}},
		},
		{
			// Various version orderings
268 269
			Remote: []Cap{{Version: 4}, {Version: 1}, {Version: 3}, {Version: 2}},
			Local:  []Protocol{{Version: 2}, {Version: 3}, {Version: 1}},
270 271 272 273
			Match:  map[string]protoRW{"": {Protocol: Protocol{Version: 3}}},
		},
		{
			// Versions overriding sub-protocol lengths
274 275
			Remote: []Cap{{Version: 1}, {Version: 2}, {Version: 3}, {Name: "a"}},
			Local:  []Protocol{{Version: 1, Length: 1}, {Version: 2, Length: 2}, {Version: 3, Length: 3}, {Name: "a"}},
276 277 278 279 280
			Match:  map[string]protoRW{"": {Protocol: Protocol{Version: 3}}, "a": {Protocol: Protocol{Name: "a"}, offset: 3}},
		},
	}

	for i, tt := range tests {
281
		result := matchProtocols(tt.Local, tt.Remote, nil)
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
		if len(result) != len(tt.Match) {
			t.Errorf("test %d: negotiation mismatch: have %v, want %v", i, len(result), len(tt.Match))
			continue
		}
		// Make sure all negotiated protocols are needed and correct
		for name, proto := range result {
			match, ok := tt.Match[name]
			if !ok {
				t.Errorf("test %d, proto '%s': negotiated but shouldn't have", i, name)
				continue
			}
			if proto.Name != match.Name {
				t.Errorf("test %d, proto '%s': name mismatch: have %v, want %v", i, name, proto.Name, match.Name)
			}
			if proto.Version != match.Version {
				t.Errorf("test %d, proto '%s': version mismatch: have %v, want %v", i, name, proto.Version, match.Version)
			}
			if proto.offset-baseProtocolLength != match.offset {
				t.Errorf("test %d, proto '%s': offset mismatch: have %v, want %v", i, name, proto.offset-baseProtocolLength, match.offset)
			}
		}
		// Make sure no protocols missed negotiation
Felix Lange's avatar
Felix Lange committed
304
		for name := range tt.Match {
305 306 307 308 309 310 311
			if _, ok := result[name]; !ok {
				t.Errorf("test %d, proto '%s': not negotiated, should have", i, name)
				continue
			}
		}
	}
}