protocoltester.go 7.3 KB

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