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