github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/p2p/protocols/protocol_test.go (about)

     1  package protocols
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/quickchainproject/quickchain/p2p"
    11  	"github.com/quickchainproject/quickchain/p2p/discover"
    12  	"github.com/quickchainproject/quickchain/p2p/simulations/adapters"
    13  	p2ptest "github.com/quickchainproject/quickchain/p2p/testing"
    14  )
    15  
    16  // handshake message type
    17  type hs0 struct {
    18  	C uint
    19  }
    20  
    21  // message to kill/drop the peer with nodeID
    22  type kill struct {
    23  	C discover.NodeID
    24  }
    25  
    26  // message to drop connection
    27  type drop struct {
    28  }
    29  
    30  /// protoHandshake represents module-independent aspects of the protocol and is
    31  // the first message peers send and receive as part the initial exchange
    32  type protoHandshake struct {
    33  	Version   uint   // local and remote peer should have identical version
    34  	NetworkID string // local and remote peer should have identical network id
    35  }
    36  
    37  // checkProtoHandshake verifies local and remote protoHandshakes match
    38  func checkProtoHandshake(testVersion uint, testNetworkID string) func(interface{}) error {
    39  	return func(rhs interface{}) error {
    40  		remote := rhs.(*protoHandshake)
    41  		if remote.NetworkID != testNetworkID {
    42  			return fmt.Errorf("%s (!= %s)", remote.NetworkID, testNetworkID)
    43  		}
    44  
    45  		if remote.Version != testVersion {
    46  			return fmt.Errorf("%d (!= %d)", remote.Version, testVersion)
    47  		}
    48  		return nil
    49  	}
    50  }
    51  
    52  // newProtocol sets up a protocol
    53  // the run function here demonstrates a typical protocol using peerPool, handshake
    54  // and messages registered to handlers
    55  func newProtocol(pp *p2ptest.TestPeerPool) func(*p2p.Peer, p2p.MsgReadWriter) error {
    56  	spec := &Spec{
    57  		Name:       "test",
    58  		Version:    42,
    59  		MaxMsgSize: 10 * 1024,
    60  		Messages: []interface{}{
    61  			protoHandshake{},
    62  			hs0{},
    63  			kill{},
    64  			drop{},
    65  		},
    66  	}
    67  	return func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
    68  		peer := NewPeer(p, rw, spec)
    69  
    70  		// initiate one-off protohandshake and check validity
    71  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    72  		defer cancel()
    73  		phs := &protoHandshake{42, "420"}
    74  		hsCheck := checkProtoHandshake(phs.Version, phs.NetworkID)
    75  		_, err := peer.Handshake(ctx, phs, hsCheck)
    76  		if err != nil {
    77  			return err
    78  		}
    79  
    80  		lhs := &hs0{42}
    81  		// module handshake demonstrating a simple repeatable exchange of same-type message
    82  		hs, err := peer.Handshake(ctx, lhs, nil)
    83  		if err != nil {
    84  			return err
    85  		}
    86  
    87  		if rmhs := hs.(*hs0); rmhs.C > lhs.C {
    88  			return fmt.Errorf("handshake mismatch remote %v > local %v", rmhs.C, lhs.C)
    89  		}
    90  
    91  		handle := func(msg interface{}) error {
    92  			switch msg := msg.(type) {
    93  
    94  			case *protoHandshake:
    95  				return errors.New("duplicate handshake")
    96  
    97  			case *hs0:
    98  				rhs := msg
    99  				if rhs.C > lhs.C {
   100  					return fmt.Errorf("handshake mismatch remote %v > local %v", rhs.C, lhs.C)
   101  				}
   102  				lhs.C += rhs.C
   103  				return peer.Send(lhs)
   104  
   105  			case *kill:
   106  				// demonstrates use of peerPool, killing another peer connection as a response to a message
   107  				id := msg.C
   108  				pp.Get(id).Drop(errors.New("killed"))
   109  				return nil
   110  
   111  			case *drop:
   112  				// for testing we can trigger self induced disconnect upon receiving drop message
   113  				return errors.New("dropped")
   114  
   115  			default:
   116  				return fmt.Errorf("unknown message type: %T", msg)
   117  			}
   118  		}
   119  
   120  		pp.Add(peer)
   121  		defer pp.Remove(peer)
   122  		return peer.Run(handle)
   123  	}
   124  }
   125  
   126  func protocolTester(t *testing.T, pp *p2ptest.TestPeerPool) *p2ptest.ProtocolTester {
   127  	conf := adapters.RandomNodeConfig()
   128  	return p2ptest.NewProtocolTester(t, conf.ID, 2, newProtocol(pp))
   129  }
   130  
   131  func protoHandshakeExchange(id discover.NodeID, proto *protoHandshake) []p2ptest.Exchange {
   132  
   133  	return []p2ptest.Exchange{
   134  		{
   135  			Expects: []p2ptest.Expect{
   136  				{
   137  					Code: 0,
   138  					Msg:  &protoHandshake{42, "420"},
   139  					Peer: id,
   140  				},
   141  			},
   142  		},
   143  		{
   144  			Triggers: []p2ptest.Trigger{
   145  				{
   146  					Code: 0,
   147  					Msg:  proto,
   148  					Peer: id,
   149  				},
   150  			},
   151  		},
   152  	}
   153  }
   154  
   155  func runProtoHandshake(t *testing.T, proto *protoHandshake, errs ...error) {
   156  	pp := p2ptest.NewTestPeerPool()
   157  	s := protocolTester(t, pp)
   158  	// TODO: make this more than one handshake
   159  	id := s.IDs[0]
   160  	if err := s.TestExchanges(protoHandshakeExchange(id, proto)...); err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	var disconnects []*p2ptest.Disconnect
   164  	for i, err := range errs {
   165  		disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
   166  	}
   167  	if err := s.TestDisconnected(disconnects...); err != nil {
   168  		t.Fatal(err)
   169  	}
   170  }
   171  
   172  func TestProtoHandshakeVersionMismatch(t *testing.T) {
   173  	runProtoHandshake(t, &protoHandshake{41, "420"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 41 (!= 42)").Error()))
   174  }
   175  
   176  func TestProtoHandshakeNetworkIDMismatch(t *testing.T) {
   177  	runProtoHandshake(t, &protoHandshake{42, "421"}, errorf(ErrHandshake, errorf(ErrHandler, "(msg code 0): 421 (!= 420)").Error()))
   178  }
   179  
   180  func TestProtoHandshakeSuccess(t *testing.T) {
   181  	runProtoHandshake(t, &protoHandshake{42, "420"})
   182  }
   183  
   184  func moduleHandshakeExchange(id discover.NodeID, resp uint) []p2ptest.Exchange {
   185  
   186  	return []p2ptest.Exchange{
   187  		{
   188  			Expects: []p2ptest.Expect{
   189  				{
   190  					Code: 1,
   191  					Msg:  &hs0{42},
   192  					Peer: id,
   193  				},
   194  			},
   195  		},
   196  		{
   197  			Triggers: []p2ptest.Trigger{
   198  				{
   199  					Code: 1,
   200  					Msg:  &hs0{resp},
   201  					Peer: id,
   202  				},
   203  			},
   204  		},
   205  	}
   206  }
   207  
   208  func runModuleHandshake(t *testing.T, resp uint, errs ...error) {
   209  	pp := p2ptest.NewTestPeerPool()
   210  	s := protocolTester(t, pp)
   211  	id := s.IDs[0]
   212  	if err := s.TestExchanges(protoHandshakeExchange(id, &protoHandshake{42, "420"})...); err != nil {
   213  		t.Fatal(err)
   214  	}
   215  	if err := s.TestExchanges(moduleHandshakeExchange(id, resp)...); err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	var disconnects []*p2ptest.Disconnect
   219  	for i, err := range errs {
   220  		disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
   221  	}
   222  	if err := s.TestDisconnected(disconnects...); err != nil {
   223  		t.Fatal(err)
   224  	}
   225  }
   226  
   227  func TestModuleHandshakeError(t *testing.T) {
   228  	runModuleHandshake(t, 43, fmt.Errorf("handshake mismatch remote 43 > local 42"))
   229  }
   230  
   231  func TestModuleHandshakeSuccess(t *testing.T) {
   232  	runModuleHandshake(t, 42)
   233  }
   234  
   235  // testing complex interactions over multiple peers, relaying, dropping
   236  func testMultiPeerSetup(a, b discover.NodeID) []p2ptest.Exchange {
   237  
   238  	return []p2ptest.Exchange{
   239  		{
   240  			Label: "primary handshake",
   241  			Expects: []p2ptest.Expect{
   242  				{
   243  					Code: 0,
   244  					Msg:  &protoHandshake{42, "420"},
   245  					Peer: a,
   246  				},
   247  				{
   248  					Code: 0,
   249  					Msg:  &protoHandshake{42, "420"},
   250  					Peer: b,
   251  				},
   252  			},
   253  		},
   254  		{
   255  			Label: "module handshake",
   256  			Triggers: []p2ptest.Trigger{
   257  				{
   258  					Code: 0,
   259  					Msg:  &protoHandshake{42, "420"},
   260  					Peer: a,
   261  				},
   262  				{
   263  					Code: 0,
   264  					Msg:  &protoHandshake{42, "420"},
   265  					Peer: b,
   266  				},
   267  			},
   268  			Expects: []p2ptest.Expect{
   269  				{
   270  					Code: 1,
   271  					Msg:  &hs0{42},
   272  					Peer: a,
   273  				},
   274  				{
   275  					Code: 1,
   276  					Msg:  &hs0{42},
   277  					Peer: b,
   278  				},
   279  			},
   280  		},
   281  
   282  		{Label: "alternative module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{41}, Peer: a},
   283  			{Code: 1, Msg: &hs0{41}, Peer: b}}},
   284  		{Label: "repeated module handshake", Triggers: []p2ptest.Trigger{{Code: 1, Msg: &hs0{1}, Peer: a}}},
   285  		{Label: "receiving repeated module handshake", Expects: []p2ptest.Expect{{Code: 1, Msg: &hs0{43}, Peer: a}}}}
   286  }
   287  
   288  func runMultiplePeers(t *testing.T, peer int, errs ...error) {
   289  	pp := p2ptest.NewTestPeerPool()
   290  	s := protocolTester(t, pp)
   291  
   292  	if err := s.TestExchanges(testMultiPeerSetup(s.IDs[0], s.IDs[1])...); err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	// after some exchanges of messages, we can test state changes
   296  	// here this is simply demonstrated by the peerPool
   297  	// after the handshake negotiations peers must be added to the pool
   298  	// time.Sleep(1)
   299  	tick := time.NewTicker(10 * time.Millisecond)
   300  	timeout := time.NewTimer(1 * time.Second)
   301  WAIT:
   302  	for {
   303  		select {
   304  		case <-tick.C:
   305  			if pp.Has(s.IDs[0]) {
   306  				break WAIT
   307  			}
   308  		case <-timeout.C:
   309  			t.Fatal("timeout")
   310  		}
   311  	}
   312  	if !pp.Has(s.IDs[1]) {
   313  		t.Fatalf("missing peer test-1: %v (%v)", pp, s.IDs)
   314  	}
   315  
   316  	// peer 0 sends kill request for peer with index <peer>
   317  	err := s.TestExchanges(p2ptest.Exchange{
   318  		Triggers: []p2ptest.Trigger{
   319  			{
   320  				Code: 2,
   321  				Msg:  &kill{s.IDs[peer]},
   322  				Peer: s.IDs[0],
   323  			},
   324  		},
   325  	})
   326  
   327  	if err != nil {
   328  		t.Fatal(err)
   329  	}
   330  
   331  	// the peer not killed sends a drop request
   332  	err = s.TestExchanges(p2ptest.Exchange{
   333  		Triggers: []p2ptest.Trigger{
   334  			{
   335  				Code: 3,
   336  				Msg:  &drop{},
   337  				Peer: s.IDs[(peer+1)%2],
   338  			},
   339  		},
   340  	})
   341  
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  
   346  	// check the actual discconnect errors on the individual peers
   347  	var disconnects []*p2ptest.Disconnect
   348  	for i, err := range errs {
   349  		disconnects = append(disconnects, &p2ptest.Disconnect{Peer: s.IDs[i], Error: err})
   350  	}
   351  	if err := s.TestDisconnected(disconnects...); err != nil {
   352  		t.Fatal(err)
   353  	}
   354  	// test if disconnected peers have been removed from peerPool
   355  	if pp.Has(s.IDs[peer]) {
   356  		t.Fatalf("peer test-%v not dropped: %v (%v)", peer, pp, s.IDs)
   357  	}
   358  
   359  }
   360  
   361  func TestMultiplePeersDropSelf(t *testing.T) {
   362  	runMultiplePeers(t, 0,
   363  		fmt.Errorf("subprotocol error"),
   364  		fmt.Errorf("Message handler error: (msg code 3): dropped"),
   365  	)
   366  }
   367  
   368  func TestMultiplePeersDropOther(t *testing.T) {
   369  	runMultiplePeers(t, 1,
   370  		fmt.Errorf("Message handler error: (msg code 3): dropped"),
   371  		fmt.Errorf("subprotocol error"),
   372  	)
   373  }