From a3d0a3543a6604a87b3414c1685a5a7a2bb5032a Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Thu, 12 Jul 2018 14:34:24 -0400 Subject: [PATCH] bucket splitting is solid --- dht/bootstrap.go | 32 +++--- dht/routing_table.go | 105 +++++++++--------- dht/routing_table_test.go | 218 ++++++++++++++++++++++++-------------- 3 files changed, 203 insertions(+), 152 deletions(-) diff --git a/dht/bootstrap.go b/dht/bootstrap.go index fde973a..b31f0aa 100644 --- a/dht/bootstrap.go +++ b/dht/bootstrap.go @@ -21,7 +21,7 @@ type BootstrapNode struct { checkInterval time.Duration nlock *sync.RWMutex - nodes map[bits.Bitmap]*peer + peers map[bits.Bitmap]*peer nodeIDs []bits.Bitmap // necessary for efficient random ID selection } @@ -34,7 +34,7 @@ func NewBootstrapNode(id bits.Bitmap, initialPingInterval, rePingInterval time.D checkInterval: rePingInterval, nlock: &sync.RWMutex{}, - nodes: make(map[bits.Bitmap]*peer), + peers: make(map[bits.Bitmap]*peer), nodeIDs: make([]bits.Bitmap, 0), } @@ -77,14 +77,14 @@ func (b *BootstrapNode) upsert(c Contact) { b.nlock.Lock() defer b.nlock.Unlock() - if node, exists := b.nodes[c.ID]; exists { - log.Debugf("[%s] bootstrap: touching contact %s", b.id.HexShort(), node.Contact.ID.HexShort()) - node.Touch() + if peer, exists := b.peers[c.ID]; exists { + log.Debugf("[%s] bootstrap: touching contact %s", b.id.HexShort(), peer.Contact.ID.HexShort()) + peer.Touch() return } log.Debugf("[%s] bootstrap: adding new contact %s", b.id.HexShort(), c.ID.HexShort()) - b.nodes[c.ID] = &peer{c, time.Now(), 0} + b.peers[c.ID] = &peer{c, b.id.Xor(c.ID), time.Now(), 0} b.nodeIDs = append(b.nodeIDs, c.ID) } @@ -93,13 +93,13 @@ func (b *BootstrapNode) remove(c Contact) { b.nlock.Lock() defer b.nlock.Unlock() - _, exists := b.nodes[c.ID] + _, exists := b.peers[c.ID] if !exists { return } log.Debugf("[%s] bootstrap: removing contact %s", b.id.HexShort(), c.ID.HexShort()) - delete(b.nodes, c.ID) + delete(b.peers, c.ID) for i := range b.nodeIDs { if b.nodeIDs[i].Equals(c.ID) { b.nodeIDs = append(b.nodeIDs[:i], b.nodeIDs[i+1:]...) @@ -113,13 +113,13 @@ func (b *BootstrapNode) get(limit int) []Contact { b.nlock.RLock() defer b.nlock.RUnlock() - if len(b.nodes) < limit { - limit = len(b.nodes) + if len(b.peers) < limit { + limit = len(b.peers) } ret := make([]Contact, limit) for i, k := range randKeys(len(b.nodeIDs))[:limit] { - ret[i] = b.nodes[b.nodeIDs[k]].Contact + ret[i] = b.peers[b.nodeIDs[k]].Contact } return ret @@ -152,9 +152,9 @@ func (b *BootstrapNode) check() { b.nlock.RLock() defer b.nlock.RUnlock() - for i := range b.nodes { - if !b.nodes[i].ActiveInLast(b.checkInterval) { - go b.ping(b.nodes[i].Contact) + for i := range b.peers { + if !b.peers[i].ActiveInLast(b.checkInterval) { + go b.ping(b.peers[i].Contact) } } } @@ -185,13 +185,13 @@ func (b *BootstrapNode) handleRequest(addr *net.UDPAddr, request Request) { go func() { b.nlock.RLock() - _, exists := b.nodes[request.NodeID] + _, exists := b.peers[request.NodeID] b.nlock.RUnlock() if !exists { log.Debugf("[%s] bootstrap: queuing %s to ping", b.id.HexShort(), request.NodeID.HexShort()) <-time.After(b.initialPingInterval) b.nlock.RLock() - _, exists = b.nodes[request.NodeID] + _, exists = b.peers[request.NodeID] b.nlock.RUnlock() if !exists { b.ping(Contact{ID: request.NodeID, IP: addr.IP, Port: addr.Port}) diff --git a/dht/routing_table.go b/dht/routing_table.go index 2df09a8..7b53d9a 100644 --- a/dht/routing_table.go +++ b/dht/routing_table.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/davecgh/go-spew/spew" "github.com/lbryio/lbry.go/errors" "github.com/lbryio/lbry.go/stop" "github.com/lbryio/reflector.go/dht/bits" @@ -21,9 +20,10 @@ import ( // TODO: use a tree with bucket splitting instead of a fixed bucket list. include jack's optimization (see link in commit mesg) // https://github.com/lbryio/lbry/pull/1211/commits/341b27b6d21ac027671d42458826d02735aaae41 -// peer is a contact with extra freshness information +// peer is a contact with extra information type peer struct { Contact Contact + Distance bits.Bitmap LastActivity time.Time // LastReplied time.Time // LastRequested time.Time @@ -79,7 +79,7 @@ func (b bucket) Len() int { return len(b.peers) } -func (b bucket) Contains(c Contact) bool { +func (b bucket) Has(c Contact) bool { b.lock.RLock() defer b.lock.RUnlock() for _, p := range b.peers { @@ -101,30 +101,27 @@ func (b bucket) Contacts() []Contact { return contacts } -// UpdateContact marks a contact as having been successfully contacted. if insertIfNew and the contact is does not exist yet, it is inserted -func (b *bucket) UpdateContact(c Contact, insertIfNew bool) { +// UpdatePeer marks a contact as having been successfully contacted. if insertIfNew and the contact is does not exist yet, it is inserted +func (b *bucket) UpdatePeer(p peer, insertIfNew bool) error { b.lock.Lock() defer b.lock.Unlock() - fmt.Printf("updating contact %s\n", c.ID) - // TODO: verify the peer is in the bucket key range + if !b.Range.Contains(p.Distance) { + return errors.Err("this bucket range does not cover this peer") + } - peerIndex := find(c.ID, b.peers) + peerIndex := find(p.Contact.ID, b.peers) if peerIndex >= 0 { - fmt.Println("exists, moving to back") b.lastUpdate = time.Now() b.peers[peerIndex].Touch() moveToBack(b.peers, peerIndex) } else if insertIfNew { - fmt.Println("inserting new") hasRoom := true if len(b.peers) >= bucketSize { - fmt.Println("no room") hasRoom = false for i := range b.peers { if b.peers[i].IsBad(maxPeerFails) { - fmt.Println("dropping bad peer to make room") // TODO: Ping contact first. Only remove if it does not respond b.peers = append(b.peers[:i], b.peers[i+1:]...) hasRoom = true @@ -134,15 +131,13 @@ func (b *bucket) UpdateContact(c Contact, insertIfNew bool) { } if hasRoom { - fmt.Println("actually adding") b.lastUpdate = time.Now() - peer := peer{Contact: c} - peer.Touch() - b.peers = append(b.peers, peer) - } else { - fmt.Println("no room, dropping") + p.Touch() + b.peers = append(b.peers, p) } } + + return nil } // FailContact marks a contact as having failed, and removes it if it failed too many times @@ -183,19 +178,21 @@ func (b *bucket) Split() (*bucket, *bucket) { right.lastUpdate = b.lastUpdate for _, p := range b.peers { - if left.Range.Contains(p.Contact.ID) { + if left.Range.Contains(p.Distance) { left.peers = append(left.peers, p) } else { right.peers = append(right.peers, p) } } - if len(left.peers) == 0 { - left, right = right.Split() - left.Range.Start = b.Range.Start - } else if len(right.peers) == 0 { - left, right = left.Split() - right.Range.End = b.Range.End + if len(b.peers) > 1 { + if len(left.peers) == 0 { + left, right = right.Split() + left.Range.Start = b.Range.Start + } else if len(right.peers) == 0 { + left, right = left.Split() + right.Range.End = b.Range.End + } } return left, right @@ -248,22 +245,33 @@ func (rt *routingTable) Update(c Contact) { rt.mu.Lock() // write lock, because updates may cause bucket splits defer rt.mu.Unlock() - if rt.shouldSplit(c) { - spew.Dump("splitting") - i := rt.bucketNumFor(c.ID) - left, right := rt.buckets[i].Split() - rt.buckets = append(rt.buckets[:i], append([]*bucket{left, right}, rt.buckets[i+1:]...)...) - } else { - spew.Dump("no split") + b := rt.bucketFor(c.ID) + + if rt.shouldSplit(b, c) { + left, right := b.Split() + + for i := range rt.buckets { + if rt.buckets[i].Range.Start.Equals(left.Range.Start) { + rt.buckets = append(rt.buckets[:i], append([]*bucket{left, right}, rt.buckets[i+1:]...)...) + break + } + } + + if left.Range.Contains(c.ID) { + b = left + } else { + b = right + } } - rt.buckets[rt.bucketNumFor(c.ID)].UpdateContact(c, true) + + b.UpdatePeer(peer{Contact: c, Distance: rt.id.Xor(c.ID)}, true) } // Fresh refreshes a contact if its already in the routing table func (rt *routingTable) Fresh(c Contact) { rt.mu.RLock() defer rt.mu.RUnlock() - rt.bucketFor(c.ID).UpdateContact(c, false) + rt.bucketFor(c.ID).UpdatePeer(peer{Contact: c, Distance: rt.id.Xor(c.ID)}, false) } // FailContact marks a contact as having failed, and removes it if it failed too many times @@ -319,38 +327,21 @@ func (rt *routingTable) Len() int { return len(rt.buckets) } -// BucketRanges returns a slice of ranges, where the `start` of each range is the smallest id that can -// go in that bucket, and the `end` is the largest id -func (rt *routingTable) BucketRanges() []bits.Range { - rt.mu.RLock() - defer rt.mu.RUnlock() - ranges := make([]bits.Range, len(rt.buckets)) - for i, b := range rt.buckets { - ranges[i] = b.Range - } - return ranges -} - -func (rt *routingTable) bucketNumFor(target bits.Bitmap) int { +func (rt *routingTable) bucketFor(target bits.Bitmap) *bucket { if rt.id.Equals(target) { panic("routing table does not have a bucket for its own id") } distance := target.Xor(rt.id) - for i, b := range rt.buckets { + for _, b := range rt.buckets { if b.Range.Contains(distance) { - return i + return b } } - panic("target value overflows the key space") + panic("target is not contained in any buckets") } -func (rt *routingTable) bucketFor(target bits.Bitmap) *bucket { - return rt.buckets[rt.bucketNumFor(target)] -} - -func (rt *routingTable) shouldSplit(c Contact) bool { - b := rt.bucketFor(c.ID) - if b.Contains(c) { +func (rt *routingTable) shouldSplit(b *bucket, c Contact) bool { + if b.Has(c) { return false } if b.Len() >= bucketSize { diff --git a/dht/routing_table_test.go b/dht/routing_table_test.go index 1b373b5..89064fd 100644 --- a/dht/routing_table_test.go +++ b/dht/routing_table_test.go @@ -2,7 +2,6 @@ package dht import ( "encoding/json" - "fmt" "math/big" "net" "strconv" @@ -13,37 +12,7 @@ import ( "github.com/sebdah/goldie" ) -func checkBucketCount(t *testing.T, rt *routingTable, correctSize, correctCount, testCaseIndex int) { - if len(rt.buckets) != correctSize { - t.Errorf("failed test case %d. there should be %d buckets, got %d", testCaseIndex+1, correctSize, len(rt.buckets)) - } - if rt.Count() != correctCount { - t.Errorf("failed test case %d. there should be %d contacts, got %d", testCaseIndex+1, correctCount, rt.Count()) - } - -} - -func checkRangeContinuity(t *testing.T, rt *routingTable) { - position := big.NewInt(0) - for i, bucket := range rt.buckets { - bucketStart := bucket.Range.Start.Big() - if bucketStart.Cmp(position) != 0 { - t.Errorf("invalid start of bucket range: %s vs %s", position.String(), bucketStart.String()) - } - if bucketStart.Cmp(bucket.Range.End.Big()) != -1 { - t.Error("range start is not less than bucket end") - } - position = bucket.Range.End.Big() - if i != len(rt.buckets)-1 { - position.Add(position, big.NewInt(1)) - } - } - if position.Cmp(bits.MaxP().Big()) != 0 { - t.Errorf("range does not cover the whole keyspace, %s vs %s", bits.FromBigP(position).String(), bits.MaxP().String()) - } -} - -func TestSplitBuckets(t *testing.T) { +func TestBucket_Split(t *testing.T) { rt := newRoutingTable(bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) if len(rt.buckets) != 1 { t.Errorf("there should only be one bucket so far") @@ -53,53 +22,51 @@ func TestSplitBuckets(t *testing.T) { } var tests = []struct { + name string id bits.Bitmap expectedBucketCount int expectedTotalContacts int }{ //fill first bucket - {bits.FromHexP("F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 1}, - {bits.FromHexP("FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 2}, - {bits.FromHexP("FFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 3}, - {bits.FromHexP("FFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 4}, - {bits.FromHexP("FFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 5}, - {bits.FromHexP("FFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 6}, - {bits.FromHexP("FFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 7}, - {bits.FromHexP("FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1, 8}, + {"b1-one", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100"), 1, 1}, + {"b1-two", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200"), 1, 2}, + {"b1-three", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300"), 1, 3}, + {"b1-four", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400"), 1, 4}, + {"b1-five", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500"), 1, 5}, + {"b1-six", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600"), 1, 6}, + {"b1-seven", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700"), 1, 7}, + {"b1-eight", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800"), 1, 8}, - // fill second bucket - {bits.FromHexP("FFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 9}, - {bits.FromHexP("FFFFFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 10}, - {bits.FromHexP("FFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 11}, - {bits.FromHexP("FFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 12}, - {bits.FromHexP("FFFFFFFFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 13}, - {bits.FromHexP("FFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 14}, - {bits.FromHexP("FFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 15}, - {bits.FromHexP("FFFFFFFFFFFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 16}, + // split off second bucket and fill it + {"b2-one", bits.FromHexP("001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 9}, + {"b2-two", bits.FromHexP("002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 10}, + {"b2-three", bits.FromHexP("003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 11}, + {"b2-four", bits.FromHexP("004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 12}, + {"b2-five", bits.FromHexP("005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 13}, + {"b2-six", bits.FromHexP("006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 14}, + {"b2-seven", bits.FromHexP("007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 15}, - // this should be skipped (no split should occur) - {bits.FromHexP("FFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 16}, + // at this point there are two buckets. the first has 7 contacts, the second has 8 - {bits.FromHexP("100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 3, 17}, - {bits.FromHexP("200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 3, 18}, - {bits.FromHexP("300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 3, 19}, + // inserts into the second bucket should be skipped + {"dont-split", bits.FromHexP("009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 15}, - {bits.FromHexP("400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 20}, - {bits.FromHexP("500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 21}, - {bits.FromHexP("600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 22}, - {bits.FromHexP("700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 23}, - {bits.FromHexP("800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 24}, - {bits.FromHexP("900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 25}, - {bits.FromHexP("A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 26}, - {bits.FromHexP("B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 27}, + // ... unless the ID is closer than the kth-closest contact + {"split-kth-closest", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 2, 16}, + + {"b3-two", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"), 3, 17}, + {"b3-three", bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"), 3, 18}, } for i, testCase := range tests { - fmt.Printf("\n\n\ncase %d\n", i) rt.Update(Contact{testCase.id, net.ParseIP("127.0.0.1"), 8000 + i}) - //spew.Dump(rt.buckets) - checkBucketCount(t, rt, testCase.expectedBucketCount, testCase.expectedTotalContacts, i) - checkRangeContinuity(t, rt) + + if len(rt.buckets) != testCase.expectedBucketCount { + t.Errorf("failed test case %s. there should be %d buckets, got %d", testCase.name, testCase.expectedBucketCount, len(rt.buckets)) + } + if rt.Count() != testCase.expectedTotalContacts { + t.Errorf("failed test case %s. there should be %d contacts, got %d", testCase.name, testCase.expectedTotalContacts, rt.Count()) + } } var testRanges = []struct { @@ -108,20 +75,115 @@ func TestSplitBuckets(t *testing.T) { }{ {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 0}, {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"), 0}, - {bits.FromHexP("200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010"), 1}, - {bits.FromHexP("380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2}, - {bits.FromHexP("F00000000000000000000000000000000000000000000000000F00000000000000000000000000000000000000000000"), 3}, - {bits.FromHexP("F0000000000000000000000000000000F0000000000000000000000000F0000000000000000000000000000000000000"), 3}, + {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410"), 1}, + {bits.FromHexP("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f0"), 1}, + {bits.FromHexP("F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800"), 2}, + {bits.FromHexP("F00000000000000000000000000000000000000000000000000F00000000000000000000000000000000000000000000"), 2}, + {bits.FromHexP("F0000000000000000000000000000000F0000000000000000000000000F0000000000000000000000000000000000000"), 2}, } for _, tt := range testRanges { - bucket := rt.bucketNumFor(tt.id) + bucket := bucketNumFor(rt, tt.id) if bucket != tt.expected { - t.Errorf("bucketFor(%s, %s) => %d, want %d", tt.id.Hex(), rt.id.Hex(), bucket, tt.expected) + t.Errorf("bucketFor(%s, %s) => got %d, expected %d", tt.id.Hex(), rt.id.Hex(), bucket, tt.expected) } } +} - rt.printBucketInfo() +func bucketNumFor(rt *routingTable, target bits.Bitmap) int { + if rt.id.Equals(target) { + panic("routing table does not have a bucket for its own id") + } + distance := target.Xor(rt.id) + for i := range rt.buckets { + if rt.buckets[i].Range.Contains(distance) { + return i + } + } + panic("target is not contained in any buckets") +} + +func TestBucket_Split_Continuous(t *testing.T) { + b := newBucket(bits.MaxRange()) + + left, right := b.Split() + + if !left.Range.Start.Equals(b.Range.Start) { + t.Errorf("left bucket start does not align with original bucket start. got %s, expected %s", left.Range.Start, b.Range.Start) + } + + if !right.Range.End.Equals(b.Range.End) { + t.Errorf("right bucket end does not align with original bucket end. got %s, expected %s", right.Range.End, b.Range.End) + } + + leftEndNext := (&big.Int{}).Add(left.Range.End.Big(), big.NewInt(1)) + if !bits.FromBigP(leftEndNext).Equals(right.Range.Start) { + t.Errorf("there's a gap between left bucket end and right bucket start. end is %s, start is %s", left.Range.End, right.Range.Start) + } +} + +func TestBucket_Split_KthClosest_DoSplit(t *testing.T) { + rt := newRoutingTable(bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) + + // add 4 low IDs + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), net.ParseIP("127.0.0.1"), 8001}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"), net.ParseIP("127.0.0.1"), 8002}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"), net.ParseIP("127.0.0.1"), 8003}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004"), net.ParseIP("127.0.0.1"), 8004}) + + // add 4 high IDs + rt.Update(Contact{bits.FromHexP("800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8001}) + rt.Update(Contact{bits.FromHexP("900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8002}) + rt.Update(Contact{bits.FromHexP("a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8003}) + rt.Update(Contact{bits.FromHexP("b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8004}) + + // split the bucket and fill the high bucket + rt.Update(Contact{bits.FromHexP("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8005}) + rt.Update(Contact{bits.FromHexP("d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8006}) + rt.Update(Contact{bits.FromHexP("e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8007}) + rt.Update(Contact{bits.FromHexP("f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8008}) + + // add a high ID. it should split because the high ID is closer than the Kth closest ID + rt.Update(Contact{bits.FromHexP("910000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.1"), 8009}) + + if len(rt.buckets) != 3 { + t.Errorf("expected 3 buckets, got %d", len(rt.buckets)) + } + if rt.Count() != 13 { + t.Errorf("expected 13 contacts, got %d", rt.Count()) + } +} + +func TestBucket_Split_KthClosest_DontSplit(t *testing.T) { + rt := newRoutingTable(bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) + + // add 4 low IDs + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), net.ParseIP("127.0.0.1"), 8001}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"), net.ParseIP("127.0.0.1"), 8002}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"), net.ParseIP("127.0.0.1"), 8003}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004"), net.ParseIP("127.0.0.1"), 8004}) + + // add 4 high IDs + rt.Update(Contact{bits.FromHexP("800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8001}) + rt.Update(Contact{bits.FromHexP("900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8002}) + rt.Update(Contact{bits.FromHexP("a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8003}) + rt.Update(Contact{bits.FromHexP("b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8004}) + + // split the bucket and fill the high bucket + rt.Update(Contact{bits.FromHexP("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8005}) + rt.Update(Contact{bits.FromHexP("d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8006}) + rt.Update(Contact{bits.FromHexP("e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8007}) + rt.Update(Contact{bits.FromHexP("f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8008}) + + // add a really high ID. this should not split because its not closer than the Kth closest ID + rt.Update(Contact{bits.FromHexP("ffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.1"), 8009}) + + if len(rt.buckets) != 2 { + t.Errorf("expected 2 buckets, got %d", len(rt.buckets)) + } + if rt.Count() != 12 { + t.Errorf("expected 12 contacts, got %d", rt.Count()) + } } func TestRoutingTable_GetClosest(t *testing.T) { @@ -213,14 +275,12 @@ func TestRoutingTable_Save(t *testing.T) { id := bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41") rt := newRoutingTable(id) - ranges := rt.BucketRanges() - - for i, r := range ranges { + for i, b := range rt.buckets { for j := 0; j < bucketSize; j++ { - toAdd := r.Start.Add(bits.FromShortHexP(strconv.Itoa(j))) - if toAdd.Cmp(r.End) <= 0 { + toAdd := b.Range.Start.Add(bits.FromShortHexP(strconv.Itoa(j))) + if toAdd.Cmp(b.Range.End) <= 0 { rt.Update(Contact{ - ID: r.Start.Add(bits.FromShortHexP(strconv.Itoa(j))), + ID: b.Range.Start.Add(bits.FromShortHexP(strconv.Itoa(j))), IP: net.ParseIP("1.2.3." + strconv.Itoa(j)), Port: 1 + i*bucketSize + j, })