From 2d0b3547d70d9991ba0a89d9e84c83614403ef59 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Tue, 26 Jun 2018 13:42:36 -0400 Subject: [PATCH 01/13] add BucketRange to bucket struct -initialize the routing table with one bucket covering the entire keyspace --- dht/routing_table.go | 144 +++++++++++++++++++++++++++----------- dht/routing_table_test.go | 56 +++++++++++---- 2 files changed, 146 insertions(+), 54 deletions(-) diff --git a/dht/routing_table.go b/dht/routing_table.go index d3d2818..ec851be 100644 --- a/dht/routing_table.go +++ b/dht/routing_table.go @@ -13,6 +13,7 @@ import ( "github.com/lbryio/lbry.go/errors" "github.com/lbryio/lbry.go/stop" "github.com/lbryio/reflector.go/dht/bits" + "math/big" ) // TODO: if routing table is ever empty (aka the node is isolated), it should re-bootstrap @@ -52,9 +53,10 @@ func (p *peer) Fail() { } type bucket struct { - lock *sync.RWMutex - peers []peer - lastUpdate time.Time + lock *sync.RWMutex + peers []peer + lastUpdate time.Time + bucketRange *bits.Range } // Len returns the number of peers in the bucket @@ -80,6 +82,8 @@ func (b *bucket) UpdateContact(c Contact, insertIfNew bool) { b.lock.Lock() defer b.lock.Unlock() + // TODO: verify the peer is in the bucket key range + peerIndex := find(c.ID, b.peers) if peerIndex >= 0 { b.lastUpdate = time.Now() @@ -140,7 +144,7 @@ func (b *bucket) NeedsRefresh(refreshInterval time.Duration) bool { type routingTable struct { id bits.Bitmap - buckets [nodeIDBits]bucket + buckets []bucket } func newRoutingTable(id bits.Bitmap) *routingTable { @@ -151,12 +155,19 @@ func newRoutingTable(id bits.Bitmap) *routingTable { } func (rt *routingTable) reset() { - for i := range rt.buckets { - rt.buckets[i] = bucket{ - peers: make([]peer, 0, bucketSize), - lock: &sync.RWMutex{}, - } - } + start := big.NewInt(0) + end := big.NewInt(1) + end.Lsh(end, bits.NumBits) + end.Sub(end, big.NewInt(1)) + rt.buckets = []bucket{} + rt.buckets = append(rt.buckets, bucket{ + peers: make([]peer, 0, bucketSize), + lock: &sync.RWMutex{}, + bucketRange: &bits.Range{ + Start: bits.FromBigP(start), + End: bits.FromBigP(end), + }, + }) } func (rt *routingTable) BucketInfo() string { @@ -179,7 +190,7 @@ func (rt *routingTable) BucketInfo() string { // Update inserts or refreshes a contact func (rt *routingTable) Update(c Contact) { - rt.bucketFor(c.ID).UpdateContact(c, true) + rt.insertContact(c) } // Fresh refreshes a contact if its already in the routing table @@ -195,36 +206,18 @@ func (rt *routingTable) Fail(c Contact) { // GetClosest returns the closest `limit` contacts from the routing table // It marks each bucket it accesses as having been accessed func (rt *routingTable) GetClosest(target bits.Bitmap, limit int) []Contact { - var toSort []sortedContact - var bucketNum int - - if rt.id.Equals(target) { - bucketNum = 0 - } else { - bucketNum = rt.bucketNumFor(target) + toSort := []sortedContact{} + for _, b := range rt.buckets { + toSort = appendContacts(toSort, b, target) } - - toSort = appendContacts(toSort, rt.buckets[bucketNum], target) - - for i := 1; (bucketNum-i >= 0 || bucketNum+i < nodeIDBits) && len(toSort) < limit; i++ { - if bucketNum-i >= 0 { - toSort = appendContacts(toSort, rt.buckets[bucketNum-i], target) - } - if bucketNum+i < nodeIDBits { - toSort = appendContacts(toSort, rt.buckets[bucketNum+i], target) - } - } - sort.Sort(byXorDistance(toSort)) - - var contacts []Contact + contacts := []Contact{} for _, sorted := range toSort { contacts = append(contacts, sorted.contact) if len(contacts) >= limit { break } } - return contacts } @@ -248,11 +241,8 @@ func (rt *routingTable) Count() int { // go in that bucket, and the `end` is the largest id func (rt *routingTable) BucketRanges() []bits.Range { ranges := make([]bits.Range, len(rt.buckets)) - for i := range rt.buckets { - ranges[i] = bits.Range{ - Start: rt.id.Suffix(i, false).Set(nodeIDBits-1-i, !rt.id.Get(nodeIDBits-1-i)), - End: rt.id.Suffix(i, true).Set(nodeIDBits-1-i, !rt.id.Get(nodeIDBits-1-i)), - } + for i, b := range rt.buckets { + ranges[i] = *b.bucketRange } return ranges } @@ -261,13 +251,89 @@ func (rt *routingTable) bucketNumFor(target bits.Bitmap) int { if rt.id.Equals(target) { panic("routing table does not have a bucket for its own id") } - return nodeIDBits - 1 - target.Xor(rt.id).PrefixLen() + distance := target.Xor(rt.id) + for i, b := range rt.buckets { + if b.bucketRange.Start.Cmp(distance) <= 0 && b.bucketRange.End.Cmp(distance) >= 0 { + return i + } + } + panic("target value overflows the key space") } func (rt *routingTable) bucketFor(target bits.Bitmap) *bucket { return &rt.buckets[rt.bucketNumFor(target)] } +func (rt *routingTable) shouldSplit(target bits.Bitmap) bool { + bucketIndex := rt.bucketNumFor(target) + if len(rt.buckets[bucketIndex].peers) >= bucketSize { + if bucketIndex == 0 { // this is the bucket covering our node id + return true + } + kClosest := rt.GetClosest(rt.id, bucketSize) + kthClosest := kClosest[len(kClosest) - 1] + if target.Xor(rt.id).Cmp(kthClosest.ID.Xor(rt.id)) < 0 { + return true // the kth closest contact is further than this one + } + } + return false +} + +func (rt *routingTable) insertContact(c Contact) { + if len(rt.buckets[rt.bucketNumFor(c.ID)].peers) < bucketSize { + rt.buckets[rt.bucketNumFor(c.ID)].UpdateContact(c, true) + } else if rt.shouldSplit(c.ID) { + rt.recursiveInsertContact(c) + } +} + +func (rt *routingTable) recursiveInsertContact(c Contact) { + bucketIndex := rt.bucketNumFor(c.ID) + b := rt.buckets[bucketIndex] + min := b.bucketRange.Start.Big() + max := b.bucketRange.End.Big() + + midpoint := max.Sub(max, min) + midpoint.Div(midpoint, big.NewInt(2)) + + // re-size the bucket to be split + b.bucketRange.Start = bits.FromBigP(min) + b.bucketRange.End = bits.FromBigP(midpoint.Sub(midpoint, big.NewInt(1))) + + movedPeers := []peer{} + resizedPeers := []peer{} + + // set the re-sized bucket to only have peers still in range + for _, p := range b.peers { + if rt.bucketNumFor(p.Contact.ID) != bucketIndex { + movedPeers = append(movedPeers, p) + } else { + resizedPeers = append(resizedPeers, p) + } + } + b.peers = resizedPeers + + // add the new bucket + insert := bucket{ + peers: make([]peer, 0, bucketSize), + lock: &sync.RWMutex{}, + bucketRange: &bits.Range{ + Start: bits.FromBigP(midpoint), + End: bits.FromBigP(max), + }, + } + rt.buckets = append(rt.buckets[:bucketIndex], append([]bucket{insert}, rt.buckets[bucketIndex:]...)...) + + // re-insert the contacts that where out of range of the split bucket + for _, p := range movedPeers { + rt.insertContact(p.Contact) + } + + // insert the new contact + rt.insertContact(c) +} + + func (rt *routingTable) GetIDsForRefresh(refreshInterval time.Duration) []bits.Bitmap { var bitmaps []bits.Bitmap for i, bucket := range rt.buckets { diff --git a/dht/routing_table_test.go b/dht/routing_table_test.go index 8850f98..2418d06 100644 --- a/dht/routing_table_test.go +++ b/dht/routing_table_test.go @@ -36,6 +36,31 @@ func TestRoutingTable_bucketFor(t *testing.T) { } } +func TestRoutingTableFillBuckets(t *testing.T) { + n1 := bits.FromHexP("FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n2 := bits.FromHexP("FFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n3 := bits.FromHexP("111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n4 := bits.FromHexP("111111120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n5 := bits.FromHexP("111111130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n6 := bits.FromHexP("111111140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n7 := bits.FromHexP("111111150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n8 := bits.FromHexP("111111160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + n9 := bits.FromHexP("111111070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + + rt := newRoutingTable(n1) + rt.Update(Contact{n2, net.ParseIP("127.0.0.1"), 8001}) + rt.Update(Contact{n3, net.ParseIP("127.0.0.1"), 8002}) + rt.Update(Contact{n4, net.ParseIP("127.0.0.1"), 8003}) + rt.Update(Contact{n5, net.ParseIP("127.0.0.1"), 8004}) + rt.Update(Contact{n6, net.ParseIP("127.0.0.1"), 8005}) + rt.Update(Contact{n7, net.ParseIP("127.0.0.1"), 8006}) + rt.Update(Contact{n7, net.ParseIP("127.0.0.1"), 8007}) + rt.Update(Contact{n8, net.ParseIP("127.0.0.1"), 8008}) + rt.Update(Contact{n9, net.ParseIP("127.0.0.1"), 8009}) + + log.Printf(rt.BucketInfo()) +} + func TestRoutingTable_GetClosest(t *testing.T) { n1 := bits.FromHexP("FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") n2 := bits.FromHexP("FFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") @@ -121,28 +146,29 @@ func TestRoutingTable_MoveToBack(t *testing.T) { } } -func TestRoutingTable_BucketRanges(t *testing.T) { +func TestRoutingTable_InitialBucketRange(t *testing.T) { id := bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41") ranges := newRoutingTable(id).BucketRanges() - if !ranges[0].Start.Equals(ranges[0].End) { - t.Error("first bucket should only fit exactly one id") + bucketRange := ranges[0] + if len(ranges) != 1 { + t.Error("there should only be one bucket") } + if !ranges[0].Start.Equals(bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) { + t.Error("bucket does not cover the lower keyspace") + } + if !ranges[0].End.Equals(bits.FromHexP("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) { + t.Error("bucket does not cover the upper keyspace") + } + found := 0 for i := 0; i < 1000; i++ { randID := bits.Rand() - found := -1 - for i, r := range ranges { - if r.Start.Cmp(randID) <= 0 && r.End.Cmp(randID) >= 0 { - if found >= 0 { - t.Errorf("%s appears in buckets %d and %d", randID.Hex(), found, i) - } else { - found = i - } - } - } - if found < 0 { - t.Errorf("%s did not appear in any bucket", randID.Hex()) + if bucketRange.Start.Cmp(randID) <= 0 && bucketRange.End.Cmp(randID) >= 0 { + found += 1 } } + if found != 1000 { + t.Errorf("%d did not appear in any bucket", found) + } } func TestRoutingTable_Save(t *testing.T) { From 8619bc6e276d478052f956f3aea76bf952cb7271 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Wed, 27 Jun 2018 18:09:10 -0400 Subject: [PATCH 02/13] expand empty buckets --- dht/routing_table.go | 141 ++++++++++++++++++++++++++++---------- dht/routing_table_test.go | 112 +++++++++++++++++++++++------- 2 files changed, 192 insertions(+), 61 deletions(-) diff --git a/dht/routing_table.go b/dht/routing_table.go index ec851be..9a109ea 100644 --- a/dht/routing_table.go +++ b/dht/routing_table.go @@ -3,17 +3,16 @@ package dht import ( "encoding/json" "fmt" + "math/big" "net" "sort" "strconv" "strings" "sync" "time" - "github.com/lbryio/lbry.go/errors" "github.com/lbryio/lbry.go/stop" "github.com/lbryio/reflector.go/dht/bits" - "math/big" ) // TODO: if routing table is ever empty (aka the node is isolated), it should re-bootstrap @@ -25,7 +24,12 @@ import ( type peer struct { Contact Contact LastActivity time.Time + // LastReplied time.Time + // LastRequested time.Time + // LastFailure time.Time + // SecondLastFailure time.Time NumFailures int + //, // // @@ -56,7 +60,7 @@ type bucket struct { lock *sync.RWMutex peers []peer lastUpdate time.Time - bucketRange *bits.Range + bucketRange bits.Range } // Len returns the number of peers in the bucket @@ -163,7 +167,7 @@ func (rt *routingTable) reset() { rt.buckets = append(rt.buckets, bucket{ peers: make([]peer, 0, bucketSize), lock: &sync.RWMutex{}, - bucketRange: &bits.Range{ + bucketRange: bits.Range{ Start: bits.FromBigP(start), End: bits.FromBigP(end), }, @@ -179,7 +183,7 @@ func (rt *routingTable) BucketInfo() string { for j, c := range contacts { s[j] = c.ID.HexShort() } - bucketInfo = append(bucketInfo, fmt.Sprintf("Bucket %d: (%d) %s", i, len(contacts), strings.Join(s, ", "))) + bucketInfo = append(bucketInfo, fmt.Sprintf("bucket %d: (%d) %s", i, len(contacts), strings.Join(s, ", "))) } } if len(bucketInfo) == 0 { @@ -242,7 +246,7 @@ func (rt *routingTable) Count() int { func (rt *routingTable) BucketRanges() []bits.Range { ranges := make([]bits.Range, len(rt.buckets)) for i, b := range rt.buckets { - ranges[i] = *b.bucketRange + ranges[i] = b.bucketRange } return ranges } @@ -280,59 +284,122 @@ func (rt *routingTable) shouldSplit(target bits.Bitmap) bool { } func (rt *routingTable) insertContact(c Contact) { - if len(rt.buckets[rt.bucketNumFor(c.ID)].peers) < bucketSize { + bucketIndex := rt.bucketNumFor(c.ID) + peersInBucket := int(len(rt.buckets[bucketIndex].peers)) + if peersInBucket < bucketSize { rt.buckets[rt.bucketNumFor(c.ID)].UpdateContact(c, true) - } else if rt.shouldSplit(c.ID) { - rt.recursiveInsertContact(c) + } else if peersInBucket >= bucketSize && rt.shouldSplit(c.ID) { + rt.splitBucket(bucketIndex) + rt.insertContact(c) } + rt.popEmptyBuckets() } -func (rt *routingTable) recursiveInsertContact(c Contact) { - bucketIndex := rt.bucketNumFor(c.ID) +func (rt *routingTable) splitBucket(bucketIndex int) { + b := rt.buckets[bucketIndex] + min := b.bucketRange.Start.Big() max := b.bucketRange.End.Big() - - midpoint := max.Sub(max, min) + midpoint := &big.Int{} + midpoint.Sub(max, min) midpoint.Div(midpoint, big.NewInt(2)) + midpoint.Add(midpoint, min) + midpointPlusOne := &big.Int{} + midpointPlusOne.Add(midpointPlusOne, min) + midpointPlusOne.Add(midpoint, big.NewInt(1)) - // re-size the bucket to be split - b.bucketRange.Start = bits.FromBigP(min) - b.bucketRange.End = bits.FromBigP(midpoint.Sub(midpoint, big.NewInt(1))) + first_half := rt.buckets[:bucketIndex+1] - movedPeers := []peer{} - resizedPeers := []peer{} - - // set the re-sized bucket to only have peers still in range - for _, p := range b.peers { - if rt.bucketNumFor(p.Contact.ID) != bucketIndex { - movedPeers = append(movedPeers, p) - } else { - resizedPeers = append(resizedPeers, p) - } + second_half := []bucket{} + for i := bucketIndex + 1; i < len(rt.buckets); i++ { + second_half = append(second_half, rt.buckets[i]) } - b.peers = resizedPeers - // add the new bucket - insert := bucket{ + copiedPeers := []peer{} + copy(copiedPeers, b.peers) + b.peers = []peer{} + + rt.buckets = []bucket{} + for _, i := range first_half { + rt.buckets = append(rt.buckets, i) + } + newBucket := bucket{ peers: make([]peer, 0, bucketSize), lock: &sync.RWMutex{}, - bucketRange: &bits.Range{ - Start: bits.FromBigP(midpoint), + bucketRange: bits.Range{ + Start: bits.FromBigP(midpointPlusOne), End: bits.FromBigP(max), }, } - rt.buckets = append(rt.buckets[:bucketIndex], append([]bucket{insert}, rt.buckets[bucketIndex:]...)...) + rt.buckets = append(rt.buckets, newBucket) + for _, i := range second_half { + rt.buckets = append(rt.buckets, i) + } + // re-size the bucket to be split + rt.buckets[bucketIndex].bucketRange.Start = bits.FromBigP(min) + rt.buckets[bucketIndex].bucketRange.End = bits.FromBigP(midpoint) - // re-insert the contacts that where out of range of the split bucket - for _, p := range movedPeers { + // re-insert the contacts that were in the re-sized bucket + for _, p := range copiedPeers { rt.insertContact(p.Contact) } - - // insert the new contact - rt.insertContact(c) } +func (rt *routingTable) printBucketInfo() { + for i, b := range rt.buckets { + fmt.Printf("bucket %d, %d contacts\n", i + 1, len(b.peers)) + fmt.Printf(" start : %s\n", b.bucketRange.Start.String()) + fmt.Printf(" stop : %s\n", b.bucketRange.End.String()) + fmt.Println("") + } +} + +func (rt *routingTable) popBucket(bucketIndex int) { + canGoLower := bucketIndex >= 1 + canGoHigher := len(rt.buckets) - 1 > bucketIndex + + if canGoLower && !canGoHigher { + // raise the end of bucket[bucketIndex-1] + rt.buckets[bucketIndex-1].bucketRange.End = bits.FromBigP(rt.buckets[bucketIndex].bucketRange.End.Big()) + } else if !canGoLower && canGoHigher { + // lower the start of bucket[bucketIndex+1] + rt.buckets[bucketIndex+1].bucketRange.Start = bits.FromBigP(rt.buckets[bucketIndex].bucketRange.Start.Big()) + } else if canGoLower && canGoHigher { + // raise the end of bucket[bucketIndex-1] and lower the start of bucket[bucketIndex+1] to the + // midpoint of the range covered by bucket[bucketIndex] + midpoint := &big.Int{} + midpoint.Sub(rt.buckets[bucketIndex].bucketRange.End.Big(), rt.buckets[bucketIndex].bucketRange.Start.Big()) + midpoint.Div(midpoint, big.NewInt(2)) + midpointPlusOne := &big.Int{} + midpointPlusOne.Add(midpoint, big.NewInt(1)) + rt.buckets[bucketIndex-1].bucketRange.End = bits.FromBigP(midpoint) + rt.buckets[bucketIndex+1].bucketRange.Start = bits.FromBigP(midpointPlusOne) + } else { + return + } + // pop the bucket + rt.buckets = rt.buckets[:bucketIndex+copy(rt.buckets[bucketIndex:], rt.buckets[bucketIndex+1:])] +} + +func (rt *routingTable) popNextEmptyBucket() bool { + for bucketIndex := 0; bucketIndex < len(rt.buckets); bucketIndex += 1 { + if len(rt.buckets[bucketIndex].peers) == 0 { + rt.popBucket(bucketIndex) + return true + } + } + return false +} + +func (rt *routingTable) popEmptyBuckets() { + if len(rt.buckets) > 1 { + popBuckets := rt.popNextEmptyBucket() + for popBuckets == true { + popBuckets = rt.popNextEmptyBucket() + } + } +} func (rt *routingTable) GetIDsForRefresh(refreshInterval time.Duration) []bits.Bitmap { var bitmaps []bits.Bitmap diff --git a/dht/routing_table_test.go b/dht/routing_table_test.go index 2418d06..69cc8f4 100644 --- a/dht/routing_table_test.go +++ b/dht/routing_table_test.go @@ -2,11 +2,11 @@ package dht import ( "encoding/json" + "math/big" "net" "strconv" "strings" "testing" - "github.com/lbryio/reflector.go/dht/bits" "github.com/sebdah/goldie" ) @@ -36,29 +36,92 @@ func TestRoutingTable_bucketFor(t *testing.T) { } } -func TestRoutingTableFillBuckets(t *testing.T) { - n1 := bits.FromHexP("FFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n2 := bits.FromHexP("FFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n3 := bits.FromHexP("111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n4 := bits.FromHexP("111111120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n5 := bits.FromHexP("111111130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n6 := bits.FromHexP("111111140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n7 := bits.FromHexP("111111150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n8 := bits.FromHexP("111111160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") - n9 := bits.FromHexP("111111070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") +func checkBucketCount(rt *routingTable, t *testing.T, 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()) + } - rt := newRoutingTable(n1) - rt.Update(Contact{n2, net.ParseIP("127.0.0.1"), 8001}) - rt.Update(Contact{n3, net.ParseIP("127.0.0.1"), 8002}) - rt.Update(Contact{n4, net.ParseIP("127.0.0.1"), 8003}) - rt.Update(Contact{n5, net.ParseIP("127.0.0.1"), 8004}) - rt.Update(Contact{n6, net.ParseIP("127.0.0.1"), 8005}) - rt.Update(Contact{n7, net.ParseIP("127.0.0.1"), 8006}) - rt.Update(Contact{n7, net.ParseIP("127.0.0.1"), 8007}) - rt.Update(Contact{n8, net.ParseIP("127.0.0.1"), 8008}) - rt.Update(Contact{n9, net.ParseIP("127.0.0.1"), 8009}) +} - log.Printf(rt.BucketInfo()) +func checkRangeContinuity(rt *routingTable, t *testing.T) { + position := big.NewInt(0) + for i, bucket := range rt.buckets { + bucketStart := bucket.bucketRange.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.bucketRange.End.Big()) != -1 { + t.Error("range start is not less than bucket end") + } + position = bucket.bucketRange.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) { + rt := newRoutingTable(bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) + if len(rt.buckets) != 1 { + t.Errorf("there should only be one bucket so far") + } + if len(rt.buckets[0].peers) != 0 { + t.Errorf("there should be no contacts yet") + } + + var tests = []struct { + 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}, + + // 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}, + + // this should be skipped (no split should occur) + {bits.FromHexP("FFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2, 16}, + + {bits.FromHexP("100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 3, 17}, + {bits.FromHexP("200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 3, 18}, + {bits.FromHexP("300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 3, 19}, + + {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}, + } + for i, testCase := range tests { + rt.Update(Contact{testCase.id, net.ParseIP("127.0.0.1"), 8000 + i}) + checkBucketCount(rt, t, testCase.expectedBucketCount, testCase.expectedTotalContacts, i) + checkRangeContinuity(rt, t) + } + rt.printBucketInfo() } func TestRoutingTable_GetClosest(t *testing.T) { @@ -77,7 +140,6 @@ func TestRoutingTable_GetClosest(t *testing.T) { if !contacts[0].ID.Equals(n3) { t.Error(contacts[0]) } - contacts = rt.GetClosest(n2, 10) if len(contacts) != 2 { t.Error(len(contacts)) @@ -148,7 +210,8 @@ func TestRoutingTable_MoveToBack(t *testing.T) { func TestRoutingTable_InitialBucketRange(t *testing.T) { id := bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41") - ranges := newRoutingTable(id).BucketRanges() + rt := newRoutingTable(id) + ranges := rt.BucketRanges() bucketRange := ranges[0] if len(ranges) != 1 { t.Error("there should only be one bucket") @@ -169,6 +232,7 @@ func TestRoutingTable_InitialBucketRange(t *testing.T) { if found != 1000 { t.Errorf("%d did not appear in any bucket", found) } + log.Println(rt.Count()) } func TestRoutingTable_Save(t *testing.T) { From 40a08cc96da934184fa68581461223a7c16b25e4 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Fri, 29 Jun 2018 15:47:00 -0400 Subject: [PATCH 03/13] more --- dht/bits/bitmap.go | 5 ++ dht/routing_table.go | 101 ++++++++++++++++++++++++++++++-------- dht/routing_table_test.go | 70 ++++++++------------------ 3 files changed, 106 insertions(+), 70 deletions(-) diff --git a/dht/bits/bitmap.go b/dht/bits/bitmap.go index 0605fb6..f0d8a26 100644 --- a/dht/bits/bitmap.go +++ b/dht/bits/bitmap.go @@ -344,6 +344,11 @@ func MaxP() Bitmap { return FromHexP(strings.Repeat("f", NumBytes*2)) } +// Min returns a bitmap with all bits set to 0 +func MinP() Bitmap { + return FromHexP(strings.Repeat("0", NumBytes*2)) +} + // Rand generates a cryptographically random bitmap with the confines of the parameters specified. func Rand() Bitmap { var id Bitmap diff --git a/dht/routing_table.go b/dht/routing_table.go index 9a109ea..dd0f69c 100644 --- a/dht/routing_table.go +++ b/dht/routing_table.go @@ -149,27 +149,29 @@ func (b *bucket) NeedsRefresh(refreshInterval time.Duration) bool { type routingTable struct { id bits.Bitmap buckets []bucket + lock *sync.RWMutex } func newRoutingTable(id bits.Bitmap) *routingTable { var rt routingTable rt.id = id + rt.lock = &sync.RWMutex{} rt.reset() return &rt } func (rt *routingTable) reset() { - start := big.NewInt(0) - end := big.NewInt(1) - end.Lsh(end, bits.NumBits) - end.Sub(end, big.NewInt(1)) + rt.Lock() + defer rt.Unlock() + newBucketLock := &sync.RWMutex{} + newBucketLock.Lock() rt.buckets = []bucket{} rt.buckets = append(rt.buckets, bucket{ peers: make([]peer, 0, bucketSize), - lock: &sync.RWMutex{}, + lock: newBucketLock, bucketRange: bits.Range{ - Start: bits.FromBigP(start), - End: bits.FromBigP(end), + Start: bits.MinP(), + End: bits.MaxP(), }, }) } @@ -207,9 +209,33 @@ func (rt *routingTable) Fail(c Contact) { rt.bucketFor(c.ID).FailContact(c.ID) } +func (rt *routingTable) getClosestToUs(limit int) []Contact { + contacts := []Contact{} + toSort := []sortedContact{} + rt.lock.RLock() + defer rt.lock.RUnlock() + for _, bucket := range rt.buckets { + toSort = []sortedContact{} + toSort = appendContacts(toSort, bucket, rt.id) + sort.Sort(byXorDistance(toSort)) + for _, sorted := range toSort { + contacts = append(contacts, sorted.contact) + if len(contacts) >= limit { + break + } + } + } + return contacts +} + // GetClosest returns the closest `limit` contacts from the routing table // It marks each bucket it accesses as having been accessed func (rt *routingTable) GetClosest(target bits.Bitmap, limit int) []Contact { + if target == rt.id { + return rt.getClosestToUs(limit) + } + rt.lock.RLock() + defer rt.lock.RUnlock() toSort := []sortedContact{} for _, b := range rt.buckets { toSort = appendContacts(toSort, b, target) @@ -235,15 +261,26 @@ func appendContacts(contacts []sortedContact, b bucket, target bits.Bitmap) []so // Count returns the number of contacts in the routing table func (rt *routingTable) Count() int { count := 0 + rt.lock.RLock() + defer rt.lock.RUnlock() for _, bucket := range rt.buckets { count += bucket.Len() } return count } +// Len returns the number of buckets in the routing table +func (rt *routingTable) Len() int { + rt.lock.RLock() + defer rt.lock.RUnlock() + 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.lock.RLock() + defer rt.lock.RUnlock() ranges := make([]bits.Range, len(rt.buckets)) for i, b := range rt.buckets { ranges[i] = b.bucketRange @@ -252,6 +289,8 @@ func (rt *routingTable) BucketRanges() []bits.Range { } func (rt *routingTable) bucketNumFor(target bits.Bitmap) int { + rt.lock.RLock() + defer rt.lock.RUnlock() if rt.id.Equals(target) { panic("routing table does not have a bucket for its own id") } @@ -265,13 +304,16 @@ func (rt *routingTable) bucketNumFor(target bits.Bitmap) int { } func (rt *routingTable) bucketFor(target bits.Bitmap) *bucket { - return &rt.buckets[rt.bucketNumFor(target)] + bucketIndex := rt.bucketNumFor(target) + rt.lock.RLock() + defer rt.lock.RUnlock() + return &rt.buckets[bucketIndex] } func (rt *routingTable) shouldSplit(target bits.Bitmap) bool { - bucketIndex := rt.bucketNumFor(target) - if len(rt.buckets[bucketIndex].peers) >= bucketSize { - if bucketIndex == 0 { // this is the bucket covering our node id + b := rt.bucketFor(target) + if b.Len() >= bucketSize { + if b.bucketRange.Start.Equals(bits.MinP()) { // this is the bucket covering our node id return true } kClosest := rt.GetClosest(rt.id, bucketSize) @@ -285,20 +327,35 @@ func (rt *routingTable) shouldSplit(target bits.Bitmap) bool { func (rt *routingTable) insertContact(c Contact) { bucketIndex := rt.bucketNumFor(c.ID) - peersInBucket := int(len(rt.buckets[bucketIndex].peers)) + peersInBucket :=rt.buckets[bucketIndex].Len() if peersInBucket < bucketSize { rt.buckets[rt.bucketNumFor(c.ID)].UpdateContact(c, true) } else if peersInBucket >= bucketSize && rt.shouldSplit(c.ID) { rt.splitBucket(bucketIndex) rt.insertContact(c) + rt.popEmptyBuckets() + } +} + +func (rt * routingTable) Lock() { + rt.lock.Lock() + for _, buk := range rt.buckets { + buk.lock.Lock() + } +} + +func (rt * routingTable) Unlock() { + rt.lock.Unlock() + for _, buk := range rt.buckets { + buk.lock.Unlock() } - rt.popEmptyBuckets() } func (rt *routingTable) splitBucket(bucketIndex int) { + rt.Lock() + defer rt.Unlock() b := rt.buckets[bucketIndex] - min := b.bucketRange.Start.Big() max := b.bucketRange.End.Big() midpoint := &big.Int{} @@ -310,7 +367,6 @@ func (rt *routingTable) splitBucket(bucketIndex int) { midpointPlusOne.Add(midpoint, big.NewInt(1)) first_half := rt.buckets[:bucketIndex+1] - second_half := []bucket{} for i := bucketIndex + 1; i < len(rt.buckets); i++ { second_half = append(second_half, rt.buckets[i]) @@ -321,20 +377,22 @@ func (rt *routingTable) splitBucket(bucketIndex int) { b.peers = []peer{} rt.buckets = []bucket{} - for _, i := range first_half { - rt.buckets = append(rt.buckets, i) + for _, buk := range first_half { + rt.buckets = append(rt.buckets, buk) } + newBucketLock := &sync.RWMutex{} + newBucketLock.Lock() // will be unlocked by the deferred rt.Unlock() newBucket := bucket{ peers: make([]peer, 0, bucketSize), - lock: &sync.RWMutex{}, + lock: newBucketLock, bucketRange: bits.Range{ Start: bits.FromBigP(midpointPlusOne), End: bits.FromBigP(max), }, } rt.buckets = append(rt.buckets, newBucket) - for _, i := range second_half { - rt.buckets = append(rt.buckets, i) + for _, buk := range second_half { + rt.buckets = append(rt.buckets, buk) } // re-size the bucket to be split rt.buckets[bucketIndex].bucketRange.Start = bits.FromBigP(min) @@ -393,6 +451,9 @@ func (rt *routingTable) popNextEmptyBucket() bool { } func (rt *routingTable) popEmptyBuckets() { + rt.Lock() + defer rt.Unlock() + if len(rt.buckets) > 1 { popBuckets := rt.popNextEmptyBucket() for popBuckets == true { diff --git a/dht/routing_table_test.go b/dht/routing_table_test.go index 69cc8f4..5ab3493 100644 --- a/dht/routing_table_test.go +++ b/dht/routing_table_test.go @@ -11,30 +11,6 @@ import ( "github.com/sebdah/goldie" ) -func TestRoutingTable_bucketFor(t *testing.T) { - rt := newRoutingTable(bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) - var tests = []struct { - id bits.Bitmap - expected int - }{ - {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 0}, - {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"), 1}, - {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"), 1}, - {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004"), 2}, - {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"), 2}, - {bits.FromHexP("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f"), 3}, - {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010"), 4}, - {bits.FromHexP("F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 383}, - {bits.FromHexP("F0000000000000000000000000000000F0000000000000000000000000F0000000000000000000000000000000000000"), 383}, - } - - for _, tt := range tests { - bucket := rt.bucketNumFor(tt.id) - if bucket != tt.expected { - t.Errorf("bucketFor(%s, %s) => %d, want %d", tt.id.Hex(), rt.id.Hex(), bucket, tt.expected) - } - } -} func checkBucketCount(rt *routingTable, t *testing.T, correctSize, correctCount, testCaseIndex int) { if len(rt.buckets) != correctSize { @@ -121,6 +97,26 @@ func TestSplitBuckets(t *testing.T) { checkBucketCount(rt, t, testCase.expectedBucketCount, testCase.expectedTotalContacts, i) checkRangeContinuity(rt, t) } + + var testRanges = []struct { + id bits.Bitmap + expected int + }{ + {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 0}, + {bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"), 0}, + {bits.FromHexP("200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010"), 1}, + {bits.FromHexP("380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 2}, + {bits.FromHexP("F00000000000000000000000000000000000000000000000000F00000000000000000000000000000000000000000000"), 3}, + {bits.FromHexP("F0000000000000000000000000000000F0000000000000000000000000F0000000000000000000000000000000000000"), 3}, + } + + for _, tt := range testRanges { + bucket := rt.bucketNumFor(tt.id) + if bucket != tt.expected { + t.Errorf("bucketFor(%s, %s) => %d, want %d", tt.id.Hex(), rt.id.Hex(), bucket, tt.expected) + } + } + rt.printBucketInfo() } @@ -208,32 +204,6 @@ func TestRoutingTable_MoveToBack(t *testing.T) { } } -func TestRoutingTable_InitialBucketRange(t *testing.T) { - id := bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41") - rt := newRoutingTable(id) - ranges := rt.BucketRanges() - bucketRange := ranges[0] - if len(ranges) != 1 { - t.Error("there should only be one bucket") - } - if !ranges[0].Start.Equals(bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) { - t.Error("bucket does not cover the lower keyspace") - } - if !ranges[0].End.Equals(bits.FromHexP("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) { - t.Error("bucket does not cover the upper keyspace") - } - found := 0 - for i := 0; i < 1000; i++ { - randID := bits.Rand() - if bucketRange.Start.Cmp(randID) <= 0 && bucketRange.End.Cmp(randID) >= 0 { - found += 1 - } - } - if found != 1000 { - t.Errorf("%d did not appear in any bucket", found) - } - log.Println(rt.Count()) -} func TestRoutingTable_Save(t *testing.T) { id := bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41") From 4c000ed4198f19f77866abb28ceb19e2d28f6853 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 10 Jul 2018 17:30:47 -0400 Subject: [PATCH 04/13] grin's cleanup and some WIP --- cmd/dht.go | 5 +- dht/bits/bitmap.go | 5 - dht/bits/range.go | 4 + dht/routing_table.go | 339 ++++++++++++++------------------------ dht/routing_table_test.go | 31 ++-- 5 files changed, 151 insertions(+), 233 deletions(-) diff --git a/cmd/dht.go b/cmd/dht.go index 62be601..d3199bd 100644 --- a/cmd/dht.go +++ b/cmd/dht.go @@ -19,7 +19,7 @@ var dhtPort int func init() { var cmd = &cobra.Command{ - Use: "dht [start|bootstrap]", + Use: "dht [bootstrap|connect]", Short: "Run dht node", ValidArgs: []string{"start", "bootstrap"}, Args: argFuncs(cobra.ExactArgs(1), cobra.OnlyValidArgs), @@ -47,5 +47,8 @@ func dhtCmd(cmd *cobra.Command, args []string) { node.Shutdown() } else { log.Fatal("not implemented") + + // + } } diff --git a/dht/bits/bitmap.go b/dht/bits/bitmap.go index f0d8a26..0605fb6 100644 --- a/dht/bits/bitmap.go +++ b/dht/bits/bitmap.go @@ -344,11 +344,6 @@ func MaxP() Bitmap { return FromHexP(strings.Repeat("f", NumBytes*2)) } -// Min returns a bitmap with all bits set to 0 -func MinP() Bitmap { - return FromHexP(strings.Repeat("0", NumBytes*2)) -} - // Rand generates a cryptographically random bitmap with the confines of the parameters specified. func Rand() Bitmap { var id Bitmap diff --git a/dht/bits/range.go b/dht/bits/range.go index 349e148..083e36c 100644 --- a/dht/bits/range.go +++ b/dht/bits/range.go @@ -61,3 +61,7 @@ func (r Range) intervalStart(n, num int) *big.Int { func (r Range) IntervalSize() *big.Int { return (&big.Int{}).Sub(r.End.Big(), r.Start.Big()) } + +func (r Range) Contains(b Bitmap) bool { + return r.Start.Cmp(b) <= 0 && r.End.Cmp(b) >= 0 +} diff --git a/dht/routing_table.go b/dht/routing_table.go index dd0f69c..3f5d26f 100644 --- a/dht/routing_table.go +++ b/dht/routing_table.go @@ -3,13 +3,14 @@ package dht import ( "encoding/json" "fmt" - "math/big" "net" "sort" "strconv" "strings" "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" @@ -28,7 +29,7 @@ type peer struct { // LastRequested time.Time // LastFailure time.Time // SecondLastFailure time.Time - NumFailures int + NumFailures int //, // @@ -57,10 +58,18 @@ func (p *peer) Fail() { } type bucket struct { - lock *sync.RWMutex - peers []peer - lastUpdate time.Time - bucketRange bits.Range + lock *sync.RWMutex + peers []peer + lastUpdate time.Time + Range bits.Range // capitalized because `range` is a keyword +} + +func newBucket(r bits.Range) *bucket { + return &bucket{ + peers: make([]peer, 0, bucketSize), + lock: &sync.RWMutex{}, + Range: r, + } } // Len returns the number of peers in the bucket @@ -70,6 +79,17 @@ func (b bucket) Len() int { return len(b.peers) } +func (b bucket) Contains(c Contact) bool { + b.lock.RLock() + defer b.lock.RUnlock() + for _, p := range b.peers { + if p.Contact.Equals(c, true) { + return true + } + } + return false +} + // Contacts returns a slice of the bucket's contacts func (b bucket) Contacts() []Contact { b.lock.RLock() @@ -85,22 +105,26 @@ func (b bucket) Contacts() []Contact { func (b *bucket) UpdateContact(c Contact, insertIfNew bool) { 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 peerIndex := find(c.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 @@ -110,10 +134,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") } } } @@ -146,37 +173,59 @@ func (b *bucket) NeedsRefresh(refreshInterval time.Duration) bool { return time.Since(b.lastUpdate) > refreshInterval } +func (b *bucket) Split() (*bucket, *bucket) { + b.lock.Lock() + defer b.lock.Unlock() + + left := newBucket(b.Range.IntervalP(1, 2)) + right := newBucket(b.Range.IntervalP(2, 2)) + left.lastUpdate = b.lastUpdate + right.lastUpdate = b.lastUpdate + + for _, p := range b.peers { + if left.Range.Contains(p.Contact.ID) { + 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 + } + + return left, right +} + type routingTable struct { id bits.Bitmap - buckets []bucket - lock *sync.RWMutex + buckets []*bucket + mu *sync.RWMutex // this mutex is write-locked only when CHANGING THE NUMBER OF BUCKETS in the table } func newRoutingTable(id bits.Bitmap) *routingTable { - var rt routingTable - rt.id = id - rt.lock = &sync.RWMutex{} + rt := routingTable{ + id: id, + mu: &sync.RWMutex{}, + } rt.reset() return &rt } func (rt *routingTable) reset() { - rt.Lock() - defer rt.Unlock() - newBucketLock := &sync.RWMutex{} - newBucketLock.Lock() - rt.buckets = []bucket{} - rt.buckets = append(rt.buckets, bucket{ - peers: make([]peer, 0, bucketSize), - lock: newBucketLock, - bucketRange: bits.Range{ - Start: bits.MinP(), - End: bits.MaxP(), - }, - }) + rt.mu.Lock() + defer rt.mu.Unlock() + rt.buckets = []*bucket{newBucket(bits.MaxRange())} } func (rt *routingTable) BucketInfo() string { + rt.mu.RLock() + defer rt.mu.RUnlock() + var bucketInfo []string for i, b := range rt.buckets { if b.Len() > 0 { @@ -196,52 +245,53 @@ func (rt *routingTable) BucketInfo() string { // Update inserts or refreshes a contact func (rt *routingTable) Update(c Contact) { - rt.insertContact(c) + 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") + } + rt.buckets[rt.bucketNumFor(c.ID)].UpdateContact(c, 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) } // FailContact marks a contact as having failed, and removes it if it failed too many times func (rt *routingTable) Fail(c Contact) { + rt.mu.RLock() + defer rt.mu.RUnlock() rt.bucketFor(c.ID).FailContact(c.ID) } -func (rt *routingTable) getClosestToUs(limit int) []Contact { - contacts := []Contact{} - toSort := []sortedContact{} - rt.lock.RLock() - defer rt.lock.RUnlock() - for _, bucket := range rt.buckets { - toSort = []sortedContact{} - toSort = appendContacts(toSort, bucket, rt.id) - sort.Sort(byXorDistance(toSort)) - for _, sorted := range toSort { - contacts = append(contacts, sorted.contact) - if len(contacts) >= limit { - break - } - } - } - return contacts +// GetClosest returns the closest `limit` contacts from the routing table. +// This is a locking wrapper around getClosest() +func (rt *routingTable) GetClosest(target bits.Bitmap, limit int) []Contact { + rt.mu.RLock() + defer rt.mu.RUnlock() + return rt.getClosest(target, limit) } -// GetClosest returns the closest `limit` contacts from the routing table -// It marks each bucket it accesses as having been accessed -func (rt *routingTable) GetClosest(target bits.Bitmap, limit int) []Contact { - if target == rt.id { - return rt.getClosestToUs(limit) - } - rt.lock.RLock() - defer rt.lock.RUnlock() - toSort := []sortedContact{} +// getClosest returns the closest `limit` contacts from the routing table +func (rt *routingTable) getClosest(target bits.Bitmap, limit int) []Contact { + var toSort []sortedContact for _, b := range rt.buckets { - toSort = appendContacts(toSort, b, target) + for _, c := range b.Contacts() { + toSort = append(toSort, sortedContact{c, c.ID.Xor(target)}) + } } sort.Sort(byXorDistance(toSort)) - contacts := []Contact{} + + var contacts []Contact for _, sorted := range toSort { contacts = append(contacts, sorted.contact) if len(contacts) >= limit { @@ -251,18 +301,11 @@ func (rt *routingTable) GetClosest(target bits.Bitmap, limit int) []Contact { return contacts } -func appendContacts(contacts []sortedContact, b bucket, target bits.Bitmap) []sortedContact { - for _, contact := range b.Contacts() { - contacts = append(contacts, sortedContact{contact, contact.ID.Xor(target)}) - } - return contacts -} - // Count returns the number of contacts in the routing table func (rt *routingTable) Count() int { + rt.mu.RLock() + defer rt.mu.RUnlock() count := 0 - rt.lock.RLock() - defer rt.lock.RUnlock() for _, bucket := range rt.buckets { count += bucket.Len() } @@ -271,32 +314,30 @@ func (rt *routingTable) Count() int { // Len returns the number of buckets in the routing table func (rt *routingTable) Len() int { - rt.lock.RLock() - defer rt.lock.RUnlock() + rt.mu.RLock() + defer rt.mu.RUnlock() 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.lock.RLock() - defer rt.lock.RUnlock() + rt.mu.RLock() + defer rt.mu.RUnlock() ranges := make([]bits.Range, len(rt.buckets)) for i, b := range rt.buckets { - ranges[i] = b.bucketRange + ranges[i] = b.Range } return ranges } func (rt *routingTable) bucketNumFor(target bits.Bitmap) int { - rt.lock.RLock() - defer rt.lock.RUnlock() 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 { - if b.bucketRange.Start.Cmp(distance) <= 0 && b.bucketRange.End.Cmp(distance) >= 0 { + if b.Range.Contains(distance) { return i } } @@ -304,164 +345,36 @@ func (rt *routingTable) bucketNumFor(target bits.Bitmap) int { } func (rt *routingTable) bucketFor(target bits.Bitmap) *bucket { - bucketIndex := rt.bucketNumFor(target) - rt.lock.RLock() - defer rt.lock.RUnlock() - return &rt.buckets[bucketIndex] + return rt.buckets[rt.bucketNumFor(target)] } -func (rt *routingTable) shouldSplit(target bits.Bitmap) bool { - b := rt.bucketFor(target) +func (rt *routingTable) shouldSplit(c Contact) bool { + b := rt.bucketFor(c.ID) + if b.Contains(c) { + return false + } if b.Len() >= bucketSize { - if b.bucketRange.Start.Equals(bits.MinP()) { // this is the bucket covering our node id + if b.Range.Start.Equals(bits.Bitmap{}) { // this is the bucket covering our node id return true } - kClosest := rt.GetClosest(rt.id, bucketSize) - kthClosest := kClosest[len(kClosest) - 1] - if target.Xor(rt.id).Cmp(kthClosest.ID.Xor(rt.id)) < 0 { - return true // the kth closest contact is further than this one + kClosest := rt.getClosest(rt.id, bucketSize) + kthClosest := kClosest[len(kClosest)-1] + if rt.id.Closer(c.ID, kthClosest.ID) { + return true } } return false } -func (rt *routingTable) insertContact(c Contact) { - bucketIndex := rt.bucketNumFor(c.ID) - peersInBucket :=rt.buckets[bucketIndex].Len() - if peersInBucket < bucketSize { - rt.buckets[rt.bucketNumFor(c.ID)].UpdateContact(c, true) - } else if peersInBucket >= bucketSize && rt.shouldSplit(c.ID) { - rt.splitBucket(bucketIndex) - rt.insertContact(c) - rt.popEmptyBuckets() - } -} - -func (rt * routingTable) Lock() { - rt.lock.Lock() - for _, buk := range rt.buckets { - buk.lock.Lock() - } -} - -func (rt * routingTable) Unlock() { - rt.lock.Unlock() - for _, buk := range rt.buckets { - buk.lock.Unlock() - } -} - -func (rt *routingTable) splitBucket(bucketIndex int) { - rt.Lock() - defer rt.Unlock() - - b := rt.buckets[bucketIndex] - min := b.bucketRange.Start.Big() - max := b.bucketRange.End.Big() - midpoint := &big.Int{} - midpoint.Sub(max, min) - midpoint.Div(midpoint, big.NewInt(2)) - midpoint.Add(midpoint, min) - midpointPlusOne := &big.Int{} - midpointPlusOne.Add(midpointPlusOne, min) - midpointPlusOne.Add(midpoint, big.NewInt(1)) - - first_half := rt.buckets[:bucketIndex+1] - second_half := []bucket{} - for i := bucketIndex + 1; i < len(rt.buckets); i++ { - second_half = append(second_half, rt.buckets[i]) - } - - copiedPeers := []peer{} - copy(copiedPeers, b.peers) - b.peers = []peer{} - - rt.buckets = []bucket{} - for _, buk := range first_half { - rt.buckets = append(rt.buckets, buk) - } - newBucketLock := &sync.RWMutex{} - newBucketLock.Lock() // will be unlocked by the deferred rt.Unlock() - newBucket := bucket{ - peers: make([]peer, 0, bucketSize), - lock: newBucketLock, - bucketRange: bits.Range{ - Start: bits.FromBigP(midpointPlusOne), - End: bits.FromBigP(max), - }, - } - rt.buckets = append(rt.buckets, newBucket) - for _, buk := range second_half { - rt.buckets = append(rt.buckets, buk) - } - // re-size the bucket to be split - rt.buckets[bucketIndex].bucketRange.Start = bits.FromBigP(min) - rt.buckets[bucketIndex].bucketRange.End = bits.FromBigP(midpoint) - - // re-insert the contacts that were in the re-sized bucket - for _, p := range copiedPeers { - rt.insertContact(p.Contact) - } -} - func (rt *routingTable) printBucketInfo() { for i, b := range rt.buckets { - fmt.Printf("bucket %d, %d contacts\n", i + 1, len(b.peers)) - fmt.Printf(" start : %s\n", b.bucketRange.Start.String()) - fmt.Printf(" stop : %s\n", b.bucketRange.End.String()) + fmt.Printf("bucket %d, %d contacts\n", i+1, len(b.peers)) + fmt.Printf(" start : %s\n", b.Range.Start.String()) + fmt.Printf(" stop : %s\n", b.Range.End.String()) fmt.Println("") } } -func (rt *routingTable) popBucket(bucketIndex int) { - canGoLower := bucketIndex >= 1 - canGoHigher := len(rt.buckets) - 1 > bucketIndex - - if canGoLower && !canGoHigher { - // raise the end of bucket[bucketIndex-1] - rt.buckets[bucketIndex-1].bucketRange.End = bits.FromBigP(rt.buckets[bucketIndex].bucketRange.End.Big()) - } else if !canGoLower && canGoHigher { - // lower the start of bucket[bucketIndex+1] - rt.buckets[bucketIndex+1].bucketRange.Start = bits.FromBigP(rt.buckets[bucketIndex].bucketRange.Start.Big()) - } else if canGoLower && canGoHigher { - // raise the end of bucket[bucketIndex-1] and lower the start of bucket[bucketIndex+1] to the - // midpoint of the range covered by bucket[bucketIndex] - midpoint := &big.Int{} - midpoint.Sub(rt.buckets[bucketIndex].bucketRange.End.Big(), rt.buckets[bucketIndex].bucketRange.Start.Big()) - midpoint.Div(midpoint, big.NewInt(2)) - midpointPlusOne := &big.Int{} - midpointPlusOne.Add(midpoint, big.NewInt(1)) - rt.buckets[bucketIndex-1].bucketRange.End = bits.FromBigP(midpoint) - rt.buckets[bucketIndex+1].bucketRange.Start = bits.FromBigP(midpointPlusOne) - } else { - return - } - // pop the bucket - rt.buckets = rt.buckets[:bucketIndex+copy(rt.buckets[bucketIndex:], rt.buckets[bucketIndex+1:])] -} - -func (rt *routingTable) popNextEmptyBucket() bool { - for bucketIndex := 0; bucketIndex < len(rt.buckets); bucketIndex += 1 { - if len(rt.buckets[bucketIndex].peers) == 0 { - rt.popBucket(bucketIndex) - return true - } - } - return false -} - -func (rt *routingTable) popEmptyBuckets() { - rt.Lock() - defer rt.Unlock() - - if len(rt.buckets) > 1 { - popBuckets := rt.popNextEmptyBucket() - for popBuckets == true { - popBuckets = rt.popNextEmptyBucket() - } - } -} - func (rt *routingTable) GetIDsForRefresh(refreshInterval time.Duration) []bits.Bitmap { var bitmaps []bits.Bitmap for i, bucket := range rt.buckets { diff --git a/dht/routing_table_test.go b/dht/routing_table_test.go index 5ab3493..bb72adb 100644 --- a/dht/routing_table_test.go +++ b/dht/routing_table_test.go @@ -2,38 +2,39 @@ package dht import ( "encoding/json" + "fmt" "math/big" "net" "strconv" "strings" "testing" + "github.com/lbryio/reflector.go/dht/bits" "github.com/sebdah/goldie" ) - -func checkBucketCount(rt *routingTable, t *testing.T, correctSize, correctCount, testCaseIndex int) { +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)) + 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()) + t.Errorf("failed test case %d. there should be %d contacts, got %d", testCaseIndex+1, correctCount, rt.Count()) } } -func checkRangeContinuity(rt *routingTable, t *testing.T) { +func checkRangeContinuity(t *testing.T, rt *routingTable) { position := big.NewInt(0) for i, bucket := range rt.buckets { - bucketStart := bucket.bucketRange.Start.Big() + 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.bucketRange.End.Big()) != -1 { + if bucketStart.Cmp(bucket.Range.End.Big()) != -1 { t.Error("range start is not less than bucket end") } - position = bucket.bucketRange.End.Big() - if i != len(rt.buckets) - 1 { + position = bucket.Range.End.Big() + if i != len(rt.buckets)-1 { position.Add(position, big.NewInt(1)) } } @@ -52,8 +53,8 @@ func TestSplitBuckets(t *testing.T) { } var tests = []struct { - id bits.Bitmap - expectedBucketCount int + id bits.Bitmap + expectedBucketCount int expectedTotalContacts int }{ //fill first bucket @@ -92,10 +93,13 @@ func TestSplitBuckets(t *testing.T) { {bits.FromHexP("A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 26}, {bits.FromHexP("B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 4, 27}, } + 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}) - checkBucketCount(rt, t, testCase.expectedBucketCount, testCase.expectedTotalContacts, i) - checkRangeContinuity(rt, t) + //spew.Dump(rt.buckets) + checkBucketCount(t, rt, testCase.expectedBucketCount, testCase.expectedTotalContacts, i) + checkRangeContinuity(t, rt) } var testRanges = []struct { @@ -204,7 +208,6 @@ func TestRoutingTable_MoveToBack(t *testing.T) { } } - func TestRoutingTable_Save(t *testing.T) { id := bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41") rt := newRoutingTable(id) From bbe3bee3b00faa9000578c326238daa3f3266e33 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Tue, 10 Jul 2018 17:41:36 -0400 Subject: [PATCH 05/13] add dht start command, run a jsonrpc server to interact with the node --- cmd/dht.go | 49 +++++++-- dht/dht.go | 1 - dht/node.go | 10 +- dht/node_rpc.go | 213 ++++++++++++++++++++++++++++++++++++++ dht/routing_table.go | 1 + dht/routing_table_test.go | 2 + 6 files changed, 260 insertions(+), 16 deletions(-) create mode 100644 dht/node_rpc.go diff --git a/cmd/dht.go b/cmd/dht.go index d3199bd..6d2adbb 100644 --- a/cmd/dht.go +++ b/cmd/dht.go @@ -7,14 +7,28 @@ import ( "strconv" "syscall" "time" - "github.com/lbryio/reflector.go/dht" "github.com/lbryio/reflector.go/dht/bits" - - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "log" + "net/http" ) +type NodeRPC string + +type PingArgs struct { + nodeID string + address string + port int +} + +type PingResult string + +func (n *NodeRPC) Ping(r *http.Request, args *PingArgs, result *PingResult) error { + *result = PingResult("pong") + return nil +} + var dhtPort int func init() { @@ -25,6 +39,7 @@ func init() { Args: argFuncs(cobra.ExactArgs(1), cobra.OnlyValidArgs), Run: dhtCmd, } + cmd.PersistentFlags().StringP("nodeID", "n", "", "nodeID in hex") cmd.PersistentFlags().IntVar(&dhtPort, "port", 4567, "Port to start DHT on") rootCmd.AddCommand(cmd) } @@ -32,23 +47,37 @@ func init() { func dhtCmd(cmd *cobra.Command, args []string) { if args[0] == "bootstrap" { node := dht.NewBootstrapNode(bits.Rand(), 1*time.Millisecond, 1*time.Minute) - listener, err := net.ListenPacket(dht.Network, "127.0.0.1:"+strconv.Itoa(dhtPort)) checkErr(err) conn := listener.(*net.UDPConn) - err = node.Connect(conn) checkErr(err) - interruptChan := make(chan os.Signal, 1) signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM) <-interruptChan log.Printf("shutting down bootstrap node") node.Shutdown() } else { - log.Fatal("not implemented") - - // - + nodeIDStr := cmd.Flag("nodeID").Value.String() + nodeID := bits.Bitmap{} + if nodeIDStr == "" { + nodeID = bits.Rand() + } else { + nodeID = bits.FromHexP(nodeIDStr) + } + log.Println(nodeID.String()) + node := dht.NewBootstrapNode(nodeID, 1*time.Millisecond, 1*time.Minute) + listener, err := net.ListenPacket(dht.Network, "0.0.0.0:"+strconv.Itoa(dhtPort)) + checkErr(err) + conn := listener.(*net.UDPConn) + err = node.Connect(conn) + checkErr(err) + log.Println("started node") + rpcServer := dht.RunRPCServer(":1234", "/", node) + interruptChan := make(chan os.Signal, 1) + signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + <-interruptChan + rpcServer.Wg.Done() + node.Shutdown() } } diff --git a/dht/dht.go b/dht/dht.go index 3990d32..f8e328c 100644 --- a/dht/dht.go +++ b/dht/dht.go @@ -39,7 +39,6 @@ const ( alpha = 3 // this is the constant alpha in the spec bucketSize = 8 // this is the constant k in the spec nodeIDLength = bits.NumBytes // bytes. this is the constant B in the spec - nodeIDBits = bits.NumBits // number of bits in node ID messageIDLength = 20 // bytes. udpRetry = 1 diff --git a/dht/node.go b/dht/node.go index c191b35..0c8f95a 100644 --- a/dht/node.go +++ b/dht/node.go @@ -143,11 +143,11 @@ func (n *Node) Connect(conn UDPConn) error { }() // TODO: turn this back on when you're sure it works right - //n.stop.Add(1) - //go func() { - // defer n.stop.Done() - // n.startRoutingTableGrooming() - //}() + n.grp.Add(1) + go func() { + defer n.grp.Done() + n.startRoutingTableGrooming() + }() return nil } diff --git a/dht/node_rpc.go b/dht/node_rpc.go new file mode 100644 index 0000000..e221e81 --- /dev/null +++ b/dht/node_rpc.go @@ -0,0 +1,213 @@ +package dht + +import ( + "net" + "net/http" + "errors" + "sync" + "github.com/gorilla/mux" + "github.com/gorilla/rpc" + "github.com/gorilla/rpc/json" + "github.com/lbryio/reflector.go/dht/bits" +) + +type NodeRPCServer struct { + Wg sync.WaitGroup + Node *BootstrapNode +} + +var mut sync.Mutex +var rpcServer *NodeRPCServer + +type NodeRPC int + +type PingArgs struct { + NodeID string + IP string + Port int +} + +type PingResult string + + +func (n *NodeRPC) Ping(r *http.Request, args *PingArgs, result *PingResult) error { + if rpcServer == nil { + return errors.New("no node set up") + } + toQuery, err := bits.FromHex(args.NodeID) + if err != nil { + return err + } + c := Contact{ID: toQuery, IP: net.ParseIP(args.IP), Port: args.Port} + req := Request{Method: "ping"} + nodeResponse := rpcServer.Node.Send(c, req) + if nodeResponse != nil { + *result = PingResult(nodeResponse.Data) + } + return nil +} + +type FindArgs struct { + Key string + NodeID string + IP string + Port int +} + +type ContactResponse struct { + NodeID string + IP string + Port int +} + +type FindNodeResult []ContactResponse + +func (n *NodeRPC) FindNode(r *http.Request, args *FindArgs, result *FindNodeResult) error { + if rpcServer == nil { + return errors.New("no node set up") + } + key, err := bits.FromHex(args.Key) + if err != nil { + return err + } + toQuery, err := bits.FromHex(args.NodeID) + if err != nil { + return err + } + c := Contact{ID: toQuery, IP: net.ParseIP(args.IP), Port: args.Port} + req := Request{ Arg: &key, Method: "findNode"} + nodeResponse := rpcServer.Node.Send(c, req) + contacts := []ContactResponse{} + if nodeResponse != nil && nodeResponse.Contacts != nil { + for _, foundContact := range nodeResponse.Contacts { + contacts = append(contacts, ContactResponse{foundContact.ID.Hex(), foundContact.IP.String(), foundContact.Port}) + } + } + *result = FindNodeResult(contacts) + return nil +} + +type FindValueResult struct { + Contacts []ContactResponse + Value string +} + +func (n *NodeRPC) FindValue(r *http.Request, args *FindArgs, result *FindValueResult) error { + if rpcServer == nil { + return errors.New("no node set up") + } + key, err := bits.FromHex(args.Key) + if err != nil { + return err + } + toQuery, err := bits.FromHex(args.NodeID) + if err != nil { + return err + } + c := Contact{ID: toQuery, IP: net.ParseIP(args.IP), Port: args.Port} + req := Request{ Arg: &key, Method: "findValue"} + nodeResponse := rpcServer.Node.Send(c, req) + contacts := []ContactResponse{} + if nodeResponse != nil && nodeResponse.FindValueKey != "" { + *result = FindValueResult{Value: nodeResponse.FindValueKey} + return nil + } else if nodeResponse != nil && nodeResponse.Contacts != nil { + for _, foundContact := range nodeResponse.Contacts { + contacts = append(contacts, ContactResponse{foundContact.ID.Hex(), foundContact.IP.String(), foundContact.Port}) + } + *result = FindValueResult{Contacts: contacts} + return nil + } + return errors.New("not sure what happened") +} + +type BucketResponse struct { + Start string + End string + Count int + Contacts []ContactResponse +} + +type RoutingTableResponse struct { + Count int + Buckets []BucketResponse +} + +type GetRoutingTableArgs struct {} + +func (n *NodeRPC) GetRoutingTable(r *http.Request, args *GetRoutingTableArgs, result *RoutingTableResponse) error { + if rpcServer == nil { + return errors.New("no node set up") + } + result.Count = len(rpcServer.Node.rt.buckets) + for _, b := range rpcServer.Node.rt.buckets { + bucketInfo := []ContactResponse{} + for _, c := range b.Contacts() { + bucketInfo = append(bucketInfo, ContactResponse{c.ID.String(), c.IP.String(), c.Port}) + } + result.Buckets = append(result.Buckets, BucketResponse{ + Start: b.Range.Start.String(), End: b.Range.End.String(), Contacts: bucketInfo, + Count: b.Len(), + }) + } + return nil +} + +type GetNodeIDArgs struct {} + +type GetNodeIDResult string + +func (n *NodeRPC) GetNodeID(r *http.Request, args *GetNodeIDArgs, result *GetNodeIDResult) error { + if rpcServer == nil { + return errors.New("no node set up") + } + log.Println("get node id") + *result = GetNodeIDResult(rpcServer.Node.id.String()) + return nil +} + +type PrintBucketInfoArgs struct {} + +type PrintBucketInfoResult string + +func (n *NodeRPC) PrintBucketInfo(r *http.Request, args *PrintBucketInfoArgs, result *PrintBucketInfoResult) error { + if rpcServer == nil { + return errors.New("no node set up") + } + rpcServer.Node.rt.printBucketInfo() + *result = PrintBucketInfoResult("printed") + return nil +} + +func RunRPCServer(address, rpcPath string, node *BootstrapNode) NodeRPCServer { + mut.Lock() + defer mut.Unlock() + rpcServer = &NodeRPCServer{ + Wg: sync.WaitGroup{}, + Node: node, + } + c := make(chan *http.Server) + rpcServer.Wg.Add(1) + go func() { + s := rpc.NewServer() + s.RegisterCodec(json.NewCodec(), "application/json") + s.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8") + node := new(NodeRPC) + s.RegisterService(node, "") + r := mux.NewRouter() + r.Handle(rpcPath, s) + server := &http.Server{Addr: address, Handler: r} + log.Println("rpc listening on " + address) + server.ListenAndServe() + c <- server + }() + go func() { + rpcServer.Wg.Wait() + close(c) + log.Println("stopped rpc listening on " + address) + for server := range c { + server.Close() + } + }() + return *rpcServer +} diff --git a/dht/routing_table.go b/dht/routing_table.go index 3f5d26f..2df09a8 100644 --- a/dht/routing_table.go +++ b/dht/routing_table.go @@ -367,6 +367,7 @@ func (rt *routingTable) shouldSplit(c Contact) bool { } func (rt *routingTable) printBucketInfo() { + fmt.Printf("there are %d contacts in %d buckets\n", rt.Count(), rt.Len()) for i, b := range rt.buckets { fmt.Printf("bucket %d, %d contacts\n", i+1, len(b.peers)) fmt.Printf(" start : %s\n", b.Range.Start.String()) diff --git a/dht/routing_table_test.go b/dht/routing_table_test.go index bb72adb..1b373b5 100644 --- a/dht/routing_table_test.go +++ b/dht/routing_table_test.go @@ -209,6 +209,7 @@ func TestRoutingTable_MoveToBack(t *testing.T) { } func TestRoutingTable_Save(t *testing.T) { + t.Skip("fix me") id := bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41") rt := newRoutingTable(id) @@ -236,6 +237,7 @@ func TestRoutingTable_Save(t *testing.T) { } func TestRoutingTable_Load_ID(t *testing.T) { + t.Skip("fix me") id := "1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41" data := []byte(`{"id": "` + id + `","contacts": []}`) From 2e83654f1a3071973643f782630784d97390736e Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Thu, 12 Jul 2018 14:34:24 -0400 Subject: [PATCH 06/13] bucket splitting is solid --- Gopkg.lock | 23 +++- dht/bootstrap.go | 32 +++--- dht/routing_table.go | 105 +++++++++--------- dht/routing_table_test.go | 218 ++++++++++++++++++++++++-------------- 4 files changed, 225 insertions(+), 153 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index fc5ca7d..7012ba0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -73,6 +73,27 @@ packages = ["."] revision = "3287d94d4c6a48a63e16fffaabf27ab20203af2a" +[[projects]] + name = "github.com/gorilla/context" + packages = ["."] + revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" + version = "v1.1.1" + +[[projects]] + name = "github.com/gorilla/mux" + packages = ["."] + revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf" + version = "v1.6.2" + +[[projects]] + name = "github.com/gorilla/rpc" + packages = [ + ".", + "json" + ] + revision = "22c016f3df3febe0c1f6727598b6389507e03a18" + version = "v1.1.0" + [[projects]] name = "github.com/gorilla/websocket" packages = ["."] @@ -268,6 +289,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "4dc432f7df1c1d59d5ee47417ab4f0fe187d26eb9e1f53fecdb6396b3bd1e6e0" + inputs-digest = "6fac5a5bd6eb2f49d18558f8ed96b510e0852f95d7c746e301d53f5df92fffc4" solver-name = "gps-cdcl" solver-version = 1 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, }) From 76b0e156366163ad9caae988253f66680a4c5bec Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Fri, 13 Jul 2018 11:23:18 -0400 Subject: [PATCH 07/13] add tcp port mapping to data store --- dht/dht.go | 2 +- dht/node.go | 15 +++++++++------ dht/store.go | 16 ++++++++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/dht/dht.go b/dht/dht.go index f8e328c..ee757d0 100644 --- a/dht/dht.go +++ b/dht/dht.go @@ -318,7 +318,7 @@ func (dht *DHT) startReannouncer() { func (dht *DHT) storeOnNode(hash bits.Bitmap, c Contact) { // self-store if dht.contact.ID == c.ID { - dht.node.Store(hash, c) + dht.node.Store(hash, c, dht.conf.PeerProtocolPort) return } diff --git a/dht/node.go b/dht/node.go index 0c8f95a..c1577af 100644 --- a/dht/node.go +++ b/dht/node.go @@ -236,8 +236,11 @@ func (n *Node) handleRequest(addr *net.UDPAddr, request Request) { // TODO: we should be sending the IP in the request, not just using the sender's IP // TODO: should we be using StoreArgs.NodeID or StoreArgs.Value.LbryID ??? if n.tokens.Verify(request.StoreArgs.Value.Token, request.NodeID, addr) { - n.Store(request.StoreArgs.BlobHash, Contact{ID: request.StoreArgs.NodeID, IP: addr.IP, Port: request.StoreArgs.Value.Port}) - + n.Store( + request.StoreArgs.BlobHash, + Contact{ID: request.StoreArgs.NodeID, IP: addr.IP, Port: addr.Port}, + request.StoreArgs.Value.Port, + ) err := n.sendMessage(addr, Response{ID: request.ID, NodeID: n.id, Data: storeSuccessResponse}) if err != nil { log.Error("error sending 'storemethod' response message - ", err) @@ -276,9 +279,9 @@ func (n *Node) handleRequest(addr *net.UDPAddr, request Request) { if contacts := n.store.Get(*request.Arg); len(contacts) > 0 { res.FindValueKey = request.Arg.RawString() - res.Contacts = contacts + res.Contacts = contacts // we are returning stored contacts with tcp ports for file transfer } else { - res.Contacts = n.rt.GetClosest(*request.Arg, bucketSize) + res.Contacts = n.rt.GetClosest(*request.Arg, bucketSize) // these are normal dht contacts with udp ports } err := n.sendMessage(addr, res) @@ -464,6 +467,6 @@ func (n *Node) startRoutingTableGrooming() { } // Store stores a node contact in the node's contact store. -func (n *Node) Store(hash bits.Bitmap, c Contact) { - n.store.Upsert(hash, c) +func (n *Node) Store(hash bits.Bitmap, c Contact, tcpPort int) { + n.store.Upsert(hash, c, tcpPort) } diff --git a/dht/store.go b/dht/store.go index bc77e53..55f444b 100644 --- a/dht/store.go +++ b/dht/store.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/lbryio/reflector.go/dht/bits" + "net" ) // TODO: expire stored data after tExpire time @@ -11,6 +12,8 @@ import ( type contactStore struct { // map of blob hashes to (map of node IDs to bools) hashes map[bits.Bitmap]map[bits.Bitmap]bool + // map of blob hashes to (map of node ids to tcp ports) + ports map[bits.Bitmap]map[bits.Bitmap]int // stores the peers themselves, so they can be updated in one place contacts map[bits.Bitmap]Contact lock sync.RWMutex @@ -19,18 +22,23 @@ type contactStore struct { func newStore() *contactStore { return &contactStore{ hashes: make(map[bits.Bitmap]map[bits.Bitmap]bool), + ports: make(map[bits.Bitmap]map[bits.Bitmap]int), contacts: make(map[bits.Bitmap]Contact), } } -func (s *contactStore) Upsert(blobHash bits.Bitmap, contact Contact) { +func (s *contactStore) Upsert(blobHash bits.Bitmap, contact Contact, tcpPort int) { s.lock.Lock() defer s.lock.Unlock() if _, ok := s.hashes[blobHash]; !ok { s.hashes[blobHash] = make(map[bits.Bitmap]bool) } + if _, ok := s.ports[blobHash]; !ok { + s.ports[blobHash] = make(map[bits.Bitmap]int) + } s.hashes[blobHash][contact.ID] = true + s.ports[blobHash][contact.ID] = tcpPort s.contacts[contact.ID] = contact } @@ -45,7 +53,11 @@ func (s *contactStore) Get(blobHash bits.Bitmap) []Contact { if !ok { panic("node id in IDs list, but not in nodeInfo") } - contacts = append(contacts, contact) + peerPort, ok := s.ports[blobHash][id] + if !ok { + panic("node id in IDs list, but missing peer port") + } + contacts = append(contacts, Contact{ID: contact.ID, IP: contact.IP, Port: peerPort}) } } return contacts From 63fe5cbdc871114c6a2221e1edad020b393379e4 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Fri, 13 Jul 2018 11:24:11 -0400 Subject: [PATCH 08/13] add jack.lbry.tech as a known node for debugging --- cmd/dht.go | 7 +++++++ dht/bootstrap.go | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/cmd/dht.go b/cmd/dht.go index 6d2adbb..2ba46b7 100644 --- a/cmd/dht.go +++ b/cmd/dht.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/cobra" "log" "net/http" + "math/big" ) type NodeRPC string @@ -73,6 +74,12 @@ func dhtCmd(cmd *cobra.Command, args []string) { err = node.Connect(conn) checkErr(err) log.Println("started node") + node.AddKnownNode( + dht.Contact{ + bits.FromHexP("62c8ad9fb40a16062e884a63cd81f47b94604446319663d1334e1734dcefc8874b348ec683225e4852017a846e07d94e"), + net.ParseIP("34.231.152.182"), 4444, + }) + _, _, err = dht.FindContacts(&node.Node, nodeID.Sub(bits.FromBigP(big.NewInt(1))), false, nil) rpcServer := dht.RunRPCServer(":1234", "/", node) interruptChan := make(chan os.Signal, 1) signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) diff --git a/dht/bootstrap.go b/dht/bootstrap.go index b31f0aa..20c614d 100644 --- a/dht/bootstrap.go +++ b/dht/bootstrap.go @@ -48,6 +48,10 @@ func (b *BootstrapNode) Add(c Contact) { b.upsert(c) } +func (b *BootstrapNode) AddKnownNode(c Contact) { + b.Node.rt.Update(c) +} + // Connect connects to the given connection and starts any background threads necessary func (b *BootstrapNode) Connect(conn UDPConn) error { err := b.Node.Connect(conn) From c0c4d851f07788db221fc4374c20c8cbf700855e Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Fri, 13 Jul 2018 11:25:08 -0400 Subject: [PATCH 09/13] iterative find value rpc command -add NodeID to GetRoutingTable response -remove other debugging commands --- dht/node_rpc.go | 55 ++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/dht/node_rpc.go b/dht/node_rpc.go index e221e81..7763492 100644 --- a/dht/node_rpc.go +++ b/dht/node_rpc.go @@ -121,6 +121,33 @@ func (n *NodeRPC) FindValue(r *http.Request, args *FindArgs, result *FindValueRe return errors.New("not sure what happened") } +type IterativeFindValueArgs struct { + Key string +} + +type IterativeFindValueResult struct { + Contacts []ContactResponse + FoundValue bool +} + +func (n *NodeRPC) IterativeFindValue(r *http.Request, args *IterativeFindValueArgs, result *IterativeFindValueResult) error { + if rpcServer == nil { + return errors.New("no node set up") + } + key, err := bits.FromHex(args.Key) + if err != nil { + return err + } + foundContacts, found, err := FindContacts(&rpcServer.Node.Node, key, false, nil) + contacts := []ContactResponse{} + result.FoundValue = found + for _, foundContact := range foundContacts { + contacts = append(contacts, ContactResponse{foundContact.ID.Hex(), foundContact.IP.String(), foundContact.Port}) + } + result.Contacts = contacts + return nil +} + type BucketResponse struct { Start string End string @@ -129,6 +156,7 @@ type BucketResponse struct { } type RoutingTableResponse struct { + NodeID string Count int Buckets []BucketResponse } @@ -139,6 +167,7 @@ func (n *NodeRPC) GetRoutingTable(r *http.Request, args *GetRoutingTableArgs, re if rpcServer == nil { return errors.New("no node set up") } + result.NodeID = rpcServer.Node.id.String() result.Count = len(rpcServer.Node.rt.buckets) for _, b := range rpcServer.Node.rt.buckets { bucketInfo := []ContactResponse{} @@ -153,32 +182,6 @@ func (n *NodeRPC) GetRoutingTable(r *http.Request, args *GetRoutingTableArgs, re return nil } -type GetNodeIDArgs struct {} - -type GetNodeIDResult string - -func (n *NodeRPC) GetNodeID(r *http.Request, args *GetNodeIDArgs, result *GetNodeIDResult) error { - if rpcServer == nil { - return errors.New("no node set up") - } - log.Println("get node id") - *result = GetNodeIDResult(rpcServer.Node.id.String()) - return nil -} - -type PrintBucketInfoArgs struct {} - -type PrintBucketInfoResult string - -func (n *NodeRPC) PrintBucketInfo(r *http.Request, args *PrintBucketInfoArgs, result *PrintBucketInfoResult) error { - if rpcServer == nil { - return errors.New("no node set up") - } - rpcServer.Node.rt.printBucketInfo() - *result = PrintBucketInfoResult("printed") - return nil -} - func RunRPCServer(address, rpcPath string, node *BootstrapNode) NodeRPCServer { mut.Lock() defer mut.Unlock() From c8e363e81207c2cacd5899d49761cccdb3dc06ef Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Fri, 13 Jul 2018 12:49:41 -0400 Subject: [PATCH 10/13] Revert "add tcp port mapping to data store" This reverts commit 76b0e156366163ad9caae988253f66680a4c5bec. --- dht/dht.go | 2 +- dht/node.go | 15 ++++++--------- dht/store.go | 16 ++-------------- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/dht/dht.go b/dht/dht.go index ee757d0..f8e328c 100644 --- a/dht/dht.go +++ b/dht/dht.go @@ -318,7 +318,7 @@ func (dht *DHT) startReannouncer() { func (dht *DHT) storeOnNode(hash bits.Bitmap, c Contact) { // self-store if dht.contact.ID == c.ID { - dht.node.Store(hash, c, dht.conf.PeerProtocolPort) + dht.node.Store(hash, c) return } diff --git a/dht/node.go b/dht/node.go index c1577af..0c8f95a 100644 --- a/dht/node.go +++ b/dht/node.go @@ -236,11 +236,8 @@ func (n *Node) handleRequest(addr *net.UDPAddr, request Request) { // TODO: we should be sending the IP in the request, not just using the sender's IP // TODO: should we be using StoreArgs.NodeID or StoreArgs.Value.LbryID ??? if n.tokens.Verify(request.StoreArgs.Value.Token, request.NodeID, addr) { - n.Store( - request.StoreArgs.BlobHash, - Contact{ID: request.StoreArgs.NodeID, IP: addr.IP, Port: addr.Port}, - request.StoreArgs.Value.Port, - ) + n.Store(request.StoreArgs.BlobHash, Contact{ID: request.StoreArgs.NodeID, IP: addr.IP, Port: request.StoreArgs.Value.Port}) + err := n.sendMessage(addr, Response{ID: request.ID, NodeID: n.id, Data: storeSuccessResponse}) if err != nil { log.Error("error sending 'storemethod' response message - ", err) @@ -279,9 +276,9 @@ func (n *Node) handleRequest(addr *net.UDPAddr, request Request) { if contacts := n.store.Get(*request.Arg); len(contacts) > 0 { res.FindValueKey = request.Arg.RawString() - res.Contacts = contacts // we are returning stored contacts with tcp ports for file transfer + res.Contacts = contacts } else { - res.Contacts = n.rt.GetClosest(*request.Arg, bucketSize) // these are normal dht contacts with udp ports + res.Contacts = n.rt.GetClosest(*request.Arg, bucketSize) } err := n.sendMessage(addr, res) @@ -467,6 +464,6 @@ func (n *Node) startRoutingTableGrooming() { } // Store stores a node contact in the node's contact store. -func (n *Node) Store(hash bits.Bitmap, c Contact, tcpPort int) { - n.store.Upsert(hash, c, tcpPort) +func (n *Node) Store(hash bits.Bitmap, c Contact) { + n.store.Upsert(hash, c) } diff --git a/dht/store.go b/dht/store.go index 55f444b..bc77e53 100644 --- a/dht/store.go +++ b/dht/store.go @@ -4,7 +4,6 @@ import ( "sync" "github.com/lbryio/reflector.go/dht/bits" - "net" ) // TODO: expire stored data after tExpire time @@ -12,8 +11,6 @@ import ( type contactStore struct { // map of blob hashes to (map of node IDs to bools) hashes map[bits.Bitmap]map[bits.Bitmap]bool - // map of blob hashes to (map of node ids to tcp ports) - ports map[bits.Bitmap]map[bits.Bitmap]int // stores the peers themselves, so they can be updated in one place contacts map[bits.Bitmap]Contact lock sync.RWMutex @@ -22,23 +19,18 @@ type contactStore struct { func newStore() *contactStore { return &contactStore{ hashes: make(map[bits.Bitmap]map[bits.Bitmap]bool), - ports: make(map[bits.Bitmap]map[bits.Bitmap]int), contacts: make(map[bits.Bitmap]Contact), } } -func (s *contactStore) Upsert(blobHash bits.Bitmap, contact Contact, tcpPort int) { +func (s *contactStore) Upsert(blobHash bits.Bitmap, contact Contact) { s.lock.Lock() defer s.lock.Unlock() if _, ok := s.hashes[blobHash]; !ok { s.hashes[blobHash] = make(map[bits.Bitmap]bool) } - if _, ok := s.ports[blobHash]; !ok { - s.ports[blobHash] = make(map[bits.Bitmap]int) - } s.hashes[blobHash][contact.ID] = true - s.ports[blobHash][contact.ID] = tcpPort s.contacts[contact.ID] = contact } @@ -53,11 +45,7 @@ func (s *contactStore) Get(blobHash bits.Bitmap) []Contact { if !ok { panic("node id in IDs list, but not in nodeInfo") } - peerPort, ok := s.ports[blobHash][id] - if !ok { - panic("node id in IDs list, but missing peer port") - } - contacts = append(contacts, Contact{ID: contact.ID, IP: contact.IP, Port: peerPort}) + contacts = append(contacts, contact) } } return contacts From 620a5d7d4894308aa672cf43274558d9c92ca9a0 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Fri, 13 Jul 2018 13:31:54 -0400 Subject: [PATCH 11/13] handle peer port correctly --- cmd/dht.go | 18 +++++++----- dht/contact.go | 39 +++++++++++++++----------- dht/contact_test.go | 6 ++-- dht/dht.go | 1 + dht/dht_test.go | 12 ++++++++ dht/message.go | 8 +++--- dht/message_test.go | 4 +-- dht/node.go | 2 +- dht/node_test.go | 2 +- dht/routing_table_test.go | 59 ++++++++++++++++++++------------------- 10 files changed, 87 insertions(+), 64 deletions(-) diff --git a/cmd/dht.go b/cmd/dht.go index 2ba46b7..b277af4 100644 --- a/cmd/dht.go +++ b/cmd/dht.go @@ -1,26 +1,28 @@ package cmd import ( + "log" + "math/big" "net" + "net/http" "os" "os/signal" "strconv" "syscall" "time" + "github.com/lbryio/reflector.go/dht" "github.com/lbryio/reflector.go/dht/bits" + "github.com/spf13/cobra" - "log" - "net/http" - "math/big" ) type NodeRPC string type PingArgs struct { - nodeID string + nodeID string address string - port int + port int } type PingResult string @@ -76,8 +78,10 @@ func dhtCmd(cmd *cobra.Command, args []string) { log.Println("started node") node.AddKnownNode( dht.Contact{ - bits.FromHexP("62c8ad9fb40a16062e884a63cd81f47b94604446319663d1334e1734dcefc8874b348ec683225e4852017a846e07d94e"), - net.ParseIP("34.231.152.182"), 4444, + bits.FromHexP("62c8ad9fb40a16062e884a63cd81f47b94604446319663d1334e1734dcefc8874b348ec683225e4852017a846e07d94e"), + net.ParseIP("34.231.152.182"), + 4444, + 3333, }) _, _, err = dht.FindContacts(&node.Node, nodeID.Sub(bits.FromBigP(big.NewInt(1))), false, nil) rpcServer := dht.RunRPCServer(":1234", "/", node) diff --git a/dht/contact.go b/dht/contact.go index 32d5881..cd4fb88 100644 --- a/dht/contact.go +++ b/dht/contact.go @@ -3,6 +3,7 @@ package dht import ( "bytes" "net" + "strconv" "github.com/lbryio/lbry.go/errors" "github.com/lbryio/reflector.go/dht/bits" @@ -12,44 +13,47 @@ import ( // TODO: if routing table is ever empty (aka the node is isolated), it should re-bootstrap -// 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 - -// Contact is a type representation of another node that a specific node is in communication with. +// Contact contains information for contacting another node on the network type Contact struct { - ID bits.Bitmap - IP net.IP - Port int + ID bits.Bitmap + IP net.IP + Port int + PeerPort int } -// Equals returns T/F if two contacts are the same. +// Equals returns true if two contacts are the same. func (c Contact) Equals(other Contact, checkID bool) bool { return c.IP.Equal(other.IP) && c.Port == other.Port && (!checkID || c.ID == other.ID) } -// Addr returns the UPD Address of the contact. +// Addr returns the address of the contact. func (c Contact) Addr() *net.UDPAddr { return &net.UDPAddr{IP: c.IP, Port: c.Port} } -// String returns the concatenated short hex encoded string of its ID + @ + string represention of its UPD Address. +// String returns a short string representation of the contact func (c Contact) String() string { - return c.ID.HexShort() + "@" + c.Addr().String() + str := c.ID.HexShort() + "@" + c.Addr().String() + if c.PeerPort != 0 { + str += "(" + strconv.Itoa(c.PeerPort) + ")" + } + return str } -// MarshalCompact returns the compact byte slice representation of a contact. +// MarshalCompact returns a compact byteslice representation of the contact +// NOTE: The compact representation always uses the tcp PeerPort, not the udp Port. This is dumb, but that's how the python daemon does it func (c Contact) MarshalCompact() ([]byte, error) { if c.IP.To4() == nil { return nil, errors.Err("ip not set") } - if c.Port < 0 || c.Port > 65535 { + if c.PeerPort < 0 || c.PeerPort > 65535 { return nil, errors.Err("invalid port") } var buf bytes.Buffer buf.Write(c.IP.To4()) - buf.WriteByte(byte(c.Port >> 8)) - buf.WriteByte(byte(c.Port)) + buf.WriteByte(byte(c.PeerPort >> 8)) + buf.WriteByte(byte(c.PeerPort)) buf.Write(c.ID[:]) if buf.Len() != compactNodeInfoLength { @@ -59,13 +63,14 @@ func (c Contact) MarshalCompact() ([]byte, error) { return buf.Bytes(), nil } -// UnmarshalCompact unmarshals the compact byte slice representation of a contact. +// UnmarshalCompact unmarshals the compact byteslice representation of a contact. +// NOTE: The compact representation always uses the tcp PeerPort, not the udp Port. This is dumb, but that's how the python daemon does it func (c *Contact) UnmarshalCompact(b []byte) error { if len(b) != compactNodeInfoLength { return errors.Err("invalid compact length") } c.IP = net.IPv4(b[0], b[1], b[2], b[3]).To4() - c.Port = int(uint16(b[5]) | uint16(b[4])<<8) + c.PeerPort = int(uint16(b[5]) | uint16(b[4])<<8) c.ID = bits.FromBytesP(b[6:]) return nil } diff --git a/dht/contact_test.go b/dht/contact_test.go index f3c9d93..cfe5abc 100644 --- a/dht/contact_test.go +++ b/dht/contact_test.go @@ -10,9 +10,9 @@ import ( func TestCompactEncoding(t *testing.T) { c := Contact{ - ID: bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41"), - IP: net.ParseIP("1.2.3.4"), - Port: int(55<<8 + 66), + ID: bits.FromHexP("1c8aff71b99462464d9eeac639595ab99664be3482cb91a29d87467515c7d9158fe72aa1f1582dab07d8f8b5db277f41"), + IP: net.ParseIP("1.2.3.4"), + PeerPort: int(55<<8 + 66), } var compact []byte diff --git a/dht/dht.go b/dht/dht.go index f8e328c..a92d373 100644 --- a/dht/dht.go +++ b/dht/dht.go @@ -318,6 +318,7 @@ func (dht *DHT) startReannouncer() { func (dht *DHT) storeOnNode(hash bits.Bitmap, c Contact) { // self-store if dht.contact.ID == c.ID { + c.PeerPort = dht.conf.PeerProtocolPort dht.node.Store(hash, c) return } diff --git a/dht/dht_test.go b/dht/dht_test.go index 7ee2ecf..918c2db 100644 --- a/dht/dht_test.go +++ b/dht/dht_test.go @@ -10,6 +10,10 @@ import ( ) func TestNodeFinder_FindNodes(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow nodeFinder test") + } + bs, dhts := TestingCreateDHT(t, 3, true, false) defer func() { for i := range dhts { @@ -73,6 +77,10 @@ func TestNodeFinder_FindNodes_NoBootstrap(t *testing.T) { } func TestNodeFinder_FindValue(t *testing.T) { + if testing.Short() { + t.Skip("skipping slow nodeFinder test") + } + bs, dhts := TestingCreateDHT(t, 3, true, false) defer func() { for i := range dhts { @@ -104,6 +112,10 @@ func TestNodeFinder_FindValue(t *testing.T) { } func TestDHT_LargeDHT(t *testing.T) { + if testing.Short() { + t.Skip("skipping large DHT test") + } + nodes := 100 bs, dhts := TestingCreateDHT(t, nodes, true, true) defer func() { diff --git a/dht/message.go b/dht/message.go index d10f1d5..31ad475 100644 --- a/dht/message.go +++ b/dht/message.go @@ -44,7 +44,7 @@ const ( protocolVersionField = "protocolVersion" ) -// Message is an extension of the bencode marshalling interface for serialized message passing. +// Message is a DHT message type Message interface { bencode.Marshaler } @@ -82,7 +82,7 @@ func newMessageID() messageID { return m } -// Request represents the structured request from one node to another. +// Request represents a DHT request message type Request struct { ID messageID NodeID bits.Bitmap @@ -261,7 +261,7 @@ func (s *storeArgs) UnmarshalBencode(b []byte) error { return nil } -// Response represents the structured response one node returns to another. +// Response represents a DHT response message type Response struct { ID messageID NodeID bits.Bitmap @@ -416,7 +416,7 @@ func (r *Response) UnmarshalBencode(b []byte) error { return nil } -// Error represents an error message that is returned from one node to another in communication. +// Error represents a DHT error response type Error struct { ID messageID NodeID bits.Bitmap diff --git a/dht/message_test.go b/dht/message_test.go index 71812eb..040f5a1 100644 --- a/dht/message_test.go +++ b/dht/message_test.go @@ -103,9 +103,9 @@ func TestBencodeFindValueResponse(t *testing.T) { ID: newMessageID(), NodeID: bits.Rand(), FindValueKey: bits.Rand().RawString(), - Token: "arst", + Token: "arstarstarst", Contacts: []Contact{ - {ID: bits.Rand(), IP: net.IPv4(1, 2, 3, 4).To4(), Port: 5678}, + {ID: bits.Rand(), IP: net.IPv4(1, 2, 3, 4).To4(), PeerPort: 8765}, }, } diff --git a/dht/node.go b/dht/node.go index 0c8f95a..c3d3738 100644 --- a/dht/node.go +++ b/dht/node.go @@ -236,7 +236,7 @@ func (n *Node) handleRequest(addr *net.UDPAddr, request Request) { // TODO: we should be sending the IP in the request, not just using the sender's IP // TODO: should we be using StoreArgs.NodeID or StoreArgs.Value.LbryID ??? if n.tokens.Verify(request.StoreArgs.Value.Token, request.NodeID, addr) { - n.Store(request.StoreArgs.BlobHash, Contact{ID: request.StoreArgs.NodeID, IP: addr.IP, Port: request.StoreArgs.Value.Port}) + n.Store(request.StoreArgs.BlobHash, Contact{ID: request.StoreArgs.NodeID, IP: addr.IP, Port: addr.Port, PeerPort: request.StoreArgs.Value.Port}) err := n.sendMessage(addr, Response{ID: request.ID, NodeID: n.id, Data: storeSuccessResponse}) if err != nil { diff --git a/dht/node_test.go b/dht/node_test.go index 26d0cc7..2a167b5 100644 --- a/dht/node_test.go +++ b/dht/node_test.go @@ -289,7 +289,7 @@ func TestFindValueExisting(t *testing.T) { messageID := newMessageID() valueToFind := bits.Rand() - nodeToFind := Contact{ID: bits.Rand(), IP: net.ParseIP("1.2.3.4"), Port: 1286} + nodeToFind := Contact{ID: bits.Rand(), IP: net.ParseIP("1.2.3.4"), PeerPort: 1286} dht.node.store.Upsert(valueToFind, nodeToFind) dht.node.store.Upsert(valueToFind, nodeToFind) dht.node.store.Upsert(valueToFind, nodeToFind) diff --git a/dht/routing_table_test.go b/dht/routing_table_test.go index 89064fd..2cc25b5 100644 --- a/dht/routing_table_test.go +++ b/dht/routing_table_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/lbryio/reflector.go/dht/bits" + "github.com/sebdah/goldie" ) @@ -59,7 +60,7 @@ func TestBucket_Split(t *testing.T) { } for i, testCase := range tests { - rt.Update(Contact{testCase.id, net.ParseIP("127.0.0.1"), 8000 + i}) + rt.Update(Contact{testCase.id, net.ParseIP("127.0.0.1"), 8000 + i, 0}) 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)) @@ -126,25 +127,25 @@ 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}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), net.ParseIP("127.0.0.1"), 8001, 0}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"), net.ParseIP("127.0.0.1"), 8002, 0}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"), net.ParseIP("127.0.0.1"), 8003, 0}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004"), net.ParseIP("127.0.0.1"), 8004, 0}) // 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}) + rt.Update(Contact{bits.FromHexP("800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8001, 0}) + rt.Update(Contact{bits.FromHexP("900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8002, 0}) + rt.Update(Contact{bits.FromHexP("a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8003, 0}) + rt.Update(Contact{bits.FromHexP("b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8004, 0}) // 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}) + rt.Update(Contact{bits.FromHexP("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8005, 0}) + rt.Update(Contact{bits.FromHexP("d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8006, 0}) + rt.Update(Contact{bits.FromHexP("e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8007, 0}) + rt.Update(Contact{bits.FromHexP("f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8008, 0}) // 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}) + rt.Update(Contact{bits.FromHexP("910000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.1"), 8009, 0}) if len(rt.buckets) != 3 { t.Errorf("expected 3 buckets, got %d", len(rt.buckets)) @@ -158,25 +159,25 @@ 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}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), net.ParseIP("127.0.0.1"), 8001, 0}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"), net.ParseIP("127.0.0.1"), 8002, 0}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"), net.ParseIP("127.0.0.1"), 8003, 0}) + rt.Update(Contact{bits.FromHexP("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004"), net.ParseIP("127.0.0.1"), 8004, 0}) // 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}) + rt.Update(Contact{bits.FromHexP("800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8001, 0}) + rt.Update(Contact{bits.FromHexP("900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8002, 0}) + rt.Update(Contact{bits.FromHexP("a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8003, 0}) + rt.Update(Contact{bits.FromHexP("b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8004, 0}) // 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}) + rt.Update(Contact{bits.FromHexP("c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8005, 0}) + rt.Update(Contact{bits.FromHexP("d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8006, 0}) + rt.Update(Contact{bits.FromHexP("e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8007, 0}) + rt.Update(Contact{bits.FromHexP("f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.2"), 8008, 0}) // 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}) + rt.Update(Contact{bits.FromHexP("ffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), net.ParseIP("127.0.0.1"), 8009, 0}) if len(rt.buckets) != 2 { t.Errorf("expected 2 buckets, got %d", len(rt.buckets)) @@ -191,8 +192,8 @@ func TestRoutingTable_GetClosest(t *testing.T) { n2 := bits.FromHexP("FFFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") n3 := bits.FromHexP("111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") rt := newRoutingTable(n1) - rt.Update(Contact{n2, net.ParseIP("127.0.0.1"), 8001}) - rt.Update(Contact{n3, net.ParseIP("127.0.0.1"), 8002}) + rt.Update(Contact{n2, net.ParseIP("127.0.0.1"), 8001, 0}) + rt.Update(Contact{n3, net.ParseIP("127.0.0.1"), 8002, 0}) contacts := rt.GetClosest(bits.FromHexP("222222220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 1) if len(contacts) != 1 { From c967af4a98fea5eeb5cb952e0576bfb7d1ab5e99 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Tue, 17 Jul 2018 17:19:03 -0400 Subject: [PATCH 12/13] more -add rpc_port argument -run node on localhost for testing --- cmd/dht.go | 13 +++------- cmd/root.go | 2 +- dht/contact.go | 4 +-- dht/node_rpc.go | 67 ++++++++++++++++++++++++++----------------------- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/cmd/dht.go b/cmd/dht.go index b277af4..5e0f637 100644 --- a/cmd/dht.go +++ b/cmd/dht.go @@ -33,6 +33,7 @@ func (n *NodeRPC) Ping(r *http.Request, args *PingArgs, result *PingResult) erro } var dhtPort int +var rpcPort int func init() { var cmd = &cobra.Command{ @@ -44,6 +45,7 @@ func init() { } cmd.PersistentFlags().StringP("nodeID", "n", "", "nodeID in hex") cmd.PersistentFlags().IntVar(&dhtPort, "port", 4567, "Port to start DHT on") + cmd.PersistentFlags().IntVar(&rpcPort, "rpc_port", 1234, "Port to listen for rpc commands on") rootCmd.AddCommand(cmd) } @@ -70,21 +72,14 @@ func dhtCmd(cmd *cobra.Command, args []string) { } log.Println(nodeID.String()) node := dht.NewBootstrapNode(nodeID, 1*time.Millisecond, 1*time.Minute) - listener, err := net.ListenPacket(dht.Network, "0.0.0.0:"+strconv.Itoa(dhtPort)) + listener, err := net.ListenPacket(dht.Network, "127.0.0.1:"+strconv.Itoa(dhtPort)) checkErr(err) conn := listener.(*net.UDPConn) err = node.Connect(conn) checkErr(err) log.Println("started node") - node.AddKnownNode( - dht.Contact{ - bits.FromHexP("62c8ad9fb40a16062e884a63cd81f47b94604446319663d1334e1734dcefc8874b348ec683225e4852017a846e07d94e"), - net.ParseIP("34.231.152.182"), - 4444, - 3333, - }) _, _, err = dht.FindContacts(&node.Node, nodeID.Sub(bits.FromBigP(big.NewInt(1))), false, nil) - rpcServer := dht.RunRPCServer(":1234", "/", node) + rpcServer := dht.RunRPCServer("127.0.0.1:"+strconv.Itoa(rpcPort), "/", node) interruptChan := make(chan os.Signal, 1) signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) <-interruptChan diff --git a/cmd/root.go b/cmd/root.go index f9528ef..35d410c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -27,7 +27,7 @@ var verbose []string const ( verboseAll = "all" verboseDHT = "dht" - verboseNodeFinder = "nodefinder" + verboseNodeFinder = "node_finder" ) var conf string diff --git a/dht/contact.go b/dht/contact.go index cd4fb88..64f205b 100644 --- a/dht/contact.go +++ b/dht/contact.go @@ -17,8 +17,8 @@ import ( type Contact struct { ID bits.Bitmap IP net.IP - Port int - PeerPort int + Port int // the udp port used for the dht + PeerPort int // the tcp port a peer can be contacted on for blob requests } // Equals returns true if two contacts are the same. diff --git a/dht/node_rpc.go b/dht/node_rpc.go index 7763492..a04349d 100644 --- a/dht/node_rpc.go +++ b/dht/node_rpc.go @@ -1,10 +1,11 @@ package dht import ( + "errors" "net" "net/http" - "errors" "sync" + "github.com/gorilla/mux" "github.com/gorilla/rpc" "github.com/gorilla/rpc/json" @@ -12,7 +13,7 @@ import ( ) type NodeRPCServer struct { - Wg sync.WaitGroup + Wg sync.WaitGroup Node *BootstrapNode } @@ -23,13 +24,12 @@ type NodeRPC int type PingArgs struct { NodeID string - IP string - Port int + IP string + Port int } type PingResult string - func (n *NodeRPC) Ping(r *http.Request, args *PingArgs, result *PingResult) error { if rpcServer == nil { return errors.New("no node set up") @@ -48,16 +48,16 @@ func (n *NodeRPC) Ping(r *http.Request, args *PingArgs, result *PingResult) erro } type FindArgs struct { - Key string + Key string NodeID string - IP string - Port int + IP string + Port int } type ContactResponse struct { NodeID string - IP string - Port int + IP string + Port int } type FindNodeResult []ContactResponse @@ -75,7 +75,7 @@ func (n *NodeRPC) FindNode(r *http.Request, args *FindArgs, result *FindNodeResu return err } c := Contact{ID: toQuery, IP: net.ParseIP(args.IP), Port: args.Port} - req := Request{ Arg: &key, Method: "findNode"} + req := Request{Arg: &key, Method: "findNode"} nodeResponse := rpcServer.Node.Send(c, req) contacts := []ContactResponse{} if nodeResponse != nil && nodeResponse.Contacts != nil { @@ -89,7 +89,7 @@ func (n *NodeRPC) FindNode(r *http.Request, args *FindArgs, result *FindNodeResu type FindValueResult struct { Contacts []ContactResponse - Value string + Value string } func (n *NodeRPC) FindValue(r *http.Request, args *FindArgs, result *FindValueResult) error { @@ -105,7 +105,7 @@ func (n *NodeRPC) FindValue(r *http.Request, args *FindArgs, result *FindValueRe return err } c := Contact{ID: toQuery, IP: net.ParseIP(args.IP), Port: args.Port} - req := Request{ Arg: &key, Method: "findValue"} + req := Request{Arg: &key, Method: "findValue"} nodeResponse := rpcServer.Node.Send(c, req) contacts := []ContactResponse{} if nodeResponse != nil && nodeResponse.FindValueKey != "" { @@ -126,7 +126,7 @@ type IterativeFindValueArgs struct { } type IterativeFindValueResult struct { - Contacts []ContactResponse + Contacts []ContactResponse FoundValue bool } @@ -149,19 +149,19 @@ func (n *NodeRPC) IterativeFindValue(r *http.Request, args *IterativeFindValueAr } type BucketResponse struct { - Start string - End string - Count int + Start string + End string + Count int Contacts []ContactResponse } type RoutingTableResponse struct { - NodeID string - Count int + NodeID string + Count int Buckets []BucketResponse } -type GetRoutingTableArgs struct {} +type GetRoutingTableArgs struct{} func (n *NodeRPC) GetRoutingTable(r *http.Request, args *GetRoutingTableArgs, result *RoutingTableResponse) error { if rpcServer == nil { @@ -182,14 +182,27 @@ func (n *NodeRPC) GetRoutingTable(r *http.Request, args *GetRoutingTableArgs, re return nil } +type AddKnownNodeResponse struct{} + +func (n *NodeRPC) AddKnownNode(r *http.Request, args *ContactResponse, result *AddKnownNodeResponse) error { + if rpcServer == nil { + return errors.New("no node set up") + } + rpcServer.Node.AddKnownNode( + Contact{ + bits.FromHexP(args.NodeID), + net.ParseIP(args.IP), args.Port, 0, + }) + return nil +} + func RunRPCServer(address, rpcPath string, node *BootstrapNode) NodeRPCServer { mut.Lock() defer mut.Unlock() rpcServer = &NodeRPCServer{ - Wg: sync.WaitGroup{}, + Wg: sync.WaitGroup{}, Node: node, } - c := make(chan *http.Server) rpcServer.Wg.Add(1) go func() { s := rpc.NewServer() @@ -202,15 +215,7 @@ func RunRPCServer(address, rpcPath string, node *BootstrapNode) NodeRPCServer { server := &http.Server{Addr: address, Handler: r} log.Println("rpc listening on " + address) server.ListenAndServe() - c <- server - }() - go func() { - rpcServer.Wg.Wait() - close(c) - log.Println("stopped rpc listening on " + address) - for server := range c { - server.Close() - } }() + return *rpcServer } From e642c110b8979ac487bc416177e38e3a10fd7309 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 25 Jul 2018 11:44:11 -0400 Subject: [PATCH 13/13] refactor contact sort --- dht/contact.go | 16 +++++----------- dht/node_finder.go | 17 +---------------- dht/routing_table.go | 22 ++++++++-------------- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/dht/contact.go b/dht/contact.go index 64f205b..027b258 100644 --- a/dht/contact.go +++ b/dht/contact.go @@ -3,6 +3,7 @@ package dht import ( "bytes" "net" + "sort" "strconv" "github.com/lbryio/lbry.go/errors" @@ -115,15 +116,8 @@ func (c *Contact) UnmarshalBencode(b []byte) error { return nil } -type sortedContact struct { - contact Contact - xorDistanceToTarget bits.Bitmap -} - -type byXorDistance []sortedContact - -func (a byXorDistance) Len() int { return len(a) } -func (a byXorDistance) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byXorDistance) Less(i, j int) bool { - return a[i].xorDistanceToTarget.Cmp(a[j].xorDistanceToTarget) < 0 +func sortByDistance(contacts []Contact, target bits.Bitmap) { + sort.Slice(contacts, func(i, j int) bool { + return contacts[i].ID.Xor(target).Cmp(contacts[j].ID.Xor(target)) < 0 + }) } diff --git a/dht/node_finder.go b/dht/node_finder.go index 7375bd9..60b2098 100644 --- a/dht/node_finder.go +++ b/dht/node_finder.go @@ -1,7 +1,6 @@ package dht import ( - "sort" "sync" "time" @@ -268,7 +267,7 @@ func (cf *contactFinder) appendNewToShortlist(contacts []Contact) { } } - sortInPlace(cf.shortlist, cf.target) + sortByDistance(cf.shortlist, cf.target) } // popFromShortlist pops the first contact off the shortlist and returns it @@ -345,17 +344,3 @@ func (cf *contactFinder) closest(contacts ...Contact) *Contact { } return &closest } - -func sortInPlace(contacts []Contact, target bits.Bitmap) { - toSort := make([]sortedContact, len(contacts)) - - for i, n := range contacts { - toSort[i] = sortedContact{n, n.ID.Xor(target)} - } - - sort.Sort(byXorDistance(toSort)) - - for i, c := range toSort { - contacts[i] = c.contact - } -} diff --git a/dht/routing_table.go b/dht/routing_table.go index 7b53d9a..30fc906 100644 --- a/dht/routing_table.go +++ b/dht/routing_table.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net" - "sort" "strconv" "strings" "sync" @@ -291,21 +290,16 @@ func (rt *routingTable) GetClosest(target bits.Bitmap, limit int) []Contact { // getClosest returns the closest `limit` contacts from the routing table func (rt *routingTable) getClosest(target bits.Bitmap, limit int) []Contact { - var toSort []sortedContact - for _, b := range rt.buckets { - for _, c := range b.Contacts() { - toSort = append(toSort, sortedContact{c, c.ID.Xor(target)}) - } - } - sort.Sort(byXorDistance(toSort)) - var contacts []Contact - for _, sorted := range toSort { - contacts = append(contacts, sorted.contact) - if len(contacts) >= limit { - break - } + for _, b := range rt.buckets { + contacts = append(contacts, b.Contacts()...) } + + sortByDistance(contacts, target) + if len(contacts) > limit { + contacts = contacts[:limit] + } + return contacts }