diff options
Diffstat (limited to 'p2p/protocols/protocol_test.go')
-rw-r--r-- | p2p/protocols/protocol_test.go | 624 |
1 files changed, 0 insertions, 624 deletions
diff --git a/p2p/protocols/protocol_test.go b/p2p/protocols/protocol_test.go deleted file mode 100644 index 00526b97a..000000000 --- a/p2p/protocols/protocol_test.go +++ /dev/null @@ -1,624 +0,0 @@ -// 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 ( - "bytes" - "context" - "errors" - "fmt" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/rlp" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "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 enode.ID -} - -// 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(ctx context.Context, 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(ctx, lhs) - - case *kill: - // demonstrates use of peerPool, killing another peer connection as a response to a message - id := msg.C - pp.Get(id).Drop() - 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(pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester { - prvkey, err := crypto.GenerateKey() - if err != nil { - panic(err) - } - return p2ptest.NewProtocolTester(prvkey, 2, newProtocol(pp)) -} - -func protoHandshakeExchange(id enode.ID, 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) { - t.Helper() - pp := p2ptest.NewTestPeerPool() - s := protocolTester(pp) - defer s.Stop() - - // TODO: make this more than one handshake - node := s.Nodes[0] - if err := s.TestExchanges(protoHandshakeExchange(node.ID(), proto)...); err != nil { - t.Fatal(err) - } - var disconnects []*p2ptest.Disconnect - for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), Error: err}) - } - if err := s.TestDisconnected(disconnects...); err != nil { - t.Fatal(err) - } -} - -type dummyHook struct { - peer *Peer - size uint32 - msg interface{} - send bool - err error - waitC chan struct{} - mu sync.Mutex -} - -type dummyMsg struct { - Content string -} - -func (d *dummyHook) Send(peer *Peer, size uint32, msg interface{}) error { - d.mu.Lock() - defer d.mu.Unlock() - - d.peer = peer - d.size = size - d.msg = msg - d.send = true - return d.err -} - -func (d *dummyHook) Receive(peer *Peer, size uint32, msg interface{}) error { - d.mu.Lock() - defer d.mu.Unlock() - - d.peer = peer - d.size = size - d.msg = msg - d.send = false - d.waitC <- struct{}{} - return d.err -} - -func TestProtocolHook(t *testing.T) { - testHook := &dummyHook{ - waitC: make(chan struct{}, 1), - } - spec := &Spec{ - Name: "test", - Version: 42, - MaxMsgSize: 10 * 1024, - Messages: []interface{}{ - dummyMsg{}, - }, - Hook: testHook, - } - - runFunc := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := NewPeer(p, rw, spec) - ctx := context.TODO() - err := peer.Send(ctx, &dummyMsg{ - Content: "handshake"}) - - if err != nil { - t.Fatal(err) - } - - handle := func(ctx context.Context, msg interface{}) error { - return nil - } - - return peer.Run(handle) - } - - prvkey, err := crypto.GenerateKey() - if err != nil { - panic(err) - } - tester := p2ptest.NewProtocolTester(prvkey, 2, runFunc) - defer tester.Stop() - err = tester.TestExchanges(p2ptest.Exchange{ - Expects: []p2ptest.Expect{ - { - Code: 0, - Msg: &dummyMsg{Content: "handshake"}, - Peer: tester.Nodes[0].ID(), - }, - }, - }) - if err != nil { - t.Fatal(err) - } - testHook.mu.Lock() - if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "handshake" { - t.Fatal("Expected msg to be set, but it is not") - } - if !testHook.send { - t.Fatal("Expected a send message, but it is not") - } - if testHook.peer == nil { - t.Fatal("Expected peer to be set, is nil") - } - if peerId := testHook.peer.ID(); peerId != tester.Nodes[0].ID() && peerId != tester.Nodes[1].ID() { - t.Fatalf("Expected peer ID to be set correctly, but it is not (got %v, exp %v or %v", peerId, tester.Nodes[0].ID(), tester.Nodes[1].ID()) - } - if testHook.size != 11 { //11 is the length of the encoded message - t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) - } - testHook.mu.Unlock() - - err = tester.TestExchanges(p2ptest.Exchange{ - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &dummyMsg{Content: "response"}, - Peer: tester.Nodes[1].ID(), - }, - }, - }) - - <-testHook.waitC - - if err != nil { - t.Fatal(err) - } - - testHook.mu.Lock() - if testHook.msg == nil || testHook.msg.(*dummyMsg).Content != "response" { - t.Fatal("Expected msg to be set, but it is not") - } - if testHook.send { - t.Fatal("Expected a send message, but it is not") - } - if testHook.peer == nil || testHook.peer.ID() != tester.Nodes[1].ID() { - t.Fatal("Expected peer ID to be set correctly, but it is not") - } - if testHook.size != 10 { //11 is the length of the encoded message - t.Fatalf("Expected size to be %d, but it is %d ", 1, testHook.size) - } - testHook.mu.Unlock() - - testHook.err = fmt.Errorf("dummy error") - err = tester.TestExchanges(p2ptest.Exchange{ - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &dummyMsg{Content: "response"}, - Peer: tester.Nodes[1].ID(), - }, - }, - }) - - <-testHook.waitC - - time.Sleep(100 * time.Millisecond) - err = tester.TestDisconnected(&p2ptest.Disconnect{Peer: tester.Nodes[1].ID(), Error: testHook.err}) - if err != nil { - t.Fatalf("Expected a specific disconnect error, but got different one: %v", err) - } -} - -//We need to test that if the hook is not defined, then message infrastructure -//(send,receive) still works -func TestNoHook(t *testing.T) { - //create a test spec - spec := createTestSpec() - //a random node - id := adapters.RandomNodeConfig().ID - //a peer - p := p2p.NewPeer(id, "testPeer", nil) - rw := &dummyRW{} - peer := NewPeer(p, rw, spec) - ctx := context.TODO() - msg := &perBytesMsgSenderPays{Content: "testBalance"} - //send a message - - if err := peer.Send(ctx, msg); err != nil { - t.Fatal(err) - } - //simulate receiving a message - rw.msg = msg - handler := func(ctx context.Context, msg interface{}) error { - return nil - } - - if err := peer.handleIncoming(handler); 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 enode.ID, 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) { - t.Helper() - pp := p2ptest.NewTestPeerPool() - s := protocolTester(pp) - defer s.Stop() - - node := s.Nodes[0] - if err := s.TestExchanges(protoHandshakeExchange(node.ID(), &protoHandshake{42, "420"})...); err != nil { - t.Fatal(err) - } - if err := s.TestExchanges(moduleHandshakeExchange(node.ID(), resp)...); err != nil { - t.Fatal(err) - } - var disconnects []*p2ptest.Disconnect - for i, err := range errs { - disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.Nodes[i].ID(), 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 enode.ID) []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) { - t.Helper() - pp := p2ptest.NewTestPeerPool() - s := protocolTester(pp) - defer s.Stop() - - if err := s.TestExchanges(testMultiPeerSetup(s.Nodes[0].ID(), s.Nodes[1].ID())...); 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.Nodes[0].ID()) { - break WAIT - } - case <-timeout.C: - t.Fatal("timeout") - } - } - if !pp.Has(s.Nodes[1].ID()) { - t.Fatalf("missing peer test-1: %v (%v)", pp, s.Nodes) - } - - // peer 0 sends kill request for peer with index <peer> - err := s.TestExchanges(p2ptest.Exchange{ - Triggers: []p2ptest.Trigger{ - { - Code: 2, - Msg: &kill{s.Nodes[peer].ID()}, - Peer: s.Nodes[0].ID(), - }, - }, - }) - - 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.Nodes[(peer+1)%2].ID(), - }, - }, - }) - - 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.Nodes[i].ID(), 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.Nodes[peer].ID()) { - t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.Nodes) - } - -} -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"), - ) -} - -//dummy implementation of a MsgReadWriter -//this allows for quick and easy unit tests without -//having to build up the complete protocol -type dummyRW struct { - msg interface{} - size uint32 - code uint64 -} - -func (d *dummyRW) WriteMsg(msg p2p.Msg) error { - return nil -} - -func (d *dummyRW) ReadMsg() (p2p.Msg, error) { - enc := bytes.NewReader(d.getDummyMsg()) - return p2p.Msg{ - Code: d.code, - Size: d.size, - Payload: enc, - ReceivedAt: time.Now(), - }, nil -} - -func (d *dummyRW) getDummyMsg() []byte { - r, _ := rlp.EncodeToBytes(d.msg) - var b bytes.Buffer - wmsg := WrappedMsg{ - Context: b.Bytes(), - Size: uint32(len(r)), - Payload: r, - } - rr, _ := rlp.EncodeToBytes(wmsg) - d.size = uint32(len(rr)) - return rr -} |