github.com/status-im/status-go@v1.1.0/services/ext/mailservers/connmanager_test.go (about) 1 package mailservers 2 3 import ( 4 "fmt" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/ethereum/go-ethereum/event" 13 "github.com/ethereum/go-ethereum/p2p" 14 "github.com/ethereum/go-ethereum/p2p/enode" 15 16 "github.com/status-im/status-go/eth-node/types" 17 "github.com/status-im/status-go/t/utils" 18 ) 19 20 type fakePeerEvents struct { 21 mu sync.Mutex 22 nodes map[types.EnodeID]struct{} 23 input chan *p2p.PeerEvent 24 } 25 26 func (f *fakePeerEvents) Nodes() []types.EnodeID { 27 f.mu.Lock() 28 rst := make([]types.EnodeID, 0, len(f.nodes)) 29 for n := range f.nodes { 30 rst = append(rst, n) 31 } 32 f.mu.Unlock() 33 return rst 34 } 35 36 func (f *fakePeerEvents) AddPeer(node *enode.Node) { 37 f.mu.Lock() 38 f.nodes[types.EnodeID(node.ID())] = struct{}{} 39 f.mu.Unlock() 40 if f.input == nil { 41 return 42 } 43 f.input <- &p2p.PeerEvent{ 44 Peer: node.ID(), 45 Type: p2p.PeerEventTypeAdd, 46 } 47 } 48 49 func (f *fakePeerEvents) RemovePeer(node *enode.Node) { 50 f.mu.Lock() 51 delete(f.nodes, types.EnodeID(node.ID())) 52 f.mu.Unlock() 53 if f.input == nil { 54 return 55 } 56 f.input <- &p2p.PeerEvent{ 57 Peer: node.ID(), 58 Type: p2p.PeerEventTypeDrop, 59 } 60 } 61 62 func newFakePeerAdderRemover() *fakePeerEvents { 63 return &fakePeerEvents{nodes: map[types.EnodeID]struct{}{}} 64 } 65 66 func (f *fakePeerEvents) SubscribeEvents(output chan *p2p.PeerEvent) event.Subscription { 67 return event.NewSubscription(func(quit <-chan struct{}) error { 68 for { 69 select { 70 case <-quit: 71 return nil 72 case ev := <-f.input: 73 // will block the same way as in any feed 74 output <- ev 75 } 76 } 77 }) 78 } 79 80 func newFakeServer() *fakePeerEvents { 81 srv := newFakePeerAdderRemover() 82 srv.input = make(chan *p2p.PeerEvent, 20) 83 return srv 84 } 85 86 type fakeEnvelopeEvents struct { 87 input chan types.EnvelopeEvent 88 } 89 90 func (f *fakeEnvelopeEvents) SubscribeEnvelopeEvents(output chan<- types.EnvelopeEvent) types.Subscription { 91 return event.NewSubscription(func(quit <-chan struct{}) error { 92 for { 93 select { 94 case <-quit: 95 return nil 96 case ev := <-f.input: 97 // will block the same way as in any feed 98 output <- ev 99 } 100 } 101 }) 102 } 103 104 func newFakeEnvelopesEvents() *fakeEnvelopeEvents { 105 return &fakeEnvelopeEvents{ 106 input: make(chan types.EnvelopeEvent), 107 } 108 } 109 110 func fillWithRandomNodes(t *testing.T, nodes []*enode.Node) { 111 var err error 112 for i := range nodes { 113 nodes[i], err = RandomNode() 114 require.NoError(t, err) 115 } 116 } 117 118 func getMapWithRandomNodes(t *testing.T, n int) map[types.EnodeID]*enode.Node { 119 nodes := make([]*enode.Node, n) 120 fillWithRandomNodes(t, nodes) 121 return nodesToMap(nodes) 122 } 123 124 func mergeOldIntoNew(old, new map[types.EnodeID]*enode.Node) { 125 for n := range old { 126 new[n] = old[n] 127 } 128 } 129 130 func TestReplaceNodes(t *testing.T) { 131 type testCase struct { 132 description string 133 old map[types.EnodeID]*enode.Node 134 new map[types.EnodeID]*enode.Node 135 target int 136 } 137 for _, tc := range []testCase{ 138 { 139 "InitialReplace", 140 getMapWithRandomNodes(t, 0), 141 getMapWithRandomNodes(t, 3), 142 2, 143 }, 144 { 145 "FullReplace", 146 getMapWithRandomNodes(t, 3), 147 getMapWithRandomNodes(t, 3), 148 2, 149 }, 150 } { 151 t.Run(tc.description, func(t *testing.T) { 152 peers := newFakePeerAdderRemover() 153 state := newInternalState(peers, tc.target, 0) 154 state.replaceNodes(tc.old) 155 require.Len(t, peers.nodes, len(tc.old)) 156 for n := range peers.nodes { 157 require.Contains(t, tc.old, n) 158 } 159 state.replaceNodes(tc.new) 160 require.Len(t, peers.nodes, len(tc.new)) 161 for n := range peers.nodes { 162 require.Contains(t, tc.new, n) 163 } 164 }) 165 } 166 } 167 168 func TestPartialReplaceNodesBelowTarget(t *testing.T) { 169 peers := newFakePeerAdderRemover() 170 old := getMapWithRandomNodes(t, 1) 171 new := getMapWithRandomNodes(t, 2) 172 state := newInternalState(peers, 2, 0) 173 state.replaceNodes(old) 174 mergeOldIntoNew(old, new) 175 state.replaceNodes(new) 176 require.Len(t, peers.nodes, len(new)) 177 } 178 179 func TestPartialReplaceNodesAboveTarget(t *testing.T) { 180 peers := newFakePeerAdderRemover() 181 initial, err := RandomNode() 182 require.NoError(t, err) 183 old := nodesToMap([]*enode.Node{initial}) 184 new := getMapWithRandomNodes(t, 2) 185 state := newInternalState(peers, 1, 0) 186 state.replaceNodes(old) 187 state.nodeAdded(types.EnodeID(initial.ID())) 188 mergeOldIntoNew(old, new) 189 state.replaceNodes(new) 190 require.Len(t, peers.nodes, 1) 191 } 192 193 func TestConnectionManagerAddDrop(t *testing.T) { 194 server := newFakeServer() 195 whisper := newFakeEnvelopesEvents() 196 target := 1 197 connmanager := NewConnectionManager(server, whisper, target, 1, 0) 198 connmanager.Start() 199 defer connmanager.Stop() 200 nodes := []*enode.Node{} 201 for _, n := range getMapWithRandomNodes(t, 3) { 202 nodes = append(nodes, n) 203 } 204 // Send 3 random nodes to connection manager. 205 connmanager.Notify(nodes) 206 var initial enode.ID 207 // Wait till connection manager establishes connection with 1 peer. 208 require.NoError(t, utils.Eventually(func() error { 209 nodes := server.Nodes() 210 if len(nodes) != target { 211 return fmt.Errorf("unexpected number of connected servers: %d", len(nodes)) 212 } 213 initial = enode.ID(nodes[0]) 214 return nil 215 }, time.Second, 100*time.Millisecond)) 216 // Send an event that peer was dropped. 217 select { 218 case server.input <- &p2p.PeerEvent{Peer: initial, Type: p2p.PeerEventTypeDrop}: 219 case <-time.After(time.Second): 220 require.FailNow(t, "can't send a drop event") 221 } 222 // Connection manager should establish connection with any other peer from initial list. 223 require.NoError(t, utils.Eventually(func() error { 224 nodes := server.Nodes() 225 if len(nodes) != target { 226 return fmt.Errorf("unexpected number of connected servers: %d", len(nodes)) 227 } 228 if enode.ID(nodes[0]) == initial { 229 return fmt.Errorf("connected node wasn't changed from %s", initial) 230 } 231 return nil 232 }, time.Second, 100*time.Millisecond)) 233 } 234 235 func TestConnectionManagerReplace(t *testing.T) { 236 server := newFakeServer() 237 whisper := newFakeEnvelopesEvents() 238 target := 1 239 connmanager := NewConnectionManager(server, whisper, target, 1, 0) 240 connmanager.Start() 241 defer connmanager.Stop() 242 nodes := []*enode.Node{} 243 for _, n := range getMapWithRandomNodes(t, 3) { 244 nodes = append(nodes, n) 245 } 246 // Send a single node to connection manager. 247 connmanager.Notify(nodes[:1]) 248 // Wait until this node will get connected. 249 require.NoError(t, utils.Eventually(func() error { 250 connected := server.Nodes() 251 if len(connected) != target { 252 return fmt.Errorf("unexpected number of connected servers: %d", len(connected)) 253 } 254 if types.EnodeID(nodes[0].ID()) != connected[0] { 255 return fmt.Errorf("connected with a wrong peer. expected %s, got %s", nodes[0].ID(), connected[0]) 256 } 257 return nil 258 }, time.Second, 100*time.Millisecond)) 259 // Replace previously sent node with 2 different nodes. 260 connmanager.Notify(nodes[1:]) 261 // Wait until connection manager replaces node connected in the first round. 262 require.NoError(t, utils.Eventually(func() error { 263 connected := server.Nodes() 264 if len(connected) != target { 265 return fmt.Errorf("unexpected number of connected servers: %d", len(connected)) 266 } 267 switch enode.ID(connected[0]) { 268 case nodes[1].ID(): 269 case nodes[2].ID(): 270 default: 271 return fmt.Errorf("connected with unexpected peer. got %s, expected %+v", connected[0], nodes[1:]) 272 } 273 return nil 274 }, time.Second, 100*time.Millisecond)) 275 } 276 277 func setupTestConnectionAfterExpiry(t *testing.T, server *fakePeerEvents, whisperMock *fakeEnvelopeEvents, target, maxFailures int, hash types.Hash) (*ConnectionManager, types.EnodeID) { 278 connmanager := NewConnectionManager(server, whisperMock, target, maxFailures, 0) 279 connmanager.Start() 280 nodes := []*enode.Node{} 281 for _, n := range getMapWithRandomNodes(t, 2) { 282 nodes = append(nodes, n) 283 } 284 // Send two random nodes to connection manager. 285 connmanager.Notify(nodes) 286 var initial types.EnodeID 287 // Wait until connection manager establishes connection with one node. 288 require.NoError(t, utils.Eventually(func() error { 289 nodes := server.Nodes() 290 if len(nodes) != target { 291 return fmt.Errorf("unexpected number of connected servers: %d", len(nodes)) 292 } 293 initial = nodes[0] 294 return nil 295 }, time.Second, 100*time.Millisecond)) 296 // Send event that history request for connected peer was sent. 297 select { 298 case whisperMock.input <- types.EnvelopeEvent{ 299 Event: types.EventMailServerRequestSent, Peer: initial, Hash: hash}: 300 case <-time.After(time.Second): 301 require.FailNow(t, "can't send a 'sent' event") 302 } 303 return connmanager, initial 304 } 305 306 func TestConnectionChangedAfterExpiry(t *testing.T) { 307 server := newFakeServer() 308 whisperMock := newFakeEnvelopesEvents() 309 target := 1 310 maxFailures := 1 311 hash := types.Hash{1} 312 connmanager, initial := setupTestConnectionAfterExpiry(t, server, whisperMock, target, maxFailures, hash) 313 defer connmanager.Stop() 314 315 // And eventually expired. 316 select { 317 case whisperMock.input <- types.EnvelopeEvent{ 318 Event: types.EventMailServerRequestExpired, Peer: initial, Hash: hash}: 319 case <-time.After(time.Second): 320 require.FailNow(t, "can't send an 'expiry' event") 321 } 322 require.NoError(t, utils.Eventually(func() error { 323 nodes := server.Nodes() 324 if len(nodes) != target { 325 return fmt.Errorf("unexpected number of connected servers: %d", len(nodes)) 326 } 327 if nodes[0] == initial { 328 return fmt.Errorf("connected node wasn't changed from %s", initial) 329 } 330 return nil 331 }, time.Second, 100*time.Millisecond)) 332 } 333 334 func TestConnectionChangedAfterSecondExpiry(t *testing.T) { 335 server := newFakeServer() 336 whisperMock := newFakeEnvelopesEvents() 337 target := 1 338 maxFailures := 2 339 hash := types.Hash{1} 340 connmanager, initial := setupTestConnectionAfterExpiry(t, server, whisperMock, target, maxFailures, hash) 341 defer connmanager.Stop() 342 343 // First expired is sent. Nothing should happen. 344 select { 345 case whisperMock.input <- types.EnvelopeEvent{ 346 Event: types.EventMailServerRequestExpired, Peer: initial, Hash: hash}: 347 case <-time.After(time.Second): 348 require.FailNow(t, "can't send an 'expiry' event") 349 } 350 351 // we use 'eventually' as 'consistently' because this function will retry for a given timeout while error is received 352 require.EqualError(t, utils.Eventually(func() error { 353 nodes := server.Nodes() 354 if len(nodes) != target { 355 return fmt.Errorf("unexpected number of connected servers: %d", len(nodes)) 356 } 357 if nodes[0] == initial { 358 return fmt.Errorf("connected node wasn't changed from %s", initial) 359 } 360 return nil 361 }, time.Second, 100*time.Millisecond), fmt.Sprintf("connected node wasn't changed from %s", initial)) 362 363 // second expiry event 364 select { 365 case whisperMock.input <- types.EnvelopeEvent{ 366 Event: types.EventMailServerRequestExpired, Peer: initial, Hash: hash}: 367 case <-time.After(time.Second): 368 require.FailNow(t, "can't send an 'expiry' event") 369 } 370 require.NoError(t, utils.Eventually(func() error { 371 nodes := server.Nodes() 372 if len(nodes) != target { 373 return fmt.Errorf("unexpected number of connected servers: %d", len(nodes)) 374 } 375 if nodes[0] == initial { 376 return fmt.Errorf("connected node wasn't changed from %s", initial) 377 } 378 return nil 379 }, time.Second, 100*time.Millisecond)) 380 } 381 382 func TestProcessReplacementWaitsForConnections(t *testing.T) { 383 srv := newFakePeerAdderRemover() 384 target := 1 385 timeout := time.Second 386 nodes := make([]*enode.Node, 2) 387 fillWithRandomNodes(t, nodes) 388 events := make(chan *p2p.PeerEvent) 389 state := newInternalState(srv, target, timeout) 390 state.currentNodes = nodesToMap(nodes) 391 go func() { 392 select { 393 case events <- &p2p.PeerEvent{Peer: nodes[0].ID(), Type: p2p.PeerEventTypeAdd}: 394 case <-time.After(time.Second): 395 assert.FailNow(t, "can't send a drop event") 396 } 397 }() 398 state.processReplacement(nodes, events) 399 require.Len(t, state.connected, 1) 400 }