github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/network/p2p/testing/protocolsession.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 testing
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/neatlab/neatio/chain/log"
    26  	"github.com/neatlab/neatio/network/p2p"
    27  	"github.com/neatlab/neatio/network/p2p/discover"
    28  	"github.com/neatlab/neatio/network/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  	IDs     []discover.NodeID
    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    discover.NodeID // 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    discover.NodeID // 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  discover.NodeID // discconnected peer
    77  	Error error           // disconnect reason
    78  }
    79  
    80  // trigger sends messages from peers
    81  func (self *ProtocolSession) trigger(trig Trigger) error {
    82  	simNode, ok := self.adapter.GetNode(trig.Peer)
    83  	if !ok {
    84  		return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(self.IDs))
    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  		errc <- mockNode.Trigger(&trig)
    95  	}()
    96  
    97  	t := trig.Timeout
    98  	if t == time.Duration(0) {
    99  		t = 1000 * time.Millisecond
   100  	}
   101  	select {
   102  	case err := <-errc:
   103  		return err
   104  	case <-time.After(t):
   105  		return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
   106  	}
   107  }
   108  
   109  // expect checks an expectation of a message sent out by the pivot node
   110  func (self *ProtocolSession) expect(exps []Expect) error {
   111  	// construct a map of expectations for each node
   112  	peerExpects := make(map[discover.NodeID][]Expect)
   113  	for _, exp := range exps {
   114  		if exp.Msg == nil {
   115  			return errors.New("no message to expect")
   116  		}
   117  		peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
   118  	}
   119  
   120  	// construct a map of mockNodes for each node
   121  	mockNodes := make(map[discover.NodeID]*mockNode)
   122  	for nodeID := range peerExpects {
   123  		simNode, ok := self.adapter.GetNode(nodeID)
   124  		if !ok {
   125  			return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(self.IDs))
   126  		}
   127  		mockNode, ok := simNode.Services()[0].(*mockNode)
   128  		if !ok {
   129  			return fmt.Errorf("trigger: peer %v is not a mock", nodeID)
   130  		}
   131  		mockNodes[nodeID] = mockNode
   132  	}
   133  
   134  	// done chanell cancels all created goroutines when function returns
   135  	done := make(chan struct{})
   136  	defer close(done)
   137  	// errc catches the first error from
   138  	errc := make(chan error)
   139  
   140  	wg := &sync.WaitGroup{}
   141  	wg.Add(len(mockNodes))
   142  	for nodeID, mockNode := range mockNodes {
   143  		nodeID := nodeID
   144  		mockNode := mockNode
   145  		go func() {
   146  			defer wg.Done()
   147  
   148  			// Sum all Expect timeouts to give the maximum
   149  			// time for all expectations to finish.
   150  			// mockNode.Expect checks all received messages against
   151  			// a list of expected messages and timeout for each
   152  			// of them can not be checked separately.
   153  			var t time.Duration
   154  			for _, exp := range peerExpects[nodeID] {
   155  				if exp.Timeout == time.Duration(0) {
   156  					t += 2000 * time.Millisecond
   157  				} else {
   158  					t += exp.Timeout
   159  				}
   160  			}
   161  			alarm := time.NewTimer(t)
   162  			defer alarm.Stop()
   163  
   164  			// expectErrc is used to check if error returned
   165  			// from mockNode.Expect is not nil and to send it to
   166  			// errc only in that case.
   167  			// done channel will be closed when function
   168  			expectErrc := make(chan error)
   169  			go func() {
   170  				select {
   171  				case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
   172  				case <-done:
   173  				case <-alarm.C:
   174  				}
   175  			}()
   176  
   177  			select {
   178  			case err := <-expectErrc:
   179  				if err != nil {
   180  					select {
   181  					case errc <- err:
   182  					case <-done:
   183  					case <-alarm.C:
   184  						errc <- errTimedOut
   185  					}
   186  				}
   187  			case <-done:
   188  			case <-alarm.C:
   189  				errc <- errTimedOut
   190  			}
   191  
   192  		}()
   193  	}
   194  
   195  	go func() {
   196  		wg.Wait()
   197  		// close errc when all goroutines finish to return nill err from errc
   198  		close(errc)
   199  	}()
   200  
   201  	return <-errc
   202  }
   203  
   204  // TestExchanges tests a series of exchanges against the session
   205  func (self *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
   206  	for i, e := range exchanges {
   207  		if err := self.testExchange(e); err != nil {
   208  			return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err)
   209  		}
   210  		log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label))
   211  	}
   212  	return nil
   213  }
   214  
   215  // testExchange tests a single Exchange.
   216  // Default timeout value is 2 seconds.
   217  func (self *ProtocolSession) testExchange(e Exchange) error {
   218  	errc := make(chan error)
   219  	done := make(chan struct{})
   220  	defer close(done)
   221  
   222  	go func() {
   223  		for _, trig := range e.Triggers {
   224  			err := self.trigger(trig)
   225  			if err != nil {
   226  				errc <- err
   227  				return
   228  			}
   229  		}
   230  
   231  		select {
   232  		case errc <- self.expect(e.Expects):
   233  		case <-done:
   234  		}
   235  	}()
   236  
   237  	// time out globally or finish when all expectations satisfied
   238  	t := e.Timeout
   239  	if t == 0 {
   240  		t = 2000 * time.Millisecond
   241  	}
   242  	alarm := time.NewTimer(t)
   243  	select {
   244  	case err := <-errc:
   245  		return err
   246  	case <-alarm.C:
   247  		return errTimedOut
   248  	}
   249  }
   250  
   251  // TestDisconnected tests the disconnections given as arguments
   252  // the disconnect structs describe what disconnect error is expected on which peer
   253  func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
   254  	expects := make(map[discover.NodeID]error)
   255  	for _, disconnect := range disconnects {
   256  		expects[disconnect.Peer] = disconnect.Error
   257  	}
   258  
   259  	timeout := time.After(time.Second)
   260  	for len(expects) > 0 {
   261  		select {
   262  		case event := <-self.events:
   263  			if event.Type != p2p.PeerEventTypeDrop {
   264  				continue
   265  			}
   266  			expectErr, ok := expects[event.Peer]
   267  			if !ok {
   268  				continue
   269  			}
   270  
   271  			if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) {
   272  				return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
   273  			}
   274  			delete(expects, event.Peer)
   275  		case <-timeout:
   276  			return fmt.Errorf("timed out waiting for peers to disconnect")
   277  		}
   278  	}
   279  	return nil
   280  }