github.com/status-im/status-go@v1.1.0/peers/topicpool_test.go (about) 1 package peers 2 3 import ( 4 "net" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/suite" 9 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/ethereum/go-ethereum/crypto" 12 "github.com/ethereum/go-ethereum/p2p" 13 "github.com/ethereum/go-ethereum/p2p/discv5" 14 "github.com/ethereum/go-ethereum/p2p/enode" 15 16 "github.com/status-im/status-go/params" 17 ) 18 19 type TopicPoolSuite struct { 20 suite.Suite 21 22 peer *p2p.Server 23 topicPool *TopicPool 24 } 25 26 func TestTopicPoolSuite(t *testing.T) { 27 suite.Run(t, new(TopicPoolSuite)) 28 } 29 30 func (s *TopicPoolSuite) createDiscV5Node(ip net.IP, port uint16) (enode.ID, *discv5.Node) { 31 id, err := crypto.GenerateKey() 32 s.Require().NoError(err) 33 nodeID := enode.PubkeyToIDV4(&id.PublicKey) 34 nodeV5 := discv5.NewNode(discv5.PubkeyID(&id.PublicKey), ip, port, port) 35 return nodeID, nodeV5 36 } 37 38 func (s *TopicPoolSuite) SetupTest() { 39 maxCachedPeersMultiplier = 1 40 key, _ := crypto.GenerateKey() 41 name := common.MakeName("peer", "1.0") 42 s.peer = &p2p.Server{ 43 Config: p2p.Config{ 44 MaxPeers: 10, 45 Name: name, 46 ListenAddr: "0.0.0.0:0", 47 PrivateKey: key, 48 NoDiscovery: true, 49 }, 50 } 51 s.Require().NoError(s.peer.Start()) 52 topic := discv5.Topic("cap=cap1") 53 limits := params.NewLimits(1, 2) 54 cache, err := newInMemoryCache() 55 s.Require().NoError(err) 56 s.topicPool = newTopicPool(nil, topic, limits, 100*time.Millisecond, 200*time.Millisecond, cache) 57 s.topicPool.running = 1 58 // This is a buffered channel to simplify testing. 59 // If your test generates more than 10 mode changes, 60 // override this `period` field or consume from it 61 // using `AssertConsumed()`. 62 s.topicPool.period = make(chan time.Duration, 10) 63 } 64 65 func (s *TopicPoolSuite) TearDown() { 66 s.peer.Stop() 67 } 68 69 func (s *TopicPoolSuite) AssertConsumed(channel <-chan time.Duration, expected time.Duration, timeout time.Duration) { 70 select { 71 case received := <-channel: 72 s.Equal(expected, received) 73 case <-time.After(timeout): 74 s.FailNow("timed out waiting") 75 } 76 } 77 78 func (s *TopicPoolSuite) TestUsingCache() { 79 s.topicPool.limits = params.NewLimits(1, 1) 80 s.topicPool.maxCachedPeers = 1 81 82 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 83 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 84 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 85 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 86 87 s.topicPool.ConfirmAdded(s.peer, nodeID1) 88 s.topicPool.ConfirmAdded(s.peer, nodeID2) 89 90 cached := s.topicPool.cache.GetPeersRange(s.topicPool.topic, 10) 91 s.Contains(cached, peer1) 92 s.Contains(cached, peer2) 93 94 s.topicPool.ConfirmDropped(s.peer, nodeID2) 95 cached = s.topicPool.cache.GetPeersRange(s.topicPool.topic, 10) 96 s.Contains(cached, peer1) 97 s.Contains(cached, peer2) 98 99 // A peer that drops by itself, should be removed from the cache. 100 s.topicPool.ConfirmDropped(s.peer, nodeID1) 101 s.Equal([]*discv5.Node{peer2}, s.topicPool.cache.GetPeersRange(s.topicPool.topic, 10)) 102 } 103 104 func (s *TopicPoolSuite) TestSyncSwitches() { 105 nodeID, peer := s.createDiscV5Node(s.peer.Self().IP(), 32311) 106 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer)) 107 s.topicPool.ConfirmAdded(s.peer, nodeID) 108 s.AssertConsumed(s.topicPool.period, s.topicPool.slowMode, time.Second) 109 s.NotNil(s.topicPool.connectedPeers[nodeID]) 110 s.topicPool.ConfirmDropped(s.peer, nodeID) 111 s.AssertConsumed(s.topicPool.period, s.topicPool.fastMode, time.Second) 112 } 113 114 func (s *TopicPoolSuite) TestTimeoutFastMode() { 115 s.topicPool.fastModeTimeout = time.Millisecond * 50 116 117 // set fast mode 118 s.topicPool.mu.Lock() 119 s.topicPool.setSyncMode(s.topicPool.fastMode) 120 s.topicPool.mu.Unlock() 121 s.Equal(s.topicPool.fastMode, <-s.topicPool.period) 122 123 // switch to slow mode after `fastModeTimeout` 124 select { 125 case mode := <-s.topicPool.period: 126 s.Equal(s.topicPool.slowMode, mode) 127 case <-time.After(s.topicPool.fastModeTimeout * 2): 128 s.FailNow("timed out") 129 } 130 } 131 132 func (s *TopicPoolSuite) TestSetSyncMode() { 133 s.topicPool.fastModeTimeout = 0 134 135 // set fast mode 136 s.topicPool.setSyncMode(s.topicPool.fastMode) 137 s.Equal(s.topicPool.fastMode, <-s.topicPool.period) 138 s.Equal(s.topicPool.fastMode, s.topicPool.currentMode) 139 140 // skip setting the same mode 141 s.topicPool.setSyncMode(s.topicPool.fastMode) 142 select { 143 case <-s.topicPool.period: 144 s.FailNow("should not have update the mode") 145 default: 146 // pass 147 } 148 149 // switch to slow mode 150 cancel := make(chan struct{}) 151 s.topicPool.fastModeTimeoutCancel = cancel // should be set to nil 152 s.topicPool.setSyncMode(s.topicPool.slowMode) 153 s.Equal(s.topicPool.slowMode, <-s.topicPool.period) 154 s.Equal(s.topicPool.slowMode, s.topicPool.currentMode) 155 select { 156 case <-cancel: 157 s.Nil(s.topicPool.fastModeTimeoutCancel) 158 default: 159 s.FailNow("cancel should be closed") 160 } 161 } 162 163 func (s *TopicPoolSuite) TestNewPeerSelectedOnDrop() { 164 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 165 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 166 nodeID3, peer3 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 167 168 // add 3 nodes and confirm connection for 1 and 2 169 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 170 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 171 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer3)) 172 s.Len(s.topicPool.pendingPeers, 3) 173 s.Len(s.topicPool.discoveredPeersQueue, 0) 174 s.topicPool.ConfirmAdded(s.peer, nodeID1) 175 s.Contains(s.topicPool.connectedPeers, nodeID1) 176 s.topicPool.ConfirmAdded(s.peer, nodeID2) 177 s.Contains(s.topicPool.connectedPeers, nodeID2) 178 s.topicPool.ConfirmAdded(s.peer, nodeID3) 179 s.topicPool.ConfirmDropped(s.peer, nodeID3) 180 s.Contains(s.topicPool.pendingPeers, nodeID3) 181 s.Len(s.topicPool.pendingPeers, 1) 182 s.Len(s.topicPool.discoveredPeersQueue, 1) 183 // drop peer1 184 s.True(s.topicPool.ConfirmDropped(s.peer, nodeID1)) 185 s.NotContains(s.topicPool.connectedPeers, nodeID1) 186 // add peer from the pool 187 s.Equal(peer3.ID, s.topicPool.AddPeerFromTable(s.peer).ID) 188 s.Len(s.topicPool.pendingPeers, 1) 189 s.Len(s.topicPool.discoveredPeersQueue, 0) 190 } 191 192 type mockConstantClock struct{} 193 194 func (mockConstantClock) Now() time.Time { return time.Unix(0, 0) } 195 196 // Regression test for https://github.com/status-im/nim-status-client/issues/522 197 func (s *TopicPoolSuite) TestNewPeerLackOfClockPrecision() { 198 _, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 199 _, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 200 _, peer3 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 201 202 s.topicPool.maxPendingPeers = 2 203 204 s.topicPool.clock = mockConstantClock{} 205 206 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 207 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 208 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer3)) 209 } 210 211 func (s *TopicPoolSuite) TestRequestedDoesntRemove() { 212 // max limit is 1 because we test that 2nd peer will stay in local table 213 // when we request to drop it 214 s.topicPool.limits = params.NewLimits(1, 1) 215 s.topicPool.maxCachedPeers = 1 216 217 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 218 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 219 220 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 221 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 222 s.topicPool.ConfirmAdded(s.peer, nodeID1) 223 s.topicPool.ConfirmAdded(s.peer, nodeID2) 224 s.False(s.topicPool.connectedPeers[nodeID1].dismissed) 225 s.True(s.topicPool.connectedPeers[nodeID2].dismissed) 226 s.topicPool.ConfirmDropped(s.peer, nodeID2) 227 s.Contains(s.topicPool.pendingPeers, nodeID2) 228 s.NotContains(s.topicPool.connectedPeers, nodeID2) 229 s.topicPool.ConfirmDropped(s.peer, nodeID1) 230 s.NotContains(s.topicPool.pendingPeers, nodeID1) 231 s.NotContains(s.topicPool.connectedPeers, nodeID1) 232 } 233 234 func (s *TopicPoolSuite) TestTheMostRecentPeerIsSelected() { 235 s.topicPool.limits = params.NewLimits(1, 1) 236 s.topicPool.maxCachedPeers = 1 237 238 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 239 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 240 nodeID3, peer3 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 241 242 // after these operations, peer1 is confirmed and peer3 and peer2 243 // was added to the pool; peer3 is the most recent one 244 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 245 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 246 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer3)) 247 s.topicPool.ConfirmAdded(s.peer, nodeID1) 248 s.topicPool.ConfirmAdded(s.peer, nodeID2) 249 s.topicPool.ConfirmAdded(s.peer, nodeID3) 250 251 s.topicPool.ConfirmDropped(s.peer, nodeID2) 252 s.topicPool.ConfirmDropped(s.peer, nodeID3) 253 // peer1 has dropped 254 s.topicPool.ConfirmDropped(s.peer, nodeID1) 255 // and peer3 is take from the pool as the most recent 256 s.True(s.topicPool.pendingPeers[nodeID2].discoveredTime.Before(s.topicPool.pendingPeers[nodeID3].discoveredTime)) 257 s.Equal(peer3.ID, s.topicPool.AddPeerFromTable(s.peer).ID) 258 } 259 260 func (s *TopicPoolSuite) TestSelectPeerAfterMaxLimit() { 261 s.topicPool.limits = params.NewLimits(1, 1) 262 s.topicPool.maxCachedPeers = 1 263 264 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 265 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 266 nodeID3, peer3 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 267 268 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 269 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 270 s.topicPool.ConfirmAdded(s.peer, nodeID1) 271 s.topicPool.ConfirmAdded(s.peer, nodeID2) 272 s.topicPool.ConfirmDropped(s.peer, nodeID2) 273 s.Len(s.topicPool.pendingPeers, 1) 274 s.Contains(s.topicPool.pendingPeers, nodeID2) 275 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer3)) 276 s.Len(s.topicPool.pendingPeers, 2) 277 s.Contains(s.topicPool.pendingPeers, nodeID3) 278 s.Equal(peer3, s.topicPool.AddPeerFromTable(s.peer)) 279 } 280 281 func (s *TopicPoolSuite) TestReplacementPeerIsCounted() { 282 s.topicPool.limits = params.NewLimits(1, 1) 283 s.topicPool.maxCachedPeers = 1 284 285 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 286 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 287 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 288 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 289 s.topicPool.ConfirmAdded(s.peer, nodeID1) 290 s.topicPool.ConfirmAdded(s.peer, nodeID2) 291 s.topicPool.ConfirmDropped(s.peer, nodeID2) 292 s.topicPool.ConfirmDropped(s.peer, nodeID1) 293 294 s.NotContains(s.topicPool.pendingPeers, nodeID1) 295 s.NotContains(s.topicPool.connectedPeers, nodeID1) 296 s.Contains(s.topicPool.pendingPeers, nodeID2) 297 s.topicPool.pendingPeers[nodeID2].added = true 298 s.topicPool.ConfirmAdded(s.peer, nodeID2) 299 s.True(s.topicPool.MaxReached()) 300 } 301 302 func (s *TopicPoolSuite) TestPeerDontAddTwice() { 303 s.topicPool.limits = params.NewLimits(1, 1) 304 s.topicPool.maxCachedPeers = 1 305 306 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 307 _, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 308 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 309 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 310 s.topicPool.ConfirmAdded(s.peer, nodeID1) 311 // peer2 already added to p2p server no reason to add it again 312 s.Nil(s.topicPool.AddPeerFromTable(s.peer)) 313 } 314 315 func (s *TopicPoolSuite) TestMaxCachedPeers() { 316 s.topicPool.limits = params.NewLimits(1, 1) 317 s.topicPool.maxCachedPeers = 3 318 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 319 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 320 nodeID3, peer3 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 321 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 322 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer2)) 323 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer3)) 324 s.topicPool.ConfirmAdded(s.peer, nodeID1) 325 s.topicPool.ConfirmAdded(s.peer, nodeID2) 326 s.topicPool.ConfirmAdded(s.peer, nodeID3) 327 328 s.Equal(3, len(s.topicPool.connectedPeers)) 329 s.False(s.topicPool.connectedPeers[nodeID1].dismissed) 330 s.True(s.topicPool.connectedPeers[nodeID2].dismissed) 331 s.True(s.topicPool.connectedPeers[nodeID3].dismissed) 332 333 cached := s.topicPool.cache.GetPeersRange(s.topicPool.topic, 5) 334 s.Equal(3, len(cached)) 335 336 cachedMap := make(map[discv5.NodeID]*discv5.Node) 337 for _, peer := range cached { 338 cachedMap[peer.ID] = peer 339 } 340 341 s.topicPool.ConfirmDropped(s.peer, nodeID2) 342 s.topicPool.ConfirmDropped(s.peer, nodeID3) 343 344 s.Contains(cachedMap, peer1.ID) 345 s.Contains(cachedMap, peer2.ID) 346 s.Contains(cachedMap, peer3.ID) 347 348 s.Contains(s.topicPool.connectedPeers, nodeID1) 349 s.NotContains(s.topicPool.connectedPeers, nodeID2) 350 s.NotContains(s.topicPool.connectedPeers, nodeID3) 351 352 s.NotContains(s.topicPool.pendingPeers, nodeID1) 353 s.Contains(s.topicPool.pendingPeers, nodeID2) 354 s.Contains(s.topicPool.pendingPeers, nodeID3) 355 356 s.True(s.topicPool.maxCachedPeersReached()) 357 cached = s.topicPool.cache.GetPeersRange(s.topicPool.topic, 5) 358 s.Equal(3, len(cached)) 359 } 360 361 func (s *TopicPoolSuite) TestMaxPendingPeers() { 362 s.topicPool.maxPendingPeers = 2 363 364 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 365 nodeID2, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 366 nodeID3, peer3 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 367 pk1, _ := peer1.ID.Pubkey() 368 pk2, _ := peer2.ID.Pubkey() 369 pk3, _ := peer3.ID.Pubkey() 370 371 s.topicPool.addToPendingPeers(&peerInfo{discoveredTime: time.Now(), node: peer1, publicKey: pk1}) 372 s.topicPool.addToPendingPeers(&peerInfo{discoveredTime: time.Now(), node: peer2, publicKey: pk2}) 373 s.topicPool.addToPendingPeers(&peerInfo{discoveredTime: time.Now(), node: peer3, publicKey: pk3}) 374 375 s.Equal(2, len(s.topicPool.pendingPeers)) 376 s.Require().NotContains(s.topicPool.pendingPeers, nodeID1) 377 s.Require().Contains(s.topicPool.pendingPeers, nodeID2) 378 s.Require().Contains(s.topicPool.pendingPeers, nodeID3) 379 380 // maxPendingPeers = 0 means no limits. 381 s.topicPool.maxPendingPeers = 0 382 s.topicPool.addToPendingPeers(&peerInfo{discoveredTime: time.Now(), node: peer1, publicKey: pk1}) 383 384 s.Equal(3, len(s.topicPool.pendingPeers)) 385 s.Require().Contains(s.topicPool.pendingPeers, nodeID1) 386 s.Require().Contains(s.topicPool.pendingPeers, nodeID2) 387 s.Require().Contains(s.topicPool.pendingPeers, nodeID3) 388 } 389 390 func (s *TopicPoolSuite) TestQueueDuplicatePeers() { 391 _, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 392 _, peer2 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 393 pk1, _ := peer1.ID.Pubkey() 394 pk2, _ := peer2.ID.Pubkey() 395 peerInfo1 := &peerInfo{discoveredTime: time.Now(), node: peer1, publicKey: pk1} 396 peerInfo2 := &peerInfo{discoveredTime: time.Now(), node: peer2, publicKey: pk2} 397 398 s.topicPool.addToPendingPeers(&peerInfo{discoveredTime: time.Now(), node: peer1, publicKey: pk1}) 399 s.topicPool.addToPendingPeers(&peerInfo{discoveredTime: time.Now(), node: peer2, publicKey: pk2}) 400 s.topicPool.addToQueue(peerInfo1) 401 s.topicPool.addToQueue(peerInfo2) 402 403 s.Equal(2, len(s.topicPool.discoveredPeersQueue)) 404 s.Equal(2, len(s.topicPool.discoveredPeers)) 405 406 s.topicPool.addToQueue(peerInfo1) 407 408 s.Equal(2, len(s.topicPool.discoveredPeersQueue)) 409 s.Equal(2, len(s.topicPool.discoveredPeers)) 410 411 peer := s.topicPool.popFromQueue() 412 413 s.Equal(1, len(s.topicPool.discoveredPeersQueue)) 414 s.Equal(1, len(s.topicPool.discoveredPeers)) 415 s.Require().NotContains(s.topicPool.discoveredPeers, peer.NodeID()) 416 } 417 418 func (s *TopicPoolSuite) TestNewTopicPoolInterface() { 419 limits := params.NewLimits(1, 2) 420 cache, err := newInMemoryCache() 421 s.Require().NoError(err) 422 423 topic := discv5.Topic("cap=cap1") 424 t := newTopicPool(nil, topic, limits, 100*time.Millisecond, 200*time.Millisecond, cache) 425 s.IsType(&TopicPool{}, t) 426 427 tp := newTopicPool(nil, MailServerDiscoveryTopic, limits, 100*time.Millisecond, 200*time.Millisecond, cache) 428 cacheTP := newCacheOnlyTopicPool(tp, &testTrueVerifier{}) 429 s.IsType(&cacheOnlyTopicPool{}, cacheTP) 430 } 431 432 func (s *TopicPoolSuite) TestIgnoreInboundConnection() { 433 s.topicPool.limits = params.NewLimits(0, 0) 434 s.topicPool.maxCachedPeers = 0 435 436 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 437 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 438 s.Contains(s.topicPool.pendingPeers, nodeID1) 439 s.topicPool.ConfirmAdded(s.peer, nodeID1) 440 s.Contains(s.topicPool.pendingPeers, nodeID1) 441 s.False(s.topicPool.pendingPeers[nodeID1].dismissed) 442 s.NotContains(s.topicPool.connectedPeers, nodeID1) 443 } 444 445 func (s *TopicPoolSuite) TestConnectedButRemoved() { 446 s.topicPool.limits = params.NewLimits(0, 0) 447 s.topicPool.maxCachedPeers = 1 448 449 nodeID1, peer1 := s.createDiscV5Node(s.peer.Self().IP(), 32311) 450 s.Require().NoError(s.topicPool.processFoundNode(s.peer, peer1)) 451 s.Contains(s.topicPool.pendingPeers, nodeID1) 452 s.topicPool.ConfirmAdded(s.peer, nodeID1) 453 s.Contains(s.topicPool.connectedPeers, nodeID1) 454 s.False(s.topicPool.ConfirmDropped(s.peer, nodeID1)) 455 s.False(s.topicPool.pendingPeers[nodeID1].added) 456 }