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