table_test.go 18.4 KB
Newer Older
1
package kbucket
2 3

import (
4
	"context"
5
	"math/rand"
Aarsh Shah's avatar
Aarsh Shah committed
6
	"sync"
7
	"testing"
8
	"time"
9

10 11
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/test"
12

Jeromy's avatar
Jeromy committed
13
	pstore "github.com/libp2p/go-libp2p-peerstore"
Aarsh Shah's avatar
Aarsh Shah committed
14
	"github.com/stretchr/testify/require"
15 16
)

17 18 19 20
var PeerAlwaysValidFnc = func(ctx context.Context, p peer.ID) bool {
	return true
}

Aarsh Shah's avatar
Aarsh Shah committed
21 22 23 24 25 26 27 28 29
func TestPrint(t *testing.T) {
	t.Parallel()
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
	rt, err := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
	require.NoError(t, err)
	rt.Print()
}

30 31
// Test basic features of the bucket struct
func TestBucket(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
32 33
	t.Parallel()

34
	b := newBucket()
35

36
	peers := make([]peer.ID, 100)
37
	for i := 0; i < 100; i++ {
38
		peers[i] = test.RandPeerIDFatal(t)
Aarsh Shah's avatar
Aarsh Shah committed
39
		b.pushFront(&PeerInfo{peers[i], PeerStateActive})
40 41
	}

42
	local := test.RandPeerIDFatal(t)
43
	localID := ConvertPeerID(local)
44

45 46
	infos := b.peers()
	require.Len(t, infos, 100)
47

48
	i := rand.Intn(len(peers))
Aarsh Shah's avatar
Aarsh Shah committed
49 50
	p := b.getPeer(peers[i])
	require.NotNil(t, p)
51 52 53
	require.Equal(t, peers[i], p.Id)
	require.Equal(t, PeerStateActive, p.State)

Aarsh Shah's avatar
Aarsh Shah committed
54 55 56 57
	// mark as missing
	p.State = PeerStateMissing
	p = b.getPeer(peers[i])
	require.NotNil(t, p)
58 59 60
	require.Equal(t, PeerStateMissing, p.State)

	spl := b.split(0, ConvertPeerID(local))
61
	llist := b.list
62
	for e := llist.Front(); e != nil; e = e.Next() {
Aarsh Shah's avatar
Aarsh Shah committed
63
		p := ConvertPeerID(e.Value.(*PeerInfo).Id)
Matt Joiner's avatar
Matt Joiner committed
64
		cpl := CommonPrefixLen(p, localID)
65
		if cpl > 0 {
66
			t.Fatalf("split failed. found id with cpl > 0 in 0 bucket")
67 68 69
		}
	}

70
	rlist := spl.list
71
	for e := rlist.Front(); e != nil; e = e.Next() {
Aarsh Shah's avatar
Aarsh Shah committed
72
		p := ConvertPeerID(e.Value.(*PeerInfo).Id)
Matt Joiner's avatar
Matt Joiner committed
73
		cpl := CommonPrefixLen(p, localID)
74
		if cpl == 0 {
75
			t.Fatalf("split failed. found id with cpl == 0 in non 0 bucket")
76 77 78 79
		}
	}
}

80
func TestGenRandPeerID(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
81 82
	t.Parallel()

83 84
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
85
	rt, err := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
86
	require.NoError(t, err)
87

Aarsh Shah's avatar
Aarsh Shah committed
88 89
	// generate above maxCplForRefresh fails
	p, err := rt.GenRandPeerID(maxCplForRefresh + 1)
Aarsh Shah's avatar
Aarsh Shah committed
90 91 92 93
	require.Error(t, err)
	require.Empty(t, p)

	// test generate rand peer ID
Aarsh Shah's avatar
Aarsh Shah committed
94
	for cpl := uint(0); cpl <= maxCplForRefresh; cpl++ {
Aarsh Shah's avatar
Aarsh Shah committed
95 96 97 98
		peerID, err := rt.GenRandPeerID(cpl)
		require.NoError(t, err)

		require.True(t, uint(CommonPrefixLen(ConvertPeerID(peerID), rt.local)) == cpl, "failed for cpl=%d", cpl)
99
	}
Aarsh Shah's avatar
Aarsh Shah committed
100
}
101

Aarsh Shah's avatar
Aarsh Shah committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
func TestNPeersForCpl(t *testing.T) {
	t.Parallel()
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
	rt, err := NewRoutingTable(2, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
	require.NoError(t, err)

	require.Equal(t, 0, rt.NPeersForCpl(0))
	require.Equal(t, 0, rt.NPeersForCpl(1))

	// one peer with cpl 1
	p, _ := rt.GenRandPeerID(1)
	rt.HandlePeerAlive(p)
	require.Equal(t, 0, rt.NPeersForCpl(0))
	require.Equal(t, 1, rt.NPeersForCpl(1))
	require.Equal(t, 0, rt.NPeersForCpl(2))

	// one peer with cpl 0
	p, _ = rt.GenRandPeerID(0)
	rt.HandlePeerAlive(p)
	require.Equal(t, 1, rt.NPeersForCpl(0))
	require.Equal(t, 1, rt.NPeersForCpl(1))
	require.Equal(t, 0, rt.NPeersForCpl(2))

	// split the bucket with a peer with cpl 1
	p, _ = rt.GenRandPeerID(1)
	rt.HandlePeerAlive(p)
	require.Equal(t, 1, rt.NPeersForCpl(0))
	require.Equal(t, 2, rt.NPeersForCpl(1))
	require.Equal(t, 0, rt.NPeersForCpl(2))

	p, _ = rt.GenRandPeerID(0)
	rt.HandlePeerAlive(p)
	require.Equal(t, 2, rt.NPeersForCpl(0))
}

Aarsh Shah's avatar
Aarsh Shah committed
138 139 140 141
func TestRefreshAndGetTrackedCpls(t *testing.T) {
	t.Parallel()
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
142
	rt, err := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
143
	require.NoError(t, err)
Aarsh Shah's avatar
Aarsh Shah committed
144

145
	// push cpl's for tracking
Aarsh Shah's avatar
Aarsh Shah committed
146
	for cpl := uint(0); cpl < maxCplForRefresh; cpl++ {
Aarsh Shah's avatar
Aarsh Shah committed
147 148 149
		peerID, err := rt.GenRandPeerID(cpl)
		require.NoError(t, err)
		rt.ResetCplRefreshedAtForID(ConvertPeerID(peerID), time.Now())
150 151
	}

Aarsh Shah's avatar
Aarsh Shah committed
152 153
	// fetch cpl's
	trackedCpls := rt.GetTrackedCplsForRefresh()
Aarsh Shah's avatar
Aarsh Shah committed
154
	require.Len(t, trackedCpls, int(maxCplForRefresh))
Aarsh Shah's avatar
Aarsh Shah committed
155 156 157 158
	actualCpls := make(map[uint]struct{})
	for i := 0; i < len(trackedCpls); i++ {
		actualCpls[trackedCpls[i].Cpl] = struct{}{}
	}
159

Aarsh Shah's avatar
Aarsh Shah committed
160
	for i := uint(0); i < maxCplForRefresh; i++ {
Aarsh Shah's avatar
Aarsh Shah committed
161 162
		_, ok := actualCpls[i]
		require.True(t, ok, "tracked cpl's should have cpl %d", i)
163 164 165
	}
}

166 167 168 169 170
func TestHandlePeerDead(t *testing.T) {
	t.Parallel()

	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
171
	rt, err := NewRoutingTable(2, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
	require.NoError(t, err)

	// push 3 peers  -> 2 for the first bucket, and 1 as candidates
	var peers []peer.ID
	for i := 0; i < 3; i++ {
		p, err := rt.GenRandPeerID(uint(0))
		require.NoError(t, err)
		require.NotEmpty(t, p)
		rt.HandlePeerAlive(p)
		peers = append(peers, p)
	}

	// ensure we have 1 candidate
	rt.cplReplacementCache.Lock()
	require.NotNil(t, rt.cplReplacementCache.candidates[uint(0)])
Aarsh Shah's avatar
Aarsh Shah committed
187
	require.True(t, len(rt.cplReplacementCache.candidates[uint(0)]) == 1)
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
	rt.cplReplacementCache.Unlock()

	// mark a peer as dead and ensure it's not in the RT
	require.NotEmpty(t, rt.Find(peers[0]))
	rt.HandlePeerDead(peers[0])
	require.Empty(t, rt.Find(peers[0]))

	// mark the peer as dead & verify we don't get it as a candidate
	rt.HandlePeerDead(peers[2])

	rt.cplReplacementCache.Lock()
	require.Nil(t, rt.cplReplacementCache.candidates[uint(0)])
	rt.cplReplacementCache.Unlock()
}

Jeromy's avatar
Jeromy committed
203
func TestTableCallbacks(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
204 205
	t.Parallel()

206
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
207
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
208
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
209
	require.NoError(t, err)
Jeromy's avatar
Jeromy committed
210 211 212

	peers := make([]peer.ID, 100)
	for i := 0; i < 100; i++ {
213
		peers[i] = test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
214 215 216 217 218 219 220 221 222 223
	}

	pset := make(map[peer.ID]struct{})
	rt.PeerAdded = func(p peer.ID) {
		pset[p] = struct{}{}
	}
	rt.PeerRemoved = func(p peer.ID) {
		delete(pset, p)
	}

224
	rt.HandlePeerAlive(peers[0])
Jeromy's avatar
Jeromy committed
225 226 227 228
	if _, ok := pset[peers[0]]; !ok {
		t.Fatal("should have this peer")
	}

229
	rt.HandlePeerDead(peers[0])
Jeromy's avatar
Jeromy committed
230 231 232 233 234
	if _, ok := pset[peers[0]]; ok {
		t.Fatal("should not have this peer")
	}

	for _, p := range peers {
235
		rt.HandlePeerAlive(p)
Jeromy's avatar
Jeromy committed
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
	}

	out := rt.ListPeers()
	for _, outp := range out {
		if _, ok := pset[outp]; !ok {
			t.Fatal("should have peer in the peerset")
		}
		delete(pset, outp)
	}

	if len(pset) > 0 {
		t.Fatal("have peers in peerset that were not in the table", len(pset))
	}
}

251 252 253 254 255
func TestHandlePeerDisconnect(t *testing.T) {
	t.Parallel()

	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
256
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
257 258 259 260 261 262 263 264
	require.NoError(t, err)

	p := test.RandPeerIDFatal(t)
	// mark a peer as alive
	rt.HandlePeerAlive(p)

	// verify it's active
	rt.tabLock.Lock()
Aarsh Shah's avatar
Aarsh Shah committed
265 266
	bp := rt.buckets[0].getPeer(p)
	require.NotNil(t, bp)
267 268 269 270 271 272 273
	require.NotNil(t, bp)
	require.Equal(t, PeerStateActive, bp.State)
	rt.tabLock.Unlock()

	//now mark it as disconnected & verify it's in missing state
	rt.HandlePeerDisconnect(p)
	rt.tabLock.Lock()
Aarsh Shah's avatar
Aarsh Shah committed
274
	bp = rt.buckets[0].getPeer(p)
275 276 277 278 279
	require.NotNil(t, bp)
	require.Equal(t, PeerStateMissing, bp.State)
	rt.tabLock.Unlock()
}

280
// Right now, this just makes sure that it doesnt hang or crash
281
func TestHandlePeerAlive(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
282 283
	t.Parallel()

284
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
285
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
286
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
287
	require.NoError(t, err)
288

289
	peers := make([]peer.ID, 100)
290
	for i := 0; i < 100; i++ {
291
		peers[i] = test.RandPeerIDFatal(t)
292 293
	}

294
	// Testing HandlePeerAlive
295
	for i := 0; i < 10000; i++ {
296
		rt.HandlePeerAlive(peers[rand.Intn(len(peers))])
297 298 299
	}

	for i := 0; i < 100; i++ {
300
		id := ConvertPeerID(test.RandPeerIDFatal(t))
301 302 303 304 305 306 307 308
		ret := rt.NearestPeers(id, 5)
		if len(ret) == 0 {
			t.Fatal("Failed to find node near ID.")
		}
	}
}

func TestTableFind(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
309 310
	t.Parallel()

311
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
312
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
313
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
314
	require.NoError(t, err)
315

316
	peers := make([]peer.ID, 100)
317
	for i := 0; i < 5; i++ {
318
		peers[i] = test.RandPeerIDFatal(t)
319
		rt.HandlePeerAlive(peers[i])
320 321
	}

322
	t.Logf("Searching for peer: '%s'", peers[2])
323 324
	found := rt.NearestPeer(ConvertPeerID(peers[2]))
	if !(found == peers[2]) {
325
		t.Fatalf("Failed to lookup known node...")
326 327 328
	}
}

329 330 331 332 333
func TestCandidateAddition(t *testing.T) {
	t.Parallel()

	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
334
	rt, err := NewRoutingTable(3, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
	require.NoError(t, err)

	// generate 6 peers for the first bucket, 3 to push to it, and 3 as candidates
	var peers []peer.ID
	for i := 0; i < 6; i++ {
		p, err := rt.GenRandPeerID(uint(0))
		require.NoError(t, err)
		require.NotEmpty(t, p)
		rt.HandlePeerAlive(p)
		peers = append(peers, p)
	}

	// fetch & verify candidates
	for _, p := range peers[3:] {
		ap, b := rt.cplReplacementCache.pop(0)
		require.True(t, b)
		require.Equal(t, p, ap)
	}

	// now pop should fail as queue should be empty
	_, b := rt.cplReplacementCache.pop(0)
	require.False(t, b)
}

359
func TestTableEldestPreferred(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
360 361
	t.Parallel()

362
	local := test.RandPeerIDFatal(t)
363
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
364
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
365
	require.NoError(t, err)
366 367 368 369

	// generate size + 1 peers to saturate a bucket
	peers := make([]peer.ID, 15)
	for i := 0; i < 15; {
370
		if p := test.RandPeerIDFatal(t); CommonPrefixLen(ConvertPeerID(local), ConvertPeerID(p)) == 0 {
371 372 373 374 375 376 377
			peers[i] = p
			i++
		}
	}

	// test 10 first peers are accepted.
	for _, p := range peers[:10] {
378
		if _, err := rt.HandlePeerAlive(p); err != nil {
379 380 381 382 383 384
			t.Errorf("expected all 10 peers to be accepted; instead got: %v", err)
		}
	}

	// test next 5 peers are rejected.
	for _, p := range peers[10:] {
385
		if _, err := rt.HandlePeerAlive(p); err != ErrPeerRejectedNoCapacity {
386 387
			t.Errorf("expected extra 5 peers to be rejected; instead got: %v", err)
		}
388 389 390 391
	}
}

func TestTableFindMultiple(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
392 393
	t.Parallel()

394
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
395
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
396
	rt, err := NewRoutingTable(20, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
397
	require.NoError(t, err)
398

399
	peers := make([]peer.ID, 100)
400
	for i := 0; i < 18; i++ {
401
		peers[i] = test.RandPeerIDFatal(t)
402
		rt.HandlePeerAlive(peers[i])
403 404
	}

405
	t.Logf("Searching for peer: '%s'", peers[2])
406
	found := rt.NearestPeers(ConvertPeerID(peers[2]), 15)
407 408 409 410
	if len(found) != 15 {
		t.Fatalf("Got back different number of peers than we expected.")
	}
}
411

412 413 414 415 416 417 418 419 420 421 422 423
func assertSortedPeerIdsEqual(t *testing.T, a, b []peer.ID) {
	t.Helper()
	if len(a) != len(b) {
		t.Fatal("slices aren't the same length")
	}
	for i, p := range a {
		if p != b[i] {
			t.Fatalf("unexpected peer %d", i)
		}
	}
}

424
func TestTableFindMultipleBuckets(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
425 426
	t.Parallel()

427
	local := test.RandPeerIDFatal(t)
428
	localID := ConvertPeerID(local)
429
	m := pstore.NewMetrics()
430

Aarsh Shah's avatar
Aarsh Shah committed
431
	rt, err := NewRoutingTable(5, localID, time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
432
	require.NoError(t, err)
433 434 435 436

	peers := make([]peer.ID, 100)
	for i := 0; i < 100; i++ {
		peers[i] = test.RandPeerIDFatal(t)
437
		rt.HandlePeerAlive(peers[i])
438 439
	}

440
	targetID := ConvertPeerID(peers[2])
441

442 443 444
	closest := SortClosestPeers(rt.ListPeers(), targetID)
	targetCpl := CommonPrefixLen(localID, targetID)

445
	// split the peers into closer, same, and further from the key (than us).
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
	var (
		closer, same, further []peer.ID
	)
	var i int
	for i = 0; i < len(closest); i++ {
		cpl := CommonPrefixLen(ConvertPeerID(closest[i]), targetID)
		if targetCpl >= cpl {
			break
		}
	}
	closer = closest[:i]

	var j int
	for j = len(closer); j < len(closest); j++ {
		cpl := CommonPrefixLen(ConvertPeerID(closest[j]), targetID)
		if targetCpl > cpl {
			break
		}
	}
	same = closest[i:j]
	further = closest[j:]
Steven Allen's avatar
Steven Allen committed
467

468 469
	// should be able to find at least 30
	// ~31 (logtwo(100) * 5)
470
	found := rt.NearestPeers(targetID, 20)
471
	if len(found) != 20 {
Steven Allen's avatar
Steven Allen committed
472 473
		t.Fatalf("asked for 20 peers, got %d", len(found))
	}
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507

	// We expect this list to include:
	// * All peers with a common prefix length > than the CPL between our key
	//   and the target (peers in the target bucket).
	// * Then a subset of the peers with the _same_ CPL (peers in buckets to the right).
	// * Finally, other peers to the left of the target bucket.

	// First, handle the peers that are _closer_ than us.
	if len(found) <= len(closer) {
		// All of our peers are "closer".
		assertSortedPeerIdsEqual(t, found, closer[:len(found)])
		return
	}
	assertSortedPeerIdsEqual(t, found[:len(closer)], closer)

	// We've worked through closer peers, let's work through peers at the
	// "same" distance.
	found = found[len(closer):]

	// Next, handle peers that are at the same distance as us.
	if len(found) <= len(same) {
		// Ok, all the remaining peers are at the same distance.
		// unfortunately, that means we may be missing peers that are
		// technically closer.

		// Make sure all remaining peers are _somewhere_ in the "closer" set.
		pset := peer.NewSet()
		for _, p := range same {
			pset.Add(p)
		}
		for _, p := range found {
			if !pset.Contains(p) {
				t.Fatalf("unexpected peer %s", p)
			}
508
		}
509
		return
510
	}
511 512 513 514 515 516 517 518 519 520 521
	assertSortedPeerIdsEqual(t, found[:len(same)], same)

	// We've worked through closer peers, let's work through the further
	// peers.
	found = found[len(same):]

	// All _further_ peers will be correctly sorted.
	assertSortedPeerIdsEqual(t, found, further[:len(found)])

	// Finally, test getting _all_ peers. These should be in the correct
	// order.
Steven Allen's avatar
Steven Allen committed
522 523 524 525 526

	// Ok, now let's try finding all of them.
	found = rt.NearestPeers(ConvertPeerID(peers[2]), 100)
	if len(found) != rt.Size() {
		t.Fatalf("asked for %d peers, got %d", rt.Size(), len(found))
527
	}
528

529
	// We should get _all_ peers in sorted order.
530 531 532 533 534
	for i, p := range found {
		if p != closest[i] {
			t.Fatalf("unexpected peer %d", i)
		}
	}
535 536
}

537 538 539 540
// Looks for race conditions in table operations. For a more 'certain'
// test, increase the loop counter from 1000 to a much higher number
// and set GOMAXPROCS above 1
func TestTableMultithreaded(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
541 542
	t.Parallel()

543
	local := peer.ID("localPeer")
Jeromy's avatar
Jeromy committed
544
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
545
	tab, err := NewRoutingTable(20, ConvertPeerID(local), time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
546
	require.NoError(t, err)
547
	var peers []peer.ID
548
	for i := 0; i < 500; i++ {
549
		peers = append(peers, test.RandPeerIDFatal(t))
550 551 552 553 554 555
	}

	done := make(chan struct{})
	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
556
			tab.HandlePeerAlive(peers[n])
557 558 559 560 561 562 563
		}
		done <- struct{}{}
	}()

	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
564
			tab.HandlePeerAlive(peers[n])
565 566 567 568 569 570 571
		}
		done <- struct{}{}
	}()

	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
572
			tab.Find(peers[n])
573 574 575 576 577 578 579 580
		}
		done <- struct{}{}
	}()
	<-done
	<-done
	<-done
}

581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
func TestTableCleanup(t *testing.T) {
	t.Parallel()
	local := test.RandPeerIDFatal(t)

	// Generate:
	// 6 peers with CPL 0
	// 6 peers with CPL 1
	cplPeerMap := make(map[int][]peer.ID)
	for cpl := 0; cpl < 2; cpl++ {
		i := 0

		for {
			p := test.RandPeerIDFatal(t)
			if CommonPrefixLen(ConvertPeerID(local), ConvertPeerID(p)) == cpl {
				cplPeerMap[cpl] = append(cplPeerMap[cpl], p)

				i++
				if i == 6 {
					break
				}
			}
		}
	}

	// mock peer validation fnc that successfully validates p[1], p[3] & p[5]
Aarsh Shah's avatar
Aarsh Shah committed
606 607
	var addedCandidatesLk sync.Mutex
	addedCandidates := make(map[peer.ID]struct{})
608
	f := func(ctx context.Context, p peer.ID) bool {
Aarsh Shah's avatar
Aarsh Shah committed
609
		cpl := CommonPrefixLen(ConvertPeerID(local), ConvertPeerID(p))
610
		if cplPeerMap[cpl][1] == p || cplPeerMap[cpl][3] == p || cplPeerMap[cpl][5] == p {
Aarsh Shah's avatar
Aarsh Shah committed
611 612 613 614 615 616
			// 1 is already in the RT, but 3 & 5 are candidates
			if cplPeerMap[cpl][3] == p || cplPeerMap[cpl][5] == p {
				addedCandidatesLk.Lock()
				addedCandidates[p] = struct{}{}
				addedCandidatesLk.Unlock()
			}
617

Aarsh Shah's avatar
Aarsh Shah committed
618
			return true
619 620 621 622 623
		} else {
			return false
		}
	}

Aarsh Shah's avatar
Aarsh Shah committed
624
	// create RT with a very short cleanup interval
Aarsh Shah's avatar
Aarsh Shah committed
625
	rt, err := NewRoutingTable(3, ConvertPeerID(local), time.Hour, pstore.NewMetrics(), PeerValidationFnc(f),
Aarsh Shah's avatar
Aarsh Shah committed
626 627 628
		TableCleanupInterval(100*time.Millisecond))
	require.NoError(t, err)

629 630 631 632 633 634 635 636 637 638 639
	// for each CPL, p[0], p[1] & p[2] got the bucket & p[3], p[4] & p[5] become candidates
	for _, peers := range cplPeerMap {
		for _, p := range peers {
			rt.HandlePeerAlive(p)

		}
	}

	// validate current state
	rt.tabLock.RLock()
	require.Len(t, rt.ListPeers(), 6)
Aarsh Shah's avatar
Aarsh Shah committed
640
	ps0 := rt.buckets[0].peerIds()
641
	require.Len(t, ps0, 3)
Aarsh Shah's avatar
Aarsh Shah committed
642
	ps1 := rt.buckets[1].peerIds()
643 644 645 646 647 648 649 650 651
	require.Len(t, ps1, 3)
	require.Contains(t, ps0, cplPeerMap[0][0])
	require.Contains(t, ps0, cplPeerMap[0][1])
	require.Contains(t, ps0, cplPeerMap[0][2])
	require.Contains(t, ps1, cplPeerMap[1][0])
	require.Contains(t, ps1, cplPeerMap[1][1])
	require.Contains(t, ps1, cplPeerMap[1][2])
	rt.tabLock.RUnlock()

Aarsh Shah's avatar
Aarsh Shah committed
652
	// now disconnect peers 0 1 & 2 from both buckets so it has only 0 left after it gets validated
653 654 655 656 657 658 659 660 661 662 663
	for _, peers := range cplPeerMap {
		rt.HandlePeerDisconnect(peers[0])
		rt.HandlePeerDisconnect(peers[1])
		rt.HandlePeerDisconnect(peers[2])
	}

	// let RT cleanup complete
	time.Sleep(1 * time.Second)

	// verify RT state
	rt.tabLock.RLock()
Aarsh Shah's avatar
Aarsh Shah committed
664 665 666 667 668
	require.Len(t, rt.ListPeers(), 2)
	ps0 = rt.buckets[0].peerIds()
	require.Len(t, ps0, 1)
	ps1 = rt.buckets[1].peerIds()
	require.Len(t, ps1, 1)
669 670 671
	require.Contains(t, ps0, cplPeerMap[0][1])
	require.Contains(t, ps1, cplPeerMap[1][1])
	rt.tabLock.RUnlock()
Aarsh Shah's avatar
Aarsh Shah committed
672 673 674 675 676 677 678 679 680

	// verify candidate state
	addedCandidatesLk.Lock()
	require.Len(t, addedCandidates, 4)
	require.Contains(t, addedCandidates, cplPeerMap[0][3])
	require.Contains(t, addedCandidates, cplPeerMap[0][5])
	require.Contains(t, addedCandidates, cplPeerMap[1][3])
	require.Contains(t, addedCandidates, cplPeerMap[1][5])
	addedCandidatesLk.Unlock()
Aarsh Shah's avatar
Aarsh Shah committed
681 682 683

	// close RT
	require.NoError(t, rt.Close())
684 685 686
}

func BenchmarkHandlePeerAlive(b *testing.B) {
687
	b.StopTimer()
Chas Leichner's avatar
Chas Leichner committed
688
	local := ConvertKey("localKey")
Jeromy's avatar
Jeromy committed
689
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
690
	tab, err := NewRoutingTable(20, local, time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
691
	require.NoError(b, err)
692

693
	var peers []peer.ID
694
	for i := 0; i < b.N; i++ {
695
		peers = append(peers, test.RandPeerIDFatal(b))
696 697 698 699
	}

	b.StartTimer()
	for i := 0; i < b.N; i++ {
700
		tab.HandlePeerAlive(peers[i])
701 702 703 704 705
	}
}

func BenchmarkFinds(b *testing.B) {
	b.StopTimer()
Chas Leichner's avatar
Chas Leichner committed
706
	local := ConvertKey("localKey")
Jeromy's avatar
Jeromy committed
707
	m := pstore.NewMetrics()
Aarsh Shah's avatar
Aarsh Shah committed
708
	tab, err := NewRoutingTable(20, local, time.Hour, m, PeerValidationFnc(PeerAlwaysValidFnc))
709
	require.NoError(b, err)
710

711
	var peers []peer.ID
712
	for i := 0; i < b.N; i++ {
713
		peers = append(peers, test.RandPeerIDFatal(b))
714
		tab.HandlePeerAlive(peers[i])
715 716 717 718
	}

	b.StartTimer()
	for i := 0; i < b.N; i++ {
719
		tab.Find(peers[i])
720 721
	}
}