github.com/julesgoullee/go-ethereum@v1.9.7/p2p/testing/protocolsession.go (about)

     1  // Copyright 2018 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 testing
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/ethereum/go-ethereum/log"
    26  	"github.com/ethereum/go-ethereum/p2p"
    27  	"github.com/ethereum/go-ethereum/p2p/enode"
    28  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    29  )
    30  
    31  var errTimedOut = errors.New("timed out")
    32  
    33  // ProtocolSession is a quasi simulation of a pivot node running
    34  // a service and a number of dummy peers that can send (trigger) or
    35  // receive (expect) messages
    36  type ProtocolSession struct {
    37  	Server  *p2p.Server
    38  	Nodes   []*enode.Node
    39  	adapter *adapters.SimAdapter
    40  	events  chan *p2p.PeerEvent
    41  }
    42  
    43  // Exchange is the basic units of protocol tests
    44  // the triggers and expects in the arrays are run immediately and asynchronously
    45  // thus one cannot have multiple expects for the SAME peer with DIFFERENT message types
    46  // because it's unpredictable which expect will receive which message
    47  // (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code)
    48  // an exchange is defined on a session
    49  type Exchange struct {
    50  	Label    string
    51  	Triggers []Trigger
    52  	Expects  []Expect
    53  	Timeout  time.Duration
    54  }
    55  
    56  // Trigger is part of the exchange, incoming message for the pivot node
    57  // sent by a peer
    58  type Trigger struct {
    59  	Msg     interface{}   // type of message to be sent
    60  	Code    uint64        // code of message is given
    61  	Peer    enode.ID      // the peer to send the message to
    62  	Timeout time.Duration // timeout duration for the sending
    63  }
    64  
    65  // Expect is part of an exchange, outgoing message from the pivot node
    66  // received by a peer
    67  type Expect struct {
    68  	Msg     interface{}   // type of message to expect
    69  	Code    uint64        // code of message is now given
    70  	Peer    enode.ID      // the peer that expects the message
    71  	Timeout time.Duration // timeout duration for receiving
    72  }
    73  
    74  // Disconnect represents a disconnect event, used and checked by TestDisconnected
    75  type Disconnect struct {
    76  	Peer  enode.ID // discconnected peer
    77  	Error error    // disconnect reason
    78  }
    79  
    80  // trigger sends messages from peers
    81  func (s *ProtocolSession) trigger(trig Trigger) error {
    82  	simNode, ok := s.adapter.GetNode(trig.Peer)
    83  	if !ok {
    84  		return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.Nodes))
    85  	}
    86  	mockNode, ok := simNode.Services()[0].(*mockNode)
    87  	if !ok {
    88  		return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer)
    89  	}
    90  
    91  	errc := make(chan error)
    92  
    93  	go func() {
    94  		log.Trace(fmt.Sprintf("trigger %v (%v)....", trig.Msg, trig.Code))
    95  		errc <- mockNode.Trigger(&trig)
    96  		log.Trace(fmt.Sprintf("triggered %v (%v)", trig.Msg, trig.Code))
    97  	}()
    98  
    99  	t := trig.Timeout
   100  	if t == time.Duration(0) {
   101  		t = 1000 * time.Millisecond
   102  	}
   103  	select {
   104  	case err := <-errc:
   105  		return err
   106  	case <-time.After(t):
   107  		return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
   108  	}
   109  }
   110  
   111  // expect checks an expectation of a message sent out by the pivot node
   112  func (s *ProtocolSession) expect(exps []Expect) error {
   113  	// construct a map of expectations for each node
   114  	peerExpects := make(map[enode.ID][]Expect)
   115  	for _, exp := range exps {
   116  		if exp.Msg == nil {
   117  			return errors.New("no message to expect")
   118  		}
   119  		peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
   120  	}
   121  
   122  	// construct a map of mockNodes for each node
   123  	mockNodes := make(map[enode.ID]*mockNode)
   124  	for nodeID := range peerExpects {
   125  		simNode, ok := s.adapter.GetNode(nodeID)
   126  		if !ok {
   127  			return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.Nodes))
   128  		}
   129  		mockNode, ok := simNode.Services()[0].(*mockNode)
   130  		if !ok {
   131  			return fmt.Errorf("trigger: peer %v is not a mock", nodeID)
   132  		}
   133  		mockNodes[nodeID] = mockNode
   134  	}
   135  
   136  	// done chanell cancels all created goroutines when function returns
   137  	done := make(chan struct{})
   138  	defer close(done)
   139  	// errc catches the first error from
   140  	errc := make(chan error)
   141  
   142  	wg := &sync.WaitGroup{}
   143  	wg.Add(len(mockNodes))
   144  	for nodeID, mockNode := range mockNodes {
   145  		nodeID := nodeID
   146  		mockNode := mockNode
   147  		go func() {
   148  			defer wg.Done()
   149  
   150  			// Sum all Expect timeouts to give the maximum
   151  			// time for all expectations to finish.
   152  			// mockNode.Expect checks all received messages against
   153  			// a list of expected messages and timeout for each
   154  			// of them can not be checked separately.
   155  			var t time.Duration
   156  			for _, exp := range peerExpects[nodeID] {
   157  				if exp.Timeout == time.Duration(0) {
   158  					t += 2000 * time.Millisecond
   159  				} else {
   160  					t += exp.Timeout
   161  				}
   162  			}
   163  			alarm := time.NewTimer(t)
   164  			defer alarm.Stop()
   165  
   166  			// expectErrc is used to check if error returned
   167  			// from mockNode.Expect is not nil and to send it to
   168  			// errc only in that case.
   169  			// done channel will be closed when function
   170  			expectErrc := make(chan error)
   171  			go func() {
   172  				select {
   173  				case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
   174  				case <-done:
   175  				case <-alarm.C:
   176  				}
   177  			}()
   178  
   179  			select {
   180  			case err := <-expectErrc:
   181  				if err != nil {
   182  					select {
   183  					case errc <- err:
   184  					case <-done:
   185  					case <-alarm.C:
   186  						errc <- errTimedOut
   187  					}
   188  				}
   189  			case <-done:
   190  			case <-alarm.C:
   191  				errc <- errTimedOut
   192  			}
   193  
   194  		}()
   195  	}
   196  
   197  	go func() {
   198  		wg.Wait()
   199  		// close errc when all goroutines finish to return nill err from errc
   200  		close(errc)
   201  	}()
   202  
   203  	return <-errc
   204  }
   205  
   206  // TestExchanges tests a series of exchanges against the session
   207  func (s *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
   208  	for i, e := range exchanges {
   209  		if err := s.testExchange(e); err != nil {
   210  			return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err)
   211  		}
   212  		log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label))
   213  	}
   214  	return nil
   215  }
   216  
   217  // testExchange tests a single Exchange.
   218  // Default timeout value is 2 seconds.
   219  func (s *ProtocolSession) testExchange(e Exchange) error {
   220  	errc := make(chan error)
   221  	done := make(chan struct{})
   222  	defer close(done)
   223  
   224  	go func() {
   225  		for _, trig := range e.Triggers {
   226  			err := s.trigger(trig)
   227  			if err != nil {
   228  				errc <- err
   229  				return
   230  			}
   231  		}
   232  
   233  		select {
   234  		case errc <- s.expect(e.Expects):
   235  		case <-done:
   236  		}
   237  	}()
   238  
   239  	// time out globally or finish when all expectations satisfied
   240  	t := e.Timeout
   241  	if t == 0 {
   242  		t = 2000 * time.Millisecond
   243  	}
   244  	alarm := time.NewTimer(t)
   245  	select {
   246  	case err := <-errc:
   247  		return err
   248  	case <-alarm.C:
   249  		return errTimedOut
   250  	}
   251  }
   252  
   253  // TestDisconnected tests the disconnections given as arguments
   254  // the disconnect structs describe what disconnect error is expected on which peer
   255  func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
   256  	expects := make(map[enode.ID]error)
   257  	for _, disconnect := range disconnects {
   258  		expects[disconnect.Peer] = disconnect.Error
   259  	}
   260  
   261  	timeout := time.After(time.Second)
   262  	for len(expects) > 0 {
   263  		select {
   264  		case event := <-s.events:
   265  			if event.Type != p2p.PeerEventTypeDrop {
   266  				continue
   267  			}
   268  			expectErr, ok := expects[event.Peer]
   269  			if !ok {
   270  				continue
   271  			}
   272  
   273  			if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) {
   274  				return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
   275  			}
   276  			delete(expects, event.Peer)
   277  		case <-timeout:
   278  			return fmt.Errorf("timed out waiting for peers to disconnect")
   279  		}
   280  	}
   281  	return nil
   282  }