From a77c431e378a3cfcddb4b33317319412799c96cb Mon Sep 17 00:00:00 2001
From: Felix Lange <fjl@twurst.com>
Date: Mon, 30 Mar 2015 17:23:28 +0200
Subject: [PATCH] p2p/discover: fix off by one error causing buckets to contain
 duplicates

---
 p2p/discover/table.go      |  2 +-
 p2p/discover/table_test.go | 42 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/p2p/discover/table.go b/p2p/discover/table.go
index 842f55d9f..dbf86c084 100644
--- a/p2p/discover/table.go
+++ b/p2p/discover/table.go
@@ -328,7 +328,7 @@ func (b *bucket) bump(n *Node) bool {
 		if b.entries[i].ID == n.ID {
 			n.bumpActive()
 			// move it to the front
-			copy(b.entries[1:], b.entries[:i+1])
+			copy(b.entries[1:], b.entries[:i])
 			b.entries[0] = n
 			return true
 		}
diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go
index 95ec30bea..a98376bca 100644
--- a/p2p/discover/table_test.go
+++ b/p2p/discover/table_test.go
@@ -66,6 +66,48 @@ func TestTable_pingReplace(t *testing.T) {
 	doit(false, false)
 }
 
+func TestBucket_bumpNoDuplicates(t *testing.T) {
+	t.Parallel()
+	cfg := &quick.Config{
+		MaxCount: 1000,
+		Rand:     quickrand,
+		Values: func(args []reflect.Value, rand *rand.Rand) {
+			// generate a random list of nodes. this will be the content of the bucket.
+			n := rand.Intn(bucketSize-1) + 1
+			nodes := make([]*Node, n)
+			for i := range nodes {
+				nodes[i] = &Node{ID: randomID(NodeID{}, 200)}
+			}
+			args[0] = reflect.ValueOf(nodes)
+			// generate random bump positions.
+			bumps := make([]int, rand.Intn(100))
+			for i := range bumps {
+				bumps[i] = rand.Intn(len(nodes))
+			}
+			args[1] = reflect.ValueOf(bumps)
+		},
+	}
+
+	prop := func(nodes []*Node, bumps []int) (ok bool) {
+		b := &bucket{entries: make([]*Node, len(nodes))}
+		copy(b.entries, nodes)
+		for i, pos := range bumps {
+			b.bump(b.entries[pos])
+			if hasDuplicates(b.entries) {
+				t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps))
+				for _, n := range b.entries {
+					t.Logf("  %p", n)
+				}
+				return false
+			}
+		}
+		return true
+	}
+	if err := quick.Check(prop, cfg); err != nil {
+		t.Error(err)
+	}
+}
+
 func fillBucket(tab *Table, ld int) (last *Node) {
 	b := tab.buckets[ld]
 	for len(b.entries) < bucketSize {
-- 
GitLab