From bade1aa277b8531ccead82384930adab8eb841f0 Mon Sep 17 00:00:00 2001 From: Jeromy <jeromyj@gmail.com> Date: Sun, 3 Aug 2014 15:04:24 -0700 Subject: [PATCH] tests for kbucket and some code cleanup --- routing/dht/bucket.go | 63 +++++++++++++++++ routing/dht/table.go | 143 +++++--------------------------------- routing/dht/table_test.go | 92 ++++++++++++++++++++++++ routing/dht/util.go | 78 +++++++++++++++++++++ 4 files changed, 249 insertions(+), 127 deletions(-) create mode 100644 routing/dht/bucket.go create mode 100644 routing/dht/table_test.go create mode 100644 routing/dht/util.go diff --git a/routing/dht/bucket.go b/routing/dht/bucket.go new file mode 100644 index 00000000..120ed29a --- /dev/null +++ b/routing/dht/bucket.go @@ -0,0 +1,63 @@ +package dht + +import ( + "container/list" + + peer "github.com/jbenet/go-ipfs/peer" +) +// Bucket holds a list of peers. +type Bucket list.List + +func (b *Bucket) Find(id peer.ID) *list.Element { + bucket_list := (*list.List)(b) + for e := bucket_list.Front(); e != nil; e = e.Next() { + if e.Value.(*peer.Peer).ID.Equal(id) { + return e + } + } + return nil +} + +func (b *Bucket) MoveToFront(e *list.Element) { + bucket_list := (*list.List)(b) + bucket_list.MoveToFront(e) +} + +func (b *Bucket) PushFront(p *peer.Peer) { + bucket_list := (*list.List)(b) + bucket_list.PushFront(p) +} + +func (b *Bucket) PopBack() *peer.Peer { + bucket_list := (*list.List)(b) + last := bucket_list.Back() + bucket_list.Remove(last) + return last.Value.(*peer.Peer) +} + +func (b *Bucket) Len() int { + bucket_list := (*list.List)(b) + return bucket_list.Len() +} + +// Splits a buckets peers into two buckets, the methods receiver will have +// peers with CPL equal to cpl, the returned bucket will have peers with CPL +// greater than cpl (returned bucket has closer peers) +func (b *Bucket) Split(cpl int, target ID) *Bucket { + bucket_list := (*list.List)(b) + out := list.New() + e := bucket_list.Front() + for e != nil { + peer_id := convertPeerID(e.Value.(*peer.Peer).ID) + peer_cpl := xor(peer_id, target).commonPrefixLen() + if peer_cpl > cpl { + cur := e + out.PushBack(e.Value) + e = e.Next() + bucket_list.Remove(cur) + continue + } + e = e.Next() + } + return (*Bucket)(out) +} diff --git a/routing/dht/table.go b/routing/dht/table.go index b8c20f8a..c1eed534 100644 --- a/routing/dht/table.go +++ b/routing/dht/table.go @@ -1,76 +1,12 @@ package dht import ( - "bytes" "container/list" "sort" - "crypto/sha256" - peer "github.com/jbenet/go-ipfs/peer" - u "github.com/jbenet/go-ipfs/util" ) -// ID for IpfsDHT should be a byte slice, to allow for simpler operations -// (xor). DHT ids are based on the peer.IDs. -// -// NOTE: peer.IDs are biased because they are multihashes (first bytes -// biased). Thus, may need to re-hash keys (uniform dist). TODO(jbenet) -type ID []byte - -// Bucket holds a list of peers. -type Bucket list.List - -func (b *Bucket) Find(id peer.ID) *list.Element { - bucket_list := (*list.List)(b) - for e := bucket_list.Front(); e != nil; e = e.Next() { - if e.Value.(*peer.Peer).ID.Equal(id) { - return e - } - } - return nil -} - -func (b *Bucket) MoveToFront(e *list.Element) { - bucket_list := (*list.List)(b) - bucket_list.MoveToFront(e) -} - -func (b *Bucket) PushFront(p *peer.Peer) { - bucket_list := (*list.List)(b) - bucket_list.PushFront(p) -} - -func (b *Bucket) PopBack() *peer.Peer { - bucket_list := (*list.List)(b) - last := bucket_list.Back() - bucket_list.Remove(last) - return last.Value.(*peer.Peer) -} - -func (b *Bucket) Len() int { - bucket_list := (*list.List)(b) - return bucket_list.Len() -} - -func (b *Bucket) Split(cpl int, target ID) *Bucket { - bucket_list := (*list.List)(b) - out := list.New() - e := bucket_list.Front() - for e != nil { - peer_id := convertPeerID(e.Value.(*peer.Peer).ID) - peer_cpl := xor(peer_id, target).commonPrefixLen() - if peer_cpl > cpl { - cur := e - out.PushBack(e.Value) - e = e.Next() - bucket_list.Remove(cur) - continue - } - } - return (*Bucket)(out) -} - // RoutingTable defines the routing table. type RoutingTable struct { @@ -82,14 +18,12 @@ type RoutingTable struct { bucketsize int } -func convertPeerID(id peer.ID) ID { - hash := sha256.Sum256(id) - return hash[:] -} - -func convertKey(id u.Key) ID { - hash := sha256.Sum256([]byte(id)) - return hash[:] +func NewRoutingTable(bucketsize int, local_id ID) *RoutingTable { + rt := new(RoutingTable) + rt.Buckets = []*Bucket{new(Bucket)} + rt.bucketsize = bucketsize + rt.local = local_id + return rt } // Update adds or moves the given peer to the front of its respective bucket @@ -114,6 +48,10 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer { if b_id == len(rt.Buckets) - 1 { new_bucket := bucket.Split(b_id, rt.local) rt.Buckets = append(rt.Buckets, new_bucket) + if new_bucket.Len() > rt.bucketsize { + // This is another very rare and annoying case + panic("Case not handled.") + } // If all elements were on left side of split... if bucket.Len() > rt.bucketsize { @@ -139,20 +77,23 @@ type peerDistance struct { p *peer.Peer distance ID } + +// peerSorterArr implements sort.Interface to sort peers by xor distance type peerSorterArr []*peerDistance func (p peerSorterArr) Len() int {return len(p)} func (p peerSorterArr) Swap(a, b int) {p[a],p[b] = p[b],p[a]} func (p peerSorterArr) Less(a, b int) bool { - return p[a].distance.Less(p[b]) + return p[a].distance.Less(p[b].distance) } // +// Returns a single peer that is nearest to the given ID func (rt *RoutingTable) NearestPeer(id ID) *peer.Peer { peers := rt.NearestPeers(id, 1) return peers[0] } -//TODO: make this accept an ID, requires method of converting keys to IDs +// Returns a list of the 'count' closest peers to the given ID func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { cpl := xor(id, rt.local).commonPrefixLen() @@ -170,7 +111,6 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { } var peerArr peerSorterArr - plist := (*list.List)(bucket) for e := plist.Front();e != nil; e = e.Next() { p := e.Value.(*peer.Peer) @@ -182,6 +122,7 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { peerArr = append(peerArr, &pd) } + // Sort by distance to local peer sort.Sort(peerArr) var out []*peer.Peer @@ -191,55 +132,3 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer { return out } - -func (id ID) Equal(other ID) bool { - return bytes.Equal(id, other) -} - -func (id ID) Less(other interface{}) bool { - a, b := equalizeSizes(id, other.(ID)) - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return a[i] < b[i] - } - } - return len(a) < len(b) -} - -func (id ID) commonPrefixLen() int { - for i := 0; i < len(id); i++ { - for j := 0; j < 8; j++ { - if (id[i]>>uint8(7-j))&0x1 != 0 { - return i*8 + j - } - } - } - return len(id)*8 - 1 -} - -func xor(a, b ID) ID { - a, b = equalizeSizes(a, b) - - c := make(ID, len(a)) - for i := 0; i < len(a); i++ { - c[i] = a[i] ^ b[i] - } - return c -} - -func equalizeSizes(a, b ID) (ID, ID) { - la := len(a) - lb := len(b) - - if la < lb { - na := make([]byte, lb) - copy(na, a) - a = na - } else if lb < la { - nb := make([]byte, la) - copy(nb, b) - b = nb - } - - return a, b -} diff --git a/routing/dht/table_test.go b/routing/dht/table_test.go new file mode 100644 index 00000000..cb52bd1a --- /dev/null +++ b/routing/dht/table_test.go @@ -0,0 +1,92 @@ +package dht + +import ( + crand "crypto/rand" + "crypto/sha256" + "math/rand" + "container/list" + "testing" + + peer "github.com/jbenet/go-ipfs/peer" +) + +func _randPeer() *peer.Peer { + p := new(peer.Peer) + p.ID = make(peer.ID, 16) + crand.Read(p.ID) + return p +} + +func _randID() ID { + buf := make([]byte, 16) + crand.Read(buf) + + hash := sha256.Sum256(buf) + return ID(hash[:]) +} + +// Test basic features of the bucket struct +func TestBucket(t *testing.T) { + b := new(Bucket) + + peers := make([]*peer.Peer, 100) + for i := 0; i < 100; i++ { + peers[i] = _randPeer() + b.PushFront(peers[i]) + } + + local := _randPeer() + local_id := convertPeerID(local.ID) + + i := rand.Intn(len(peers)) + e := b.Find(peers[i].ID) + if e == nil { + t.Errorf("Failed to find peer: %v", peers[i]) + } + + spl := b.Split(0, convertPeerID(local.ID)) + llist := (*list.List)(b) + for e := llist.Front(); e != nil; e = e.Next() { + p := convertPeerID(e.Value.(*peer.Peer).ID) + cpl := xor(p, local_id).commonPrefixLen() + if cpl > 0 { + t.Fatalf("Split failed. found id with cpl > 0 in 0 bucket") + } + } + + rlist := (*list.List)(spl) + for e := rlist.Front(); e != nil; e = e.Next() { + p := convertPeerID(e.Value.(*peer.Peer).ID) + cpl := xor(p, local_id).commonPrefixLen() + if cpl == 0 { + t.Fatalf("Split failed. found id with cpl == 0 in non 0 bucket") + } + } +} + +// Right now, this just makes sure that it doesnt hang or crash +func TestTableUpdate(t *testing.T) { + local := _randPeer() + rt := NewRoutingTable(10, convertPeerID(local.ID)) + + peers := make([]*peer.Peer, 100) + for i := 0; i < 100; i++ { + peers[i] = _randPeer() + } + + // Testing Update + for i := 0; i < 10000; i++ { + p := rt.Update(peers[rand.Intn(len(peers))]) + if p != nil { + t.Log("evicted peer.") + } + } + + for i := 0; i < 100; i++ { + id := _randID() + ret := rt.NearestPeers(id, 5) + if len(ret) == 0 { + t.Fatal("Failed to find node near ID.") + } + } +} diff --git a/routing/dht/util.go b/routing/dht/util.go new file mode 100644 index 00000000..eed8d930 --- /dev/null +++ b/routing/dht/util.go @@ -0,0 +1,78 @@ +package dht + +import ( + "bytes" + "crypto/sha256" + + peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" +) + +// ID for IpfsDHT should be a byte slice, to allow for simpler operations +// (xor). DHT ids are based on the peer.IDs. +// +// NOTE: peer.IDs are biased because they are multihashes (first bytes +// biased). Thus, may need to re-hash keys (uniform dist). TODO(jbenet) +type ID []byte + +func (id ID) Equal(other ID) bool { + return bytes.Equal(id, other) +} + +func (id ID) Less(other interface{}) bool { + a, b := equalizeSizes(id, other.(ID)) + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return a[i] < b[i] + } + } + return len(a) < len(b) +} + +func (id ID) commonPrefixLen() int { + for i := 0; i < len(id); i++ { + for j := 0; j < 8; j++ { + if (id[i]>>uint8(7-j))&0x1 != 0 { + return i*8 + j + } + } + } + return len(id)*8 - 1 +} + +func xor(a, b ID) ID { + a, b = equalizeSizes(a, b) + + c := make(ID, len(a)) + for i := 0; i < len(a); i++ { + c[i] = a[i] ^ b[i] + } + return c +} + +func equalizeSizes(a, b ID) (ID, ID) { + la := len(a) + lb := len(b) + + if la < lb { + na := make([]byte, lb) + copy(na, a) + a = na + } else if lb < la { + nb := make([]byte, la) + copy(nb, b) + b = nb + } + + return a, b +} + +func convertPeerID(id peer.ID) ID { + hash := sha256.Sum256(id) + return hash[:] +} + +func convertKey(id u.Key) ID { + hash := sha256.Sum256([]byte(id)) + return hash[:] +} -- GitLab