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