table_test.go 12 KB
Newer Older
1
package kbucket
2 3 4 5

import (
	"math/rand"
	"testing"
6
	"time"
7

8 9
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/test"
10

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

15
var NoOpThreshold = float64(100 * time.Hour)
16

Aarsh Shah's avatar
Aarsh Shah committed
17 18 19 20
func TestPrint(t *testing.T) {
	t.Parallel()
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
21
	rt, err := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
Aarsh Shah's avatar
Aarsh Shah committed
22 23 24 25
	require.NoError(t, err)
	rt.Print()
}

26 27
// Test basic features of the bucket struct
func TestBucket(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
28
	t.Parallel()
29
	testTime1 := time.Now()
Aarsh Shah's avatar
Aarsh Shah committed
30
	testTime2 := time.Now().AddDate(1, 0, 0)
Steven Allen's avatar
Steven Allen committed
31

32
	b := newBucket()
33

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

40
	local := test.RandPeerIDFatal(t)
41
	localID := ConvertPeerID(local)
42

43 44
	infos := b.peers()
	require.Len(t, infos, 100)
45

46
	i := rand.Intn(len(peers))
Aarsh Shah's avatar
Aarsh Shah committed
47 48
	p := b.getPeer(peers[i])
	require.NotNil(t, p)
49
	require.Equal(t, peers[i], p.Id)
50
	require.Equal(t, ConvertPeerID(peers[i]), p.dhtId)
Aarsh Shah's avatar
Aarsh Shah committed
51 52
	require.EqualValues(t, testTime1, p.LastUsefulAt)
	require.EqualValues(t, testTime2, p.LastSuccessfulOutboundQueryAt)
53

54
	t2 := time.Now().Add(1 * time.Hour)
Aarsh Shah's avatar
Aarsh Shah committed
55 56 57
	t3 := t2.Add(1 * time.Hour)
	p.LastSuccessfulOutboundQueryAt = t2
	p.LastUsefulAt = t3
Aarsh Shah's avatar
Aarsh Shah committed
58 59
	p = b.getPeer(peers[i])
	require.NotNil(t, p)
Aarsh Shah's avatar
Aarsh Shah committed
60 61
	require.EqualValues(t, t2, p.LastSuccessfulOutboundQueryAt)
	require.EqualValues(t, t3, p.LastUsefulAt)
62 63

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

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

83
func TestRemovePeer(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
84
	t.Parallel()
85
	local := test.RandPeerIDFatal(t)
Aarsh Shah's avatar
Aarsh Shah committed
86

Aarsh Shah's avatar
Aarsh Shah committed
87
	m := pstore.NewMetrics()
88
	rt, err := NewRoutingTable(2, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
89 90
	require.NoError(t, err)

91 92
	p1, _ := rt.GenRandPeerID(0)
	p2, _ := rt.GenRandPeerID(0)
93 94 95 96 97 98
	b, err := rt.TryAddPeer(p1, true)
	require.True(t, b)
	require.NoError(t, err)
	b, err = rt.TryAddPeer(p2, true)
	require.True(t, b)
	require.NoError(t, err)
99 100 101 102 103

	// ensure p1 & p2 are in the RT
	require.Len(t, rt.ListPeers(), 2)
	require.Contains(t, rt.ListPeers(), p1)
	require.Contains(t, rt.ListPeers(), p2)
104

105
	// remove a peer and ensure it's not in the RT
106
	require.NotEmpty(t, rt.Find(p1))
107
	rt.RemovePeer(p1)
108
	require.Empty(t, rt.Find(p1))
Aarsh Shah's avatar
Aarsh Shah committed
109
	require.NotEmpty(t, rt.Find(p2))
110 111
}

Jeromy's avatar
Jeromy committed
112
func TestTableCallbacks(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
113 114
	t.Parallel()

115
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
116
	m := pstore.NewMetrics()
117
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
118
	require.NoError(t, err)
Jeromy's avatar
Jeromy committed
119 120 121

	peers := make([]peer.ID, 100)
	for i := 0; i < 100; i++ {
122
		peers[i] = test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
123 124 125 126 127 128 129 130 131 132
	}

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

133
	rt.TryAddPeer(peers[0], true)
Jeromy's avatar
Jeromy committed
134 135 136 137
	if _, ok := pset[peers[0]]; !ok {
		t.Fatal("should have this peer")
	}

138
	rt.RemovePeer(peers[0])
Jeromy's avatar
Jeromy committed
139 140 141 142 143
	if _, ok := pset[peers[0]]; ok {
		t.Fatal("should not have this peer")
	}

	for _, p := range peers {
144
		rt.TryAddPeer(p, true)
Jeromy's avatar
Jeromy committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
	}

	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))
	}
}

160
// Right now, this just makes sure that it doesnt hang or crash
161
func TestTryAddPeerLoad(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
162 163
	t.Parallel()

164
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
165
	m := pstore.NewMetrics()
166
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
167
	require.NoError(t, err)
168

169
	peers := make([]peer.ID, 100)
170
	for i := 0; i < 100; i++ {
171
		peers[i] = test.RandPeerIDFatal(t)
172 173 174
	}

	for i := 0; i < 10000; i++ {
175
		rt.TryAddPeer(peers[rand.Intn(len(peers))], true)
176 177 178
	}

	for i := 0; i < 100; i++ {
179
		id := ConvertPeerID(test.RandPeerIDFatal(t))
180 181 182 183 184 185 186 187
		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
188 189
	t.Parallel()

190
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
191
	m := pstore.NewMetrics()
192
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
193
	require.NoError(t, err)
194

195
	peers := make([]peer.ID, 100)
196
	for i := 0; i < 5; i++ {
197
		peers[i] = test.RandPeerIDFatal(t)
198
		rt.TryAddPeer(peers[i], true)
199 200
	}

201
	t.Logf("Searching for peer: '%s'", peers[2])
202 203
	found := rt.NearestPeer(ConvertPeerID(peers[2]))
	if !(found == peers[2]) {
204
		t.Fatalf("Failed to lookup known node...")
205 206 207
	}
}

Aarsh Shah's avatar
Aarsh Shah committed
208
func TestUpdateLastSuccessfulOutboundQueryAt(t *testing.T) {
209 210
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
211
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
212 213
	require.NoError(t, err)

214 215 216 217
	p := test.RandPeerIDFatal(t)
	b, err := rt.TryAddPeer(p, true)
	require.True(t, b)
	require.NoError(t, err)
218

219 220
	// increment and assert
	t2 := time.Now().Add(1 * time.Hour)
Aarsh Shah's avatar
Aarsh Shah committed
221
	rt.UpdateLastSuccessfulOutboundQueryAt(p, t2)
222 223 224
	rt.tabLock.Lock()
	pi := rt.buckets[0].getPeer(p)
	require.NotNil(t, pi)
Aarsh Shah's avatar
Aarsh Shah committed
225
	require.EqualValues(t, t2, pi.LastSuccessfulOutboundQueryAt)
226
	rt.tabLock.Unlock()
227 228
}

Aarsh Shah's avatar
Aarsh Shah committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
func TestUpdateLastUsefulAt(t *testing.T) {
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
	require.NoError(t, err)

	p := test.RandPeerIDFatal(t)
	b, err := rt.TryAddPeer(p, true)
	require.True(t, b)
	require.NoError(t, err)

	// increment and assert
	t2 := time.Now().Add(1 * time.Hour)
	rt.UpdateLastUsefulAt(p, t2)
	rt.tabLock.Lock()
	pi := rt.buckets[0].getPeer(p)
	require.NotNil(t, pi)
	require.EqualValues(t, t2, pi.LastUsefulAt)
	rt.tabLock.Unlock()
}

250 251
func TestTryAddPeer(t *testing.T) {
	minThreshold := float64(24 * 1 * time.Hour)
Steven Allen's avatar
Steven Allen committed
252 253
	t.Parallel()

254
	local := test.RandPeerIDFatal(t)
255
	m := pstore.NewMetrics()
256
	rt, err := NewRoutingTable(2, ConvertPeerID(local), time.Hour, m, minThreshold)
257
	require.NoError(t, err)
258

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
	// generate 2 peers to saturate the first bucket for cpl=0
	p1, _ := rt.GenRandPeerID(0)
	b, err := rt.TryAddPeer(p1, true)
	require.NoError(t, err)
	require.True(t, b)
	p2, _ := rt.GenRandPeerID(0)
	b, err = rt.TryAddPeer(p2, true)
	require.NoError(t, err)
	require.True(t, b)
	require.Equal(t, p1, rt.Find(p1))
	require.Equal(t, p2, rt.Find(p2))

	// trying to add a peer with cpl=0 fails
	p3, _ := rt.GenRandPeerID(0)
	b, err = rt.TryAddPeer(p3, true)
	require.Equal(t, ErrPeerRejectedNoCapacity, err)
	require.False(t, b)
	require.Empty(t, rt.Find(p3))
277

278 279 280 281 282 283 284
	// however, trying to add peer with cpl=1 works
	p4, _ := rt.GenRandPeerID(1)
	b, err = rt.TryAddPeer(p4, true)
	require.NoError(t, err)
	require.True(t, b)
	require.Equal(t, p4, rt.Find(p4))

Aarsh Shah's avatar
Aarsh Shah committed
285
	// adding a peer with cpl 0 works if an existing peer has LastUsefulAt above the max threshold
286
	// because that existing peer will get replaced
Aarsh Shah's avatar
Aarsh Shah committed
287
	require.True(t, rt.UpdateLastUsefulAt(p2, time.Now().AddDate(0, 0, -2)))
288 289 290 291 292 293 294 295 296 297 298
	b, err = rt.TryAddPeer(p3, true)
	require.NoError(t, err)
	require.True(t, b)
	require.Equal(t, p3, rt.Find(p3))
	require.Equal(t, p1, rt.Find(p1))
	// p2 has been removed
	require.Empty(t, rt.Find(p2))

	// however adding peer fails if below threshold
	p5, err := rt.GenRandPeerID(0)
	require.NoError(t, err)
Aarsh Shah's avatar
Aarsh Shah committed
299
	require.True(t, rt.UpdateLastUsefulAt(p1, time.Now()))
300 301 302 303 304 305 306 307 308 309 310 311 312
	b, err = rt.TryAddPeer(p5, true)
	require.Error(t, err)
	require.False(t, b)

	// adding non query peer
	p6, err := rt.GenRandPeerID(3)
	require.NoError(t, err)
	b, err = rt.TryAddPeer(p6, false)
	require.NoError(t, err)
	require.True(t, b)
	rt.tabLock.Lock()
	pi := rt.buckets[rt.bucketIdForPeer(p6)].getPeer(p6)
	require.NotNil(t, p6)
Aarsh Shah's avatar
Aarsh Shah committed
313
	require.True(t, pi.LastUsefulAt.IsZero())
314
	rt.tabLock.Unlock()
315

316 317 318
}

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

321
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
322
	m := pstore.NewMetrics()
323
	rt, err := NewRoutingTable(20, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
324
	require.NoError(t, err)
325

326
	peers := make([]peer.ID, 100)
327
	for i := 0; i < 18; i++ {
328
		peers[i] = test.RandPeerIDFatal(t)
329
		rt.TryAddPeer(peers[i], true)
330 331
	}

332
	t.Logf("Searching for peer: '%s'", peers[2])
333
	found := rt.NearestPeers(ConvertPeerID(peers[2]), 15)
334 335 336 337
	if len(found) != 15 {
		t.Fatalf("Got back different number of peers than we expected.")
	}
}
338

339
func TestTableFindMultipleBuckets(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
340 341
	t.Parallel()

342 343
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
344

Steven Allen's avatar
Steven Allen committed
345
	rt, err := NewRoutingTable(5, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
346
	require.NoError(t, err)
347 348 349 350

	peers := make([]peer.ID, 100)
	for i := 0; i < 100; i++ {
		peers[i] = test.RandPeerIDFatal(t)
351
		rt.TryAddPeer(peers[i], true)
352 353
	}

Steven Allen's avatar
Steven Allen committed
354
	closest := SortClosestPeers(rt.ListPeers(), ConvertPeerID(peers[2]))
355

Steven Allen's avatar
Steven Allen committed
356
	t.Logf("Searching for peer: '%s'", peers[2])
Steven Allen's avatar
Steven Allen committed
357

358 359
	// should be able to find at least 30
	// ~31 (logtwo(100) * 5)
Steven Allen's avatar
Steven Allen committed
360
	found := rt.NearestPeers(ConvertPeerID(peers[2]), 20)
361
	if len(found) != 20 {
Steven Allen's avatar
Steven Allen committed
362 363
		t.Fatalf("asked for 20 peers, got %d", len(found))
	}
Steven Allen's avatar
Steven Allen committed
364 365 366
	for i, p := range found {
		if p != closest[i] {
			t.Fatalf("unexpected peer %d", i)
367 368
		}
	}
Steven Allen's avatar
Steven Allen committed
369 370 371 372 373

	// 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))
374
	}
375 376 377 378 379 380

	for i, p := range found {
		if p != closest[i] {
			t.Fatalf("unexpected peer %d", i)
		}
	}
381 382
}

383 384 385 386
// 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
387 388
	t.Parallel()

389
	local := peer.ID("localPeer")
Jeromy's avatar
Jeromy committed
390
	m := pstore.NewMetrics()
391
	tab, err := NewRoutingTable(20, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
392
	require.NoError(t, err)
393
	var peers []peer.ID
394
	for i := 0; i < 500; i++ {
395
		peers = append(peers, test.RandPeerIDFatal(t))
396 397 398 399 400 401
	}

	done := make(chan struct{})
	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
402
			tab.TryAddPeer(peers[n], true)
403 404 405 406 407 408 409
		}
		done <- struct{}{}
	}()

	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
410
			tab.TryAddPeer(peers[n], true)
411 412 413 414 415 416 417
		}
		done <- struct{}{}
	}()

	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
418
			tab.Find(peers[n])
419 420 421 422 423 424 425 426
		}
		done <- struct{}{}
	}()
	<-done
	<-done
	<-done
}

Aarsh Shah's avatar
Aarsh Shah committed
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
func TestGetPeerInfos(t *testing.T) {
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
	rt, err := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m, NoOpThreshold)
	require.NoError(t, err)

	require.Empty(t, rt.GetPeerInfos())

	p1 := test.RandPeerIDFatal(t)
	p2 := test.RandPeerIDFatal(t)

	b, err := rt.TryAddPeer(p1, false)
	require.True(t, b)
	require.NoError(t, err)
	b, err = rt.TryAddPeer(p2, true)
	require.True(t, b)
	require.NoError(t, err)

	ps := rt.GetPeerInfos()
	require.Len(t, ps, 2)
	ms := make(map[peer.ID]PeerInfo)
	for _, p := range ps {
		ms[p.Id] = p
	}

	require.Equal(t, p1, ms[p1].Id)
Aarsh Shah's avatar
Aarsh Shah committed
453
	require.True(t, ms[p1].LastUsefulAt.IsZero())
Aarsh Shah's avatar
Aarsh Shah committed
454
	require.Equal(t, p2, ms[p2].Id)
Aarsh Shah's avatar
Aarsh Shah committed
455
	require.False(t, ms[p2].LastUsefulAt.IsZero())
Aarsh Shah's avatar
Aarsh Shah committed
456 457
}

458
func BenchmarkAddPeer(b *testing.B) {
459
	b.StopTimer()
Chas Leichner's avatar
Chas Leichner committed
460
	local := ConvertKey("localKey")
Jeromy's avatar
Jeromy committed
461
	m := pstore.NewMetrics()
462
	tab, err := NewRoutingTable(20, local, time.Hour, m, NoOpThreshold)
463
	require.NoError(b, err)
464

465
	var peers []peer.ID
466
	for i := 0; i < b.N; i++ {
467
		peers = append(peers, test.RandPeerIDFatal(b))
468 469 470 471
	}

	b.StartTimer()
	for i := 0; i < b.N; i++ {
472
		tab.TryAddPeer(peers[i], true)
473 474 475 476 477
	}
}

func BenchmarkFinds(b *testing.B) {
	b.StopTimer()
Chas Leichner's avatar
Chas Leichner committed
478
	local := ConvertKey("localKey")
Jeromy's avatar
Jeromy committed
479
	m := pstore.NewMetrics()
480
	tab, err := NewRoutingTable(20, local, time.Hour, m, NoOpThreshold)
481
	require.NoError(b, err)
482

483
	var peers []peer.ID
484
	for i := 0; i < b.N; i++ {
485
		peers = append(peers, test.RandPeerIDFatal(b))
486
		tab.TryAddPeer(peers[i], true)
487 488 489 490
	}

	b.StartTimer()
	for i := 0; i < b.N; i++ {
491
		tab.Find(peers[i])
492 493
	}
}