123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- // Copyright 2017 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // 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.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- package protocols
- import (
- "context"
- "errors"
- "fmt"
- "testing"
- "time"
- "github.com/ethereum/go-ethereum/p2p"
- "github.com/ethereum/go-ethereum/p2p/discover"
- "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
- p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
- )
- // handshake message type
- type hs0 struct {
- C uint
- }
- // message to kill/drop the peer with nodeID
- type kill struct {
- C discover.NodeID
- }
- // message to drop connection
- type drop struct {
- }
- /// protoHandshake represents module-independent aspects of the protocol and is
- // the first message peers send and receive as part the initial exchange
- type protoHandshake struct {
- Version uint // local and remote peer should have identical version
- NetworkID string // local and remote peer should have identical network id
- }
- // checkProtoHandshake verifies local and remote protoHandshakes match
- func checkProtoHandshake(testVersion uint, testNetworkID string) func(interface{}) error {
- return func(rhs interface{}) error {
- remote := rhs.(*protoHandshake)
- if remote.NetworkID != testNetworkID {
- return fmt.Errorf("%s (!= %s)", remote.NetworkID, testNetworkID)
- }
- if remote.Version != testVersion {
- return fmt.Errorf("%d (!= %d)", remote.Version, testVersion)
- }
- return nil
- }
- }
- // newProtocol sets up a protocol
- // the run function here demonstrates a typical protocol using peerPool, handshake
- // and messages registered to handlers
- func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) error {
- spec := &Spec{
- Name: "test",
- Version: 42,
- MaxMsgSize: 10 * 1024,
- Messages: []interface{}{
- protoHandshake{},
- hs0{},
- kill{},
- drop{},
- },
- }
- return func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
- peer := NewPeer(p, rw, spec)
- // initiate one-off protohandshake and check validity
- ctx, cancel := context.WithTimeout(context.Background(), time.Second)
- defer cancel()
- phs := &protoHandshake{42, "420"}
- hsCheck := checkProtoHandshake(phs.Version, phs.NetworkID)
- _, err := peer.Handshake(ctx, phs, hsCheck)
- if err != nil {
- return err
- }
- lhs := &hs0{42}
- // module handshake demonstrating a simple repeatable exchange of same-type message
- hs, err := peer.Handshake(ctx, lhs, nil)
- if err != nil {
- return err
- }
- if rmhs := hs.(*hs0); rmhs.C > lhs.C {
- return fmt.Errorf("handshake mismatch remote %v > local %v", rmhs.C, lhs.C)
- }
- handle := func(msg interface{}) error {
- switch msg := msg.(type) {
- case *protoHandshake:
- return errors.New("duplicate handshake")
- case *hs0:
- rhs := msg
- if rhs.C > lhs.C {
- return fmt.Errorf("handshake mismatch remote %v > local %v", rhs.C, lhs.C)
- }
- lhs.C += rhs.C
- return peer.Send(lhs)
- case *kill:
- // demonstrates use of peerPool, killing another peer connection as a response to a message
- id := msg.C
- pp.Get(id).Drop(errors.New("killed"))
- return nil
- case *drop:
- // for testing we can trigger self induced disconnect upon receiving drop message
- return errors.New("dropped")
- default:
- return fmt.Errorf("unknown message type: %T", msg)
- }
- }
- pp.Add(peer)
- defer pp.Remove(peer)
- return peer.Run(handle)
- }
- }
- func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester {
- conf := adapters.RandomNodeConfig()
- return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp))
- }
- func protoHandshakeExchange(id discover.NodeID, proto *protoHandshake) []p2ptest.Exchange {
- return []p2ptest.Exchange{
- {
- Expects: []p2ptest.Expect{
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: id,
- },
- },
- },
- {
- Triggers: []p2ptest.Trigger{
- {
- Code: 0,
- Msg: proto,
- Peer: id,
- },
- },
- },
- }
- }
- func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) {
- pp := p2ptest.NewTestPeerPool()
- s := protocolTester(t, pp)
- // TODO: make this more than one handshake
- id := s.IDs[0]
- if err := s.TestExchanges(protoHandshakeExchange(id, proto)...); err != nil {
- t.Fatal(err)
- }
- var disconnects []*p2ptest.Disconnect
- for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
- }
- if err := s.TestDisconnected(disconnects...); err != nil {
- t.Fatal(err)
- }
- }
- func TestProtoHandshakeVersionMismatch(t *testing.T) {
- runProtoHandshake(t, &protoHandshake{41, "420"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 41 (!= 42)").Error()))
- }
- func TestProtoHandshakeNetworkIDMismatch(t *testing.T) {
- runProtoHandshake(t, &protoHandshake{42, "421"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 421 (!= 420)").Error()))
- }
- func TestProtoHandshakeSuccess(t *testing.T) {
- runProtoHandshake(t, &protoHandshake{42, "420"})
- }
- func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange {
- return []p2ptest.Exchange{
- {
- Expects: []p2ptest.Expect{
- {
- Code: 1,
- Msg: &hs0{42},
- Peer: id,
- },
- },
- },
- {
- Triggers: []p2ptest.Trigger{
- {
- Code: 1,
- Msg: &hs0{resp},
- Peer: id,
- },
- },
- },
- }
- }
- func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
- pp := p2ptest.NewTestPeerPool()
- s := protocolTester(t, pp)
- id := s.IDs[0]
- if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil {
- t.Fatal(err)
- }
- if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil {
- t.Fatal(err)
- }
- var disconnects []*p2ptest.Disconnect
- for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
- }
- if err := s.TestDisconnected(disconnects...); err != nil {
- t.Fatal(err)
- }
- }
- func TestModuleHandshakeError(t *testing.T) {
- runModuleHandshake(t, 43, fmt.Errorf("handshake mismatch remote 43 > local 42"))
- }
- func TestModuleHandshakeSuccess(t *testing.T) {
- runModuleHandshake(t, 42)
- }
- // testing complex interactions over multiple peers, relaying, dropping
- func testMultiPeerSetup(a, b discover.NodeID) []p2ptest.Exchange {
- return []p2ptest.Exchange{
- {
- Label: "primary handshake",
- Expects: []p2ptest.Expect{
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: a,
- },
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: b,
- },
- },
- },
- {
- Label: "module handshake",
- Triggers: []p2ptest.Trigger{
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: a,
- },
- {
- Code: 0,
- Msg: &protoHandshake{42, "420"},
- Peer: b,
- },
- },
- Expects: []p2ptest.Expect{
- {
- Code: 1,
- Msg: &hs0{42},
- Peer: a,
- },
- {
- Code: 1,
- Msg: &hs0{42},
- Peer: b,
- },
- },
- },
- {Label: "alternative module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{41}, Peer: a},
- {Code: 1, Msg: &hs0{41}, Peer: b}}},
- {Label: "repeated module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{1}, Peer: a}}},
- {Label: "receiving repeated module handshake", Expects: []p2ptest.Expect{{Code: 1, Msg: &hs0{43}, Peer: a}}}}
- }
- func runMultiplePeers(t *testing.T, peer int, errs ...error) {
- pp := p2ptest.NewTestPeerPool()
- s := protocolTester(t, pp)
- if err := s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...); err != nil {
- t.Fatal(err)
- }
- // after some exchanges of messages, we can test state changes
- // here this is simply demonstrated by the peerPool
- // after the handshake negotiations peers must be added to the pool
- // time.Sleep(1)
- tick := time.NewTicker(10 * time.Millisecond)
- timeout := time.NewTimer(1 * time.Second)
- WAIT:
- for {
- select {
- case <-tick.C:
- if pp.Has(s.IDs[0]) {
- break WAIT
- }
- case <-timeout.C:
- t.Fatal("timeout")
- }
- }
- if !pp.Has(s.IDs[1]) {
- t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs)
- }
- // peer 0 sends kill request for peer with index <peer>
- err := s.TestExchanges(p2ptest.Exchange{
- Triggers: []p2ptest.Trigger{
- {
- Code: 2,
- Msg: &kill{s.IDs[peer]},
- Peer: s.IDs[0],
- },
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- // the peer not killed sends a drop request
- err = s.TestExchanges(p2ptest.Exchange{
- Triggers: []p2ptest.Trigger{
- {
- Code: 3,
- Msg: &drop{},
- Peer: s.IDs[(peer+1)%2],
- },
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- // check the actual discconnect errors on the individual peers
- var disconnects []*p2ptest.Disconnect
- for i, err := range errs {
- disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
- }
- if err := s.TestDisconnected(disconnects...); err != nil {
- t.Fatal(err)
- }
- // test if disconnected peers have been removed from peerPool
- if pp.Has(s.IDs[peer]) {
- t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.IDs)
- }
- }
- func TestMultiplePeersDropSelf(t *testing.T) {
- runMultiplePeers(t, 0,
- fmt.Errorf("subprotocol error"),
- fmt.Errorf("Message handler error: (msg code 3): dropped"),
- )
- }
- func TestMultiplePeersDropOther(t *testing.T) {
- runMultiplePeers(t, 1,
- fmt.Errorf("Message handler error: (msg code 3): dropped"),
- fmt.Errorf("subprotocol error"),
- )
- }
|