github.com/neatio-net/neatio@v1.7.3-0.20231114194659-f4d7a2226baa/network/p2p/testing/protocoltester.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 /* 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 "fmt" 29 "io" 30 "io/ioutil" 31 "strings" 32 "sync" 33 "testing" 34 35 "github.com/neatio-net/neatio/chain/log" 36 "github.com/neatio-net/neatio/network/node" 37 "github.com/neatio-net/neatio/network/p2p" 38 "github.com/neatio-net/neatio/network/p2p/discover" 39 "github.com/neatio-net/neatio/network/p2p/simulations" 40 "github.com/neatio-net/neatio/network/p2p/simulations/adapters" 41 "github.com/neatio-net/neatio/network/rpc" 42 "github.com/neatio-net/neatio/utilities/rlp" 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(t *testing.T, id discover.NodeID, n 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 if _, err := net.NewNodeWithConfig(&adapters.NodeConfig{ 67 ID: id, 68 EnableMsgEvents: true, 69 Services: []string{"test"}, 70 }); err != nil { 71 panic(err.Error()) 72 } 73 if err := net.Start(id); err != nil { 74 panic(err.Error()) 75 } 76 77 node := net.GetNode(id).Node.(*adapters.SimNode) 78 peers := make([]*adapters.NodeConfig, n) 79 peerIDs := make([]discover.NodeID, n) 80 for i := 0; i < n; i++ { 81 peers[i] = adapters.RandomNodeConfig() 82 peers[i].Services = []string{"mock"} 83 peerIDs[i] = peers[i].ID 84 } 85 events := make(chan *p2p.PeerEvent, 1000) 86 node.SubscribeEvents(events) 87 ps := &ProtocolSession{ 88 Server: node.Server(), 89 IDs: peerIDs, 90 adapter: adapter, 91 events: events, 92 } 93 self := &ProtocolTester{ 94 ProtocolSession: ps, 95 network: net, 96 } 97 98 self.Connect(id, peers...) 99 100 return self 101 } 102 103 // Stop stops the p2p server 104 func (self *ProtocolTester) Stop() error { 105 self.Server.Stop() 106 return nil 107 } 108 109 // Connect brings up the remote peer node and connects it using the 110 // p2p/simulations network connection with the in memory network adapter 111 func (self *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) { 112 for _, peer := range peers { 113 log.Trace(fmt.Sprintf("start node %v", peer.ID)) 114 if _, err := self.network.NewNodeWithConfig(peer); err != nil { 115 panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err)) 116 } 117 if err := self.network.Start(peer.ID); err != nil { 118 panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err)) 119 } 120 log.Trace(fmt.Sprintf("connect to %v", peer.ID)) 121 if err := self.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 m.err <- p2p.Send(rw, trig.Code, trig.Msg) 184 case exps := <-m.expect: 185 m.err <- expectMsgs(rw, exps) 186 case <-m.stop: 187 return nil 188 } 189 } 190 } 191 192 func (m *mockNode) Trigger(trig *Trigger) error { 193 m.trigger <- trig 194 return <-m.err 195 } 196 197 func (m *mockNode) Expect(exp ...Expect) error { 198 m.expect <- exp 199 return <-m.err 200 } 201 202 func (m *mockNode) Stop() error { 203 m.stopOnce.Do(func() { close(m.stop) }) 204 return nil 205 } 206 207 func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error { 208 matched := make([]bool, len(exps)) 209 for { 210 msg, err := rw.ReadMsg() 211 if err != nil { 212 if err == io.EOF { 213 break 214 } 215 return err 216 } 217 actualContent, err := ioutil.ReadAll(msg.Payload) 218 if err != nil { 219 return err 220 } 221 var found bool 222 for i, exp := range exps { 223 if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(exp.Msg)) { 224 if matched[i] { 225 return fmt.Errorf("message #%d received two times", i) 226 } 227 matched[i] = true 228 found = true 229 break 230 } 231 } 232 if !found { 233 expected := make([]string, 0) 234 for i, exp := range exps { 235 if matched[i] { 236 continue 237 } 238 expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(exp.Msg))) 239 } 240 return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or ")) 241 } 242 done := true 243 for _, m := range matched { 244 if !m { 245 done = false 246 break 247 } 248 } 249 if done { 250 return nil 251 } 252 } 253 for i, m := range matched { 254 if !m { 255 return fmt.Errorf("expected message #%d not received", i) 256 } 257 } 258 return nil 259 } 260 261 // mustEncodeMsg uses rlp to encode a message. 262 // In case of error it panics. 263 func mustEncodeMsg(msg interface{}) []byte { 264 contentEnc, err := rlp.EncodeToBytes(msg) 265 if err != nil { 266 panic("content encode error: " + err.Error()) 267 } 268 return contentEnc 269 }