github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/testing/protocoltester.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  /*
    19  the p2p/testing package provides a unit test scheme to check simple
    20  protocol message exchanges with one pivot node and a number of dummy peers
    21  The pivot test node runs a node.Service, the dummy peers run a mock node
    22  that can be used to send and receive messages
    23  */
    24  
    25  package testing
    26  
    27  import (
    28  	"bytes"
    29  	"crypto/ecdsa"
    30  	"fmt"
    31  	"io"
    32  	"io/ioutil"
    33  	"strings"
    34  	"sync"
    35  
    36  	"github.com/AigarNetwork/aigar/log"
    37  	"github.com/AigarNetwork/aigar/node"
    38  	"github.com/AigarNetwork/aigar/p2p"
    39  	"github.com/AigarNetwork/aigar/p2p/enode"
    40  	"github.com/AigarNetwork/aigar/p2p/simulations"
    41  	"github.com/AigarNetwork/aigar/p2p/simulations/adapters"
    42  	"github.com/AigarNetwork/aigar/rlp"
    43  	"github.com/AigarNetwork/aigar/rpc"
    44  )
    45  
    46  // ProtocolTester is the tester environment used for unit testing protocol
    47  // message exchanges. It uses p2p/simulations framework
    48  type ProtocolTester struct {
    49  	*ProtocolSession
    50  	network *simulations.Network
    51  }
    52  
    53  // NewProtocolTester constructs a new ProtocolTester
    54  // it takes as argument the pivot node id, the number of dummy peers and the
    55  // protocol run function called on a peer connection by the p2p server
    56  func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
    57  	services := adapters.Services{
    58  		"test": func(ctx *adapters.ServiceContext) (node.Service, error) {
    59  			return &testNode{run}, nil
    60  		},
    61  		"mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
    62  			return newMockNode(), nil
    63  		},
    64  	}
    65  	adapter := adapters.NewSimAdapter(services)
    66  	net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
    67  	nodeConfig := &adapters.NodeConfig{
    68  		PrivateKey:      prvkey,
    69  		EnableMsgEvents: true,
    70  		Services:        []string{"test"},
    71  	}
    72  	if _, err := net.NewNodeWithConfig(nodeConfig); err != nil {
    73  		panic(err.Error())
    74  	}
    75  	if err := net.Start(nodeConfig.ID); err != nil {
    76  		panic(err.Error())
    77  	}
    78  
    79  	node := net.GetNode(nodeConfig.ID).Node.(*adapters.SimNode)
    80  	peers := make([]*adapters.NodeConfig, nodeCount)
    81  	nodes := make([]*enode.Node, nodeCount)
    82  	for i := 0; i < nodeCount; i++ {
    83  		peers[i] = adapters.RandomNodeConfig()
    84  		peers[i].Services = []string{"mock"}
    85  		if _, err := net.NewNodeWithConfig(peers[i]); err != nil {
    86  			panic(fmt.Sprintf("error initializing peer %v: %v", peers[i].ID, err))
    87  		}
    88  		if err := net.Start(peers[i].ID); err != nil {
    89  			panic(fmt.Sprintf("error starting peer %v: %v", peers[i].ID, err))
    90  		}
    91  		nodes[i] = peers[i].Node()
    92  	}
    93  	events := make(chan *p2p.PeerEvent, 1000)
    94  	node.SubscribeEvents(events)
    95  	ps := &ProtocolSession{
    96  		Server:  node.Server(),
    97  		Nodes:   nodes,
    98  		adapter: adapter,
    99  		events:  events,
   100  	}
   101  	self := &ProtocolTester{
   102  		ProtocolSession: ps,
   103  		network:         net,
   104  	}
   105  
   106  	self.Connect(nodeConfig.ID, peers...)
   107  
   108  	return self
   109  }
   110  
   111  // Stop stops the p2p server
   112  func (t *ProtocolTester) Stop() {
   113  	t.Server.Stop()
   114  	t.network.Shutdown()
   115  }
   116  
   117  // Connect brings up the remote peer node and connects it using the
   118  // p2p/simulations network connection with the in memory network adapter
   119  func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) {
   120  	for _, peer := range peers {
   121  		log.Trace(fmt.Sprintf("connect to %v", peer.ID))
   122  		if err := t.network.Connect(selfID, peer.ID); err != nil {
   123  			panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
   124  		}
   125  	}
   126  
   127  }
   128  
   129  // testNode wraps a protocol run function and implements the node.Service
   130  // interface
   131  type testNode struct {
   132  	run func(*p2p.Peer, p2p.MsgReadWriter) error
   133  }
   134  
   135  func (t *testNode) Protocols() []p2p.Protocol {
   136  	return []p2p.Protocol{{
   137  		Length: 100,
   138  		Run:    t.run,
   139  	}}
   140  }
   141  
   142  func (t *testNode) APIs() []rpc.API {
   143  	return nil
   144  }
   145  
   146  func (t *testNode) Start(server *p2p.Server) error {
   147  	return nil
   148  }
   149  
   150  func (t *testNode) Stop() error {
   151  	return nil
   152  }
   153  
   154  // mockNode is a testNode which doesn't actually run a protocol, instead
   155  // exposing channels so that tests can manually trigger and expect certain
   156  // messages
   157  type mockNode struct {
   158  	testNode
   159  
   160  	trigger  chan *Trigger
   161  	expect   chan []Expect
   162  	err      chan error
   163  	stop     chan struct{}
   164  	stopOnce sync.Once
   165  }
   166  
   167  func newMockNode() *mockNode {
   168  	mock := &mockNode{
   169  		trigger: make(chan *Trigger),
   170  		expect:  make(chan []Expect),
   171  		err:     make(chan error),
   172  		stop:    make(chan struct{}),
   173  	}
   174  	mock.testNode.run = mock.Run
   175  	return mock
   176  }
   177  
   178  // Run is a protocol run function which just loops waiting for tests to
   179  // instruct it to either trigger or expect a message from the peer
   180  func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
   181  	for {
   182  		select {
   183  		case trig := <-m.trigger:
   184  			wmsg := Wrap(trig.Msg)
   185  			m.err <- p2p.Send(rw, trig.Code, wmsg)
   186  		case exps := <-m.expect:
   187  			m.err <- expectMsgs(rw, exps)
   188  		case <-m.stop:
   189  			return nil
   190  		}
   191  	}
   192  }
   193  
   194  func (m *mockNode) Trigger(trig *Trigger) error {
   195  	m.trigger <- trig
   196  	return <-m.err
   197  }
   198  
   199  func (m *mockNode) Expect(exp ...Expect) error {
   200  	m.expect <- exp
   201  	return <-m.err
   202  }
   203  
   204  func (m *mockNode) Stop() error {
   205  	m.stopOnce.Do(func() { close(m.stop) })
   206  	return nil
   207  }
   208  
   209  func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error {
   210  	matched := make([]bool, len(exps))
   211  	for {
   212  		msg, err := rw.ReadMsg()
   213  		if err != nil {
   214  			if err == io.EOF {
   215  				break
   216  			}
   217  			return err
   218  		}
   219  		actualContent, err := ioutil.ReadAll(msg.Payload)
   220  		if err != nil {
   221  			return err
   222  		}
   223  		var found bool
   224  		for i, exp := range exps {
   225  			if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(Wrap(exp.Msg))) {
   226  				if matched[i] {
   227  					return fmt.Errorf("message #%d received two times", i)
   228  				}
   229  				matched[i] = true
   230  				found = true
   231  				break
   232  			}
   233  		}
   234  		if !found {
   235  			expected := make([]string, 0)
   236  			for i, exp := range exps {
   237  				if matched[i] {
   238  					continue
   239  				}
   240  				expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(Wrap(exp.Msg))))
   241  			}
   242  			return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or "))
   243  		}
   244  		done := true
   245  		for _, m := range matched {
   246  			if !m {
   247  				done = false
   248  				break
   249  			}
   250  		}
   251  		if done {
   252  			return nil
   253  		}
   254  	}
   255  	for i, m := range matched {
   256  		if !m {
   257  			return fmt.Errorf("expected message #%d not received", i)
   258  		}
   259  	}
   260  	return nil
   261  }
   262  
   263  // mustEncodeMsg uses rlp to encode a message.
   264  // In case of error it panics.
   265  func mustEncodeMsg(msg interface{}) []byte {
   266  	contentEnc, err := rlp.EncodeToBytes(msg)
   267  	if err != nil {
   268  		panic("content encode error: " + err.Error())
   269  	}
   270  	return contentEnc
   271  }
   272  
   273  type WrappedMsg struct {
   274  	Context []byte
   275  	Size    uint32
   276  	Payload []byte
   277  }
   278  
   279  func Wrap(msg interface{}) interface{} {
   280  	data, _ := rlp.EncodeToBytes(msg)
   281  	return &WrappedMsg{
   282  		Size:    uint32(len(data)),
   283  		Payload: data,
   284  	}
   285  }