table_test.go 10.4 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"
Jeromy's avatar
Jeromy committed
10
	pstore "github.com/libp2p/go-libp2p-peerstore"
Aarsh Shah's avatar
Aarsh Shah committed
11
	"github.com/stretchr/testify/require"
12 13 14 15
)

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

18
	b := newBucket()
19

20
	peers := make([]peer.ID, 100)
21
	for i := 0; i < 100; i++ {
22
		peers[i] = test.RandPeerIDFatal(t)
23
		b.PushFront(peers[i])
24 25
	}

26
	local := test.RandPeerIDFatal(t)
27
	localID := ConvertPeerID(local)
28 29

	i := rand.Intn(len(peers))
30
	if !b.Has(peers[i]) {
31 32 33
		t.Errorf("Failed to find peer: %v", peers[i])
	}

34
	spl := b.Split(0, ConvertPeerID(local))
35
	llist := b.list
36
	for e := llist.Front(); e != nil; e = e.Next() {
37
		p := ConvertPeerID(e.Value.(peer.ID))
Matt Joiner's avatar
Matt Joiner committed
38
		cpl := CommonPrefixLen(p, localID)
39 40 41 42 43
		if cpl > 0 {
			t.Fatalf("Split failed. found id with cpl > 0 in 0 bucket")
		}
	}

44
	rlist := spl.list
45
	for e := rlist.Front(); e != nil; e = e.Next() {
46
		p := ConvertPeerID(e.Value.(peer.ID))
Matt Joiner's avatar
Matt Joiner committed
47
		cpl := CommonPrefixLen(p, localID)
48 49 50 51 52 53
		if cpl == 0 {
			t.Fatalf("Split failed. found id with cpl == 0 in non 0 bucket")
		}
	}
}

54
func TestGenRandPeerID(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
55 56
	t.Parallel()

57 58 59 60
	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
	rt := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m)

Aarsh Shah's avatar
Aarsh Shah committed
61 62
	// generate above maxCplForRefresh fails
	p, err := rt.GenRandPeerID(maxCplForRefresh + 1)
Aarsh Shah's avatar
Aarsh Shah committed
63 64 65 66
	require.Error(t, err)
	require.Empty(t, p)

	// test generate rand peer ID
Aarsh Shah's avatar
Aarsh Shah committed
67
	for cpl := uint(0); cpl <= maxCplForRefresh; cpl++ {
Aarsh Shah's avatar
Aarsh Shah committed
68 69 70 71
		peerID, err := rt.GenRandPeerID(cpl)
		require.NoError(t, err)

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

Aarsh Shah's avatar
Aarsh Shah committed
75 76 77 78 79 80 81 82
func TestRefreshAndGetTrackedCpls(t *testing.T) {
	t.Parallel()

	local := test.RandPeerIDFatal(t)
	m := pstore.NewMetrics()
	rt := NewRoutingTable(1, ConvertPeerID(local), time.Hour, m)

	// add cpl's for tracking
Aarsh Shah's avatar
Aarsh Shah committed
83
	for cpl := uint(0); cpl < maxCplForRefresh; cpl++ {
Aarsh Shah's avatar
Aarsh Shah committed
84 85 86
		peerID, err := rt.GenRandPeerID(cpl)
		require.NoError(t, err)
		rt.ResetCplRefreshedAtForID(ConvertPeerID(peerID), time.Now())
87 88
	}

Aarsh Shah's avatar
Aarsh Shah committed
89 90
	// fetch cpl's
	trackedCpls := rt.GetTrackedCplsForRefresh()
Aarsh Shah's avatar
Aarsh Shah committed
91
	require.Len(t, trackedCpls, int(maxCplForRefresh))
Aarsh Shah's avatar
Aarsh Shah committed
92 93 94 95
	actualCpls := make(map[uint]struct{})
	for i := 0; i < len(trackedCpls); i++ {
		actualCpls[trackedCpls[i].Cpl] = struct{}{}
	}
96

Aarsh Shah's avatar
Aarsh Shah committed
97
	for i := uint(0); i < maxCplForRefresh; i++ {
Aarsh Shah's avatar
Aarsh Shah committed
98 99
		_, ok := actualCpls[i]
		require.True(t, ok, "tracked cpl's should have cpl %d", i)
100 101 102
	}
}

Jeromy's avatar
Jeromy committed
103
func TestTableCallbacks(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
104 105
	t.Parallel()

106
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
107 108 109 110 111
	m := pstore.NewMetrics()
	rt := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m)

	peers := make([]peer.ID, 100)
	for i := 0; i < 100; i++ {
112
		peers[i] = test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
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 138 139 140 141 142 143 144 145 146 147 148 149
	}

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

	rt.Update(peers[0])
	if _, ok := pset[peers[0]]; !ok {
		t.Fatal("should have this peer")
	}

	rt.Remove(peers[0])
	if _, ok := pset[peers[0]]; ok {
		t.Fatal("should not have this peer")
	}

	for _, p := range peers {
		rt.Update(p)
	}

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

150 151
// Right now, this just makes sure that it doesnt hang or crash
func TestTableUpdate(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
152 153
	t.Parallel()

154
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
155
	m := pstore.NewMetrics()
156
	rt := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m)
157

158
	peers := make([]peer.ID, 100)
159
	for i := 0; i < 100; i++ {
160
		peers[i] = test.RandPeerIDFatal(t)
161 162 163 164
	}

	// Testing Update
	for i := 0; i < 10000; i++ {
165
		rt.Update(peers[rand.Intn(len(peers))])
166 167 168
	}

	for i := 0; i < 100; i++ {
169
		id := ConvertPeerID(test.RandPeerIDFatal(t))
170 171 172 173 174 175 176 177
		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
178 179
	t.Parallel()

180
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
181
	m := pstore.NewMetrics()
182
	rt := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m)
183

184
	peers := make([]peer.ID, 100)
185
	for i := 0; i < 5; i++ {
186
		peers[i] = test.RandPeerIDFatal(t)
187 188 189
		rt.Update(peers[i])
	}

190
	t.Logf("Searching for peer: '%s'", peers[2])
191 192
	found := rt.NearestPeer(ConvertPeerID(peers[2]))
	if !(found == peers[2]) {
193
		t.Fatalf("Failed to lookup known node...")
194 195 196 197
	}
}

func TestTableEldestPreferred(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
198 199
	t.Parallel()

200
	local := test.RandPeerIDFatal(t)
201 202 203 204 205 206
	m := pstore.NewMetrics()
	rt := NewRoutingTable(10, ConvertPeerID(local), time.Hour, m)

	// generate size + 1 peers to saturate a bucket
	peers := make([]peer.ID, 15)
	for i := 0; i < 15; {
207
		if p := test.RandPeerIDFatal(t); CommonPrefixLen(ConvertPeerID(local), ConvertPeerID(p)) == 0 {
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
			peers[i] = p
			i++
		}
	}

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

	// test next 5 peers are rejected.
	for _, p := range peers[10:] {
		if _, err := rt.Update(p); err != ErrPeerRejectedNoCapacity {
			t.Errorf("expected extra 5 peers to be rejected; instead got: %v", err)
		}
225 226 227 228
	}
}

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

231
	local := test.RandPeerIDFatal(t)
Jeromy's avatar
Jeromy committed
232
	m := pstore.NewMetrics()
233
	rt := NewRoutingTable(20, ConvertPeerID(local), time.Hour, m)
234

235
	peers := make([]peer.ID, 100)
236
	for i := 0; i < 18; i++ {
237
		peers[i] = test.RandPeerIDFatal(t)
238 239 240
		rt.Update(peers[i])
	}

241
	t.Logf("Searching for peer: '%s'", peers[2])
242
	found := rt.NearestPeers(ConvertPeerID(peers[2]), 15)
243 244 245 246
	if len(found) != 15 {
		t.Fatalf("Got back different number of peers than we expected.")
	}
}
247

248 249 250 251 252 253 254 255 256 257 258 259
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)
		}
	}
}

260
func TestTableFindMultipleBuckets(t *testing.T) {
Steven Allen's avatar
Steven Allen committed
261 262
	t.Parallel()

263
	local := test.RandPeerIDFatal(t)
264
	localID := ConvertPeerID(local)
265
	m := pstore.NewMetrics()
266 267

	rt := NewRoutingTable(5, localID, time.Hour, m)
268 269 270 271 272 273 274

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

275
	targetID := ConvertPeerID(peers[2])
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
	closest := SortClosestPeers(rt.ListPeers(), targetID)
	targetCpl := CommonPrefixLen(localID, targetID)

	// Split the peers into closer, same, and further from the key (than us).
	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
302

303 304
	// should be able to find at least 30
	// ~31 (logtwo(100) * 5)
305
	found := rt.NearestPeers(targetID, 20)
306
	if len(found) != 20 {
Steven Allen's avatar
Steven Allen committed
307 308
		t.Fatalf("asked for 20 peers, got %d", len(found))
	}
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342

	// 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)
			}
343
		}
344
		return
345
	}
346 347 348 349 350 351 352 353 354 355 356
	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
357 358 359 360 361

	// 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))
362
	}
363

364
	// We should get _all_ peers in sorted order.
365 366 367 368 369
	for i, p := range found {
		if p != closest[i] {
			t.Fatalf("unexpected peer %d", i)
		}
	}
370 371
}

372 373 374 375
// 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
376 377
	t.Parallel()

378
	local := peer.ID("localPeer")
Jeromy's avatar
Jeromy committed
379
	m := pstore.NewMetrics()
380 381
	tab := NewRoutingTable(20, ConvertPeerID(local), time.Hour, m)
	var peers []peer.ID
382
	for i := 0; i < 500; i++ {
383
		peers = append(peers, test.RandPeerIDFatal(t))
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
	}

	done := make(chan struct{})
	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
			tab.Update(peers[n])
		}
		done <- struct{}{}
	}()

	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
			tab.Update(peers[n])
		}
		done <- struct{}{}
	}()

	go func() {
		for i := 0; i < 1000; i++ {
			n := rand.Intn(len(peers))
406
			tab.Find(peers[n])
407 408 409 410 411 412 413 414 415 416
		}
		done <- struct{}{}
	}()
	<-done
	<-done
	<-done
}

func BenchmarkUpdates(b *testing.B) {
	b.StopTimer()
Chas Leichner's avatar
Chas Leichner committed
417
	local := ConvertKey("localKey")
Jeromy's avatar
Jeromy committed
418
	m := pstore.NewMetrics()
419
	tab := NewRoutingTable(20, local, time.Hour, m)
420

421
	var peers []peer.ID
422
	for i := 0; i < b.N; i++ {
423
		peers = append(peers, test.RandPeerIDFatal(b))
424 425 426 427 428 429 430 431 432 433
	}

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

func BenchmarkFinds(b *testing.B) {
	b.StopTimer()
Chas Leichner's avatar
Chas Leichner committed
434
	local := ConvertKey("localKey")
Jeromy's avatar
Jeromy committed
435
	m := pstore.NewMetrics()
436
	tab := NewRoutingTable(20, local, time.Hour, m)
437

438
	var peers []peer.ID
439
	for i := 0; i < b.N; i++ {
440
		peers = append(peers, test.RandPeerIDFatal(b))
441 442 443 444 445
		tab.Update(peers[i])
	}

	b.StartTimer()
	for i := 0; i < b.N; i++ {
446
		tab.Find(peers[i])
447 448
	}
}