github.com/lukso-network/go-ethereum@v1.8.22/p2p/protocols/protocol_test.go (about) 1 // Copyright 2017 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 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 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 // GNU Lesser General Public License for more details. 13 // 14 // 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 17 package protocols 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "testing" 25 "time" 26 27 "github.com/ethereum/go-ethereum/rlp" 28 29 "github.com/ethereum/go-ethereum/p2p" 30 "github.com/ethereum/go-ethereum/p2p/enode" 31 "github.com/ethereum/go-ethereum/p2p/simulations/adapters" 32 p2ptest "github.com/ethereum/go-ethereum/p2p/testing" 33 ) 34 35 // handshake message type 36 type hs0 struct { 37 C uint 38 } 39 40 // message to kill/drop the peer with nodeID 41 type kill struct { 42 C enode.ID 43 } 44 45 // message to drop connection 46 type drop struct { 47 } 48 49 /// protoHandshake represents module-independent aspects of the protocol and is 50 // the first message peers send and receive as part the initial exchange 51 type protoHandshake struct { 52 Version uint // local and remote peer should have identical version 53 NetworkID string // local and remote peer should have identical network id 54 } 55 56 // checkProtoHandshake verifies local and remote protoHandshakes match 57 func checkProtoHandshake(testVersion uint, testNetworkID string) func(interface{}) error { 58 return func(rhs interface{}) error { 59 remote := rhs.(*protoHandshake) 60 if remote.NetworkID != testNetworkID { 61 return fmt.Errorf("%s (!= %s)", remote.NetworkID, testNetworkID) 62 } 63 64 if remote.Version != testVersion { 65 return fmt.Errorf("%d (!= %d)", remote.Version, testVersion) 66 } 67 return nil 68 } 69 } 70 71 // newProtocol sets up a protocol 72 // the run function here demonstrates a typical protocol using peerPool, handshake 73 // and messages registered to handlers 74 func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) error { 75 spec := &Spec{ 76 Name: "test", 77 Version: 42, 78 MaxMsgSize: 10 * 1024, 79 Messages: []interface{}{ 80 protoHandshake{}, 81 hs0{}, 82 kill{}, 83 drop{}, 84 }, 85 } 86 return func(p *p2p.Peer, rw p2p.MsgReadWriter) error { 87 peer := NewPeer(p, rw, spec) 88 89 // initiate one-off protohandshake and check validity 90 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 91 defer cancel() 92 phs := &protoHandshake{42, "420"} 93 hsCheck := checkProtoHandshake(phs.Version, phs.NetworkID) 94 _, err := peer.Handshake(ctx, phs, hsCheck) 95 if err != nil { 96 return err 97 } 98 99 lhs := &hs0{42} 100 // module handshake demonstrating a simple repeatable exchange of same-type message 101 hs, err := peer.Handshake(ctx, lhs, nil) 102 if err != nil { 103 return err 104 } 105 106 if rmhs := hs.(*hs0); rmhs.C > lhs.C { 107 return fmt.Errorf("handshake mismatch remote %v > local %v", rmhs.C, lhs.C) 108 } 109 110 handle := func(ctx context.Context, msg interface{}) error { 111 switch msg := msg.(type) { 112 113 case *protoHandshake: 114 return errors.New("duplicate handshake") 115 116 case *hs0: 117 rhs := msg 118 if rhs.C > lhs.C { 119 return fmt.Errorf("handshake mismatch remote %v > local %v", rhs.C, lhs.C) 120 } 121 lhs.C += rhs.C 122 return peer.Send(ctx, lhs) 123 124 case *kill: 125 // demonstrates use of peerPool, killing another peer connection as a response to a message 126 id := msg.C 127 pp.Get(id).Drop(errors.New("killed")) 128 return nil 129 130 case *drop: 131 // for testing we can trigger self induced disconnect upon receiving drop message 132 return errors.New("dropped") 133 134 default: 135 return fmt.Errorf("unknown message type: %T", msg) 136 } 137 } 138 139 pp.Add(peer) 140 defer pp.Remove(peer) 141 return peer.Run(handle) 142 } 143 } 144 145 func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { 146 conf := adapters.RandomNodeConfig() 147 return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp)) 148 } 149 150 func protoHandshakeExchange(id enode.ID, proto *protoHandshake) []p2ptest.Exchange { 151 152 return []p2ptest.Exchange{ 153 { 154 Expects: []p2ptest.Expect{ 155 { 156 Code: 0, 157 Msg: &protoHandshake{42, "420"}, 158 Peer: id, 159 }, 160 }, 161 }, 162 { 163 Triggers: []p2ptest.Trigger{ 164 { 165 Code: 0, 166 Msg: proto, 167 Peer: id, 168 }, 169 }, 170 }, 171 } 172 } 173 174 func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) { 175 pp := p2ptest.NewTestPeerPool() 176 s := protocolTester(t, pp) 177 // TODO: make this more than one handshake 178 node := s.Nodes[0] 179 if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { 180 t.Fatal(err) 181 } 182 var disconnects []*p2ptest.Disconnect 183 for i, err := range errs { 184 disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) 185 } 186 if err := s.TestDisconnected(disconnects...); err != nil { 187 t.Fatal(err) 188 } 189 } 190 191 type dummyHook struct { 192 peer *Peer 193 size uint32 194 msg interface{} 195 send bool 196 err error 197 waitC chan struct{} 198 } 199 200 type dummyMsg struct { 201 Content string 202 } 203 204 func (d *dummyHook) Send(peer *Peer, size uint32, msg interface{}) error { 205 d.peer = peer 206 d.size = size 207 d.msg = msg 208 d.send = true 209 return d.err 210 } 211 212 func (d *dummyHook) Receive(peer *Peer, size uint32, msg interface{}) error { 213 d.peer = peer 214 d.size = size 215 d.msg = msg 216 d.send = false 217 d.waitC <- struct{}{} 218 return d.err 219 } 220 221 func TestProtocolHook(t *testing.T) { 222 testHook := &dummyHook{ 223 waitC: make(chan struct{}, 1), 224 } 225 spec := &Spec{ 226 Name: "test", 227 Version: 42, 228 MaxMsgSize: 10 * 1024, 229 Messages: []interface{}{ 230 dummyMsg{}, 231 }, 232 Hook: testHook, 233 } 234 235 runFunc := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { 236 peer := NewPeer(p, rw, spec) 237 ctx := context.TODO() 238 err := peer.Send(ctx, &dummyMsg{ 239 Content: "handshake"}) 240 241 if err != nil { 242 t.Fatal(err) 243 } 244 245 handle := func(ctx context.Context, msg interface{}) error { 246 return nil 247 } 248 249 return peer.Run(handle) 250 } 251 252 conf := adapters.RandomNodeConfig() 253 tester := p2ptest.NewProtocolTester(t, conf.ID, 2, runFunc) 254 err := tester.TestExchanges(p2ptest.Exchange{ 255 Expects: []p2ptest.Expect{ 256 { 257 Code: 0, 258 Msg: &dummyMsg{Content: "handshake"}, 259 Peer: tester.Nodes[0].ID(), 260 }, 261 }, 262 }) 263 if err != nil { 264 t.Fatal(err) 265 } 266 if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "handshake" { 267 t.Fatal("Expected msg to be set, but it is not") 268 } 269 if !testHook.send { 270 t.Fatal("Expected a send message, but it is not") 271 } 272 if testHook.peer == nil || testHook.peer.ID() != tester.Nodes[0].ID() { 273 t.Fatal("Expected peer ID to be set correctly, but it is not") 274 } 275 if testHook.size != 11 { //11 is the length of the encoded message 276 t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) 277 } 278 279 err = tester.TestExchanges(p2ptest.Exchange{ 280 Triggers: []p2ptest.Trigger{ 281 { 282 Code: 0, 283 Msg: &dummyMsg{Content: "response"}, 284 Peer: tester.Nodes[1].ID(), 285 }, 286 }, 287 }) 288 289 <-testHook.waitC 290 291 if err != nil { 292 t.Fatal(err) 293 } 294 if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "response" { 295 t.Fatal("Expected msg to be set, but it is not") 296 } 297 if testHook.send { 298 t.Fatal("Expected a send message, but it is not") 299 } 300 if testHook.peer == nil || testHook.peer.ID() != tester.Nodes[1].ID() { 301 t.Fatal("Expected peer ID to be set correctly, but it is not") 302 } 303 if testHook.size != 10 { //11 is the length of the encoded message 304 t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) 305 } 306 307 testHook.err = fmt.Errorf("dummy error") 308 err = tester.TestExchanges(p2ptest.Exchange{ 309 Triggers: []p2ptest.Trigger{ 310 { 311 Code: 0, 312 Msg: &dummyMsg{Content: "response"}, 313 Peer: tester.Nodes[1].ID(), 314 }, 315 }, 316 }) 317 318 <-testHook.waitC 319 320 time.Sleep(100 * time.Millisecond) 321 err = tester.TestDisconnected(&p2ptest.Disconnect{Peer: tester.Nodes[1].ID(), Error: testHook.err}) 322 if err != nil { 323 t.Fatalf("Expected a specific disconnect error, but got different one: %v", err) 324 } 325 326 } 327 328 //We need to test that if the hook is not defined, then message infrastructure 329 //(send,receive) still works 330 func TestNoHook(t *testing.T) { 331 //create a test spec 332 spec := createTestSpec() 333 //a random node 334 id := adapters.RandomNodeConfig().ID 335 //a peer 336 p := p2p.NewPeer(id, "testPeer", nil) 337 rw := &dummyRW{} 338 peer := NewPeer(p, rw, spec) 339 ctx := context.TODO() 340 msg := &perBytesMsgSenderPays{Content: "testBalance"} 341 //send a message 342 err := peer.Send(ctx, msg) 343 if err != nil { 344 t.Fatal(err) 345 } 346 //simulate receiving a message 347 rw.msg = msg 348 peer.handleIncoming(func(ctx context.Context, msg interface{}) error { 349 return nil 350 }) 351 //all should just work and not result in any error 352 } 353 354 func TestProtoHandshakeVersionMismatch(t *testing.T) { 355 runProtoHandshake(t, &protoHandshake{41, "420"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 41 (!= 42)").Error())) 356 } 357 358 func TestProtoHandshakeNetworkIDMismatch(t *testing.T) { 359 runProtoHandshake(t, &protoHandshake{42, "421"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 421 (!= 420)").Error())) 360 } 361 362 func TestProtoHandshakeSuccess(t *testing.T) { 363 runProtoHandshake(t, &protoHandshake{42, "420"}) 364 } 365 366 func moduleHandshakeExchange(id enode.ID, resp uint) []p2ptest.Exchange { 367 368 return []p2ptest.Exchange{ 369 { 370 Expects: []p2ptest.Expect{ 371 { 372 Code: 1, 373 Msg: &hs0{42}, 374 Peer: id, 375 }, 376 }, 377 }, 378 { 379 Triggers: []p2ptest.Trigger{ 380 { 381 Code: 1, 382 Msg: &hs0{resp}, 383 Peer: id, 384 }, 385 }, 386 }, 387 } 388 } 389 390 func runModuleHandshake(t *testing.T, resp uint, errs ...error) { 391 pp := p2ptest.NewTestPeerPool() 392 s := protocolTester(t, pp) 393 node := s.Nodes[0] 394 if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { 395 t.Fatal(err) 396 } 397 if err := s.TestExchanges(moduleHandshakeExchange(node.ID(), resp)...); err != nil { 398 t.Fatal(err) 399 } 400 var disconnects []*p2ptest.Disconnect 401 for i, err := range errs { 402 disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) 403 } 404 if err := s.TestDisconnected(disconnects...); err != nil { 405 t.Fatal(err) 406 } 407 } 408 409 func TestModuleHandshakeError(t *testing.T) { 410 runModuleHandshake(t, 43, fmt.Errorf("handshake mismatch remote 43 > local 42")) 411 } 412 413 func TestModuleHandshakeSuccess(t *testing.T) { 414 runModuleHandshake(t, 42) 415 } 416 417 // testing complex interactions over multiple peers, relaying, dropping 418 func testMultiPeerSetup(a, b enode.ID) []p2ptest.Exchange { 419 420 return []p2ptest.Exchange{ 421 { 422 Label: "primary handshake", 423 Expects: []p2ptest.Expect{ 424 { 425 Code: 0, 426 Msg: &protoHandshake{42, "420"}, 427 Peer: a, 428 }, 429 { 430 Code: 0, 431 Msg: &protoHandshake{42, "420"}, 432 Peer: b, 433 }, 434 }, 435 }, 436 { 437 Label: "module handshake", 438 Triggers: []p2ptest.Trigger{ 439 { 440 Code: 0, 441 Msg: &protoHandshake{42, "420"}, 442 Peer: a, 443 }, 444 { 445 Code: 0, 446 Msg: &protoHandshake{42, "420"}, 447 Peer: b, 448 }, 449 }, 450 Expects: []p2ptest.Expect{ 451 { 452 Code: 1, 453 Msg: &hs0{42}, 454 Peer: a, 455 }, 456 { 457 Code: 1, 458 Msg: &hs0{42}, 459 Peer: b, 460 }, 461 }, 462 }, 463 464 {Label: "alternative module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{41}, Peer: a}, 465 {Code: 1, Msg: &hs0{41}, Peer: b}}}, 466 {Label: "repeated module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{1}, Peer: a}}}, 467 {Label: "receiving repeated module handshake", Expects: []p2ptest.Expect{{Code: 1, Msg: &hs0{43}, Peer: a}}}} 468 } 469 470 func runMultiplePeers(t *testing.T, peer int, errs ...error) { 471 pp := p2ptest.NewTestPeerPool() 472 s := protocolTester(t, pp) 473 474 if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); err != nil { 475 t.Fatal(err) 476 } 477 // after some exchanges of messages, we can test state changes 478 // here this is simply demonstrated by the peerPool 479 // after the handshake negotiations peers must be added to the pool 480 // time.Sleep(1) 481 tick := time.NewTicker(10 * time.Millisecond) 482 timeout := time.NewTimer(1 * time.Second) 483 WAIT: 484 for { 485 select { 486 case <-tick.C: 487 if pp.Has(s.Nodes[0].ID()) { 488 break WAIT 489 } 490 case <-timeout.C: 491 t.Fatal("timeout") 492 } 493 } 494 if !pp.Has(s.Nodes[1].ID()) { 495 t.Fatalf("missing peer test-1: %v (%v)", pp, s.Nodes) 496 } 497 498 // peer 0 sends kill request for peer with index <peer> 499 err := s.TestExchanges(p2ptest.Exchange{ 500 Triggers: []p2ptest.Trigger{ 501 { 502 Code: 2, 503 Msg: &kill{s.Nodes[peer].ID()}, 504 Peer: s.Nodes[0].ID(), 505 }, 506 }, 507 }) 508 509 if err != nil { 510 t.Fatal(err) 511 } 512 513 // the peer not killed sends a drop request 514 err = s.TestExchanges(p2ptest.Exchange{ 515 Triggers: []p2ptest.Trigger{ 516 { 517 Code: 3, 518 Msg: &drop{}, 519 Peer: s.Nodes[(peer+1)%2].ID(), 520 }, 521 }, 522 }) 523 524 if err != nil { 525 t.Fatal(err) 526 } 527 528 // check the actual discconnect errors on the individual peers 529 var disconnects []*p2ptest.Disconnect 530 for i, err := range errs { 531 disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) 532 } 533 if err := s.TestDisconnected(disconnects...); err != nil { 534 t.Fatal(err) 535 } 536 // test if disconnected peers have been removed from peerPool 537 if pp.Has(s.Nodes[peer].ID()) { 538 t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.Nodes) 539 } 540 541 } 542 func XTestMultiplePeersDropSelf(t *testing.T) { 543 runMultiplePeers(t, 0, 544 fmt.Errorf("subprotocol error"), 545 fmt.Errorf("Message handler error: (msg code 3): dropped"), 546 ) 547 } 548 549 func XTestMultiplePeersDropOther(t *testing.T) { 550 runMultiplePeers(t, 1, 551 fmt.Errorf("Message handler error: (msg code 3): dropped"), 552 fmt.Errorf("subprotocol error"), 553 ) 554 } 555 556 //dummy implementation of a MsgReadWriter 557 //this allows for quick and easy unit tests without 558 //having to build up the complete protocol 559 type dummyRW struct { 560 msg interface{} 561 size uint32 562 code uint64 563 } 564 565 func (d *dummyRW) WriteMsg(msg p2p.Msg) error { 566 return nil 567 } 568 569 func (d *dummyRW) ReadMsg() (p2p.Msg, error) { 570 enc := bytes.NewReader(d.getDummyMsg()) 571 return p2p.Msg{ 572 Code: d.code, 573 Size: d.size, 574 Payload: enc, 575 ReceivedAt: time.Now(), 576 }, nil 577 } 578 579 func (d *dummyRW) getDummyMsg() []byte { 580 r, _ := rlp.EncodeToBytes(d.msg) 581 var b bytes.Buffer 582 wmsg := WrappedMsg{ 583 Context: b.Bytes(), 584 Size: uint32(len(r)), 585 Payload: r, 586 } 587 rr, _ := rlp.EncodeToBytes(wmsg) 588 d.size = uint32(len(rr)) 589 return rr 590 }