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