github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/kad_table.go (about)

     1  // Copyright (C) 2013-2017, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.)
     2  // Use of this source code is governed by GPLv3 found in the LICENSE file
     3  //
     4  // This code is adapted from the libp2p project, specifically:
     5  // https://github.com/libp2p/go-libp2p-kbucket/table.go
     6  // we don't need to unify keyspaces between random strings and peer.IDs which ipfs requires.
     7  //----------------------------------------------------------------------------------------
     8  
     9  package holochain
    10  
    11  import (
    12  	"container/list"
    13  	"fmt"
    14  	. "github.com/holochain/holochain-proto/hash"
    15  	peer "github.com/libp2p/go-libp2p-peer"
    16  	pstore "github.com/libp2p/go-libp2p-peerstore"
    17  	"sort"
    18  	"sync"
    19  	"time"
    20  )
    21  
    22  // RoutingTable defines the routing table.
    23  type RoutingTable struct {
    24  
    25  	// ID of the local peer
    26  	local peer.ID
    27  
    28  	// Blanket lock, refine later for better performance
    29  	tabLock sync.RWMutex
    30  
    31  	// latency metrics
    32  	metrics pstore.Metrics
    33  
    34  	// Maximum acceptable latency for peers in this cluster
    35  	maxLatency time.Duration
    36  
    37  	// kBuckets define all the fingers to other nodes.
    38  	Buckets    []*Bucket
    39  	bucketsize int
    40  
    41  	// notification functions
    42  	PeerRemoved func(peer.ID)
    43  	PeerAdded   func(peer.ID)
    44  }
    45  
    46  // NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance.
    47  func NewRoutingTable(bucketsize int, localID peer.ID, latency time.Duration, m pstore.Metrics) *RoutingTable {
    48  	rt := &RoutingTable{
    49  		Buckets:     []*Bucket{newBucket()},
    50  		bucketsize:  bucketsize,
    51  		local:       localID,
    52  		maxLatency:  latency,
    53  		metrics:     m,
    54  		PeerRemoved: func(peer.ID) {},
    55  		PeerAdded:   func(peer.ID) {},
    56  	}
    57  
    58  	return rt
    59  }
    60  
    61  func commonPrefixLen(a, b peer.ID) int {
    62  	return ZeroPrefixLen(XOR([]byte(a), []byte(b)))
    63  }
    64  
    65  // Update adds or moves the given peer to the front of its respective bucket
    66  // If a peer gets removed from a bucket, it is returned
    67  func (rt *RoutingTable) Update(p peer.ID) {
    68  	cpl := commonPrefixLen(p, rt.local)
    69  
    70  	rt.tabLock.Lock()
    71  	defer rt.tabLock.Unlock()
    72  	bucketID := cpl
    73  	if bucketID >= len(rt.Buckets) {
    74  		bucketID = len(rt.Buckets) - 1
    75  	}
    76  
    77  	bucket := rt.Buckets[bucketID]
    78  	if bucket.Has(p) {
    79  		// If the peer is already in the table, move it to the front.
    80  		// This signifies that it it "more active" and the less active nodes
    81  		// Will as a result tend towards the back of the list
    82  		bucket.MoveToFront(p)
    83  		return
    84  	}
    85  
    86  	if rt.metrics.LatencyEWMA(p) > rt.maxLatency {
    87  		// Connection doesn't meet requirements, skip!
    88  		return
    89  	}
    90  
    91  	// New peer, add to bucket
    92  	bucket.PushFront(p)
    93  	rt.PeerAdded(p)
    94  
    95  	// Are we past the max bucket size?
    96  	if bucket.Len() > rt.bucketsize {
    97  		// If this bucket is the rightmost bucket, and its full
    98  		// we need to split it and create a new bucket
    99  		if bucketID == len(rt.Buckets)-1 {
   100  			rt.PeerRemoved(rt.nextBucket())
   101  			return
   102  		} else {
   103  			// If the bucket cant split kick out least active node
   104  			rt.PeerRemoved(bucket.PopBack())
   105  			return
   106  		}
   107  	}
   108  }
   109  
   110  // Remove deletes a peer from the routing table. This is to be used
   111  // when we are sure a node has disconnected completely.
   112  func (rt *RoutingTable) Remove(p peer.ID) {
   113  	rt.tabLock.Lock()
   114  	defer rt.tabLock.Unlock()
   115  	cpl := commonPrefixLen(p, rt.local)
   116  
   117  	bucketID := cpl
   118  	if bucketID >= len(rt.Buckets) {
   119  		bucketID = len(rt.Buckets) - 1
   120  	}
   121  
   122  	bucket := rt.Buckets[bucketID]
   123  	bucket.Remove(p)
   124  	rt.PeerRemoved(p)
   125  }
   126  
   127  func (rt *RoutingTable) nextBucket() peer.ID {
   128  	bucket := rt.Buckets[len(rt.Buckets)-1]
   129  	newBucket := bucket.Split(len(rt.Buckets)-1, rt.local)
   130  	rt.Buckets = append(rt.Buckets, newBucket)
   131  	if newBucket.Len() > rt.bucketsize {
   132  		return rt.nextBucket()
   133  	}
   134  
   135  	// If all elements were on left side of split...
   136  	if bucket.Len() > rt.bucketsize {
   137  		return bucket.PopBack()
   138  	}
   139  	return ""
   140  }
   141  
   142  // Find a specific peer by ID or return nil
   143  func (rt *RoutingTable) Find(id peer.ID) peer.ID {
   144  	srch := rt.NearestPeers(HashFromPeerID(id), 1)
   145  	if len(srch) == 0 || srch[0] != id {
   146  		return ""
   147  	}
   148  	return srch[0]
   149  }
   150  
   151  // NearestPeer returns a single peer that is nearest to the given Hash
   152  func (rt *RoutingTable) NearestPeer(hash Hash) peer.ID {
   153  	peers := rt.NearestPeers(hash, 1)
   154  	if len(peers) > 0 {
   155  		return peers[0]
   156  	}
   157  
   158  	Debugf("NearestPeer: Returning nil, table size = %d", rt.Size())
   159  	return ""
   160  }
   161  
   162  func copyPeersFromList(target peer.ID, hashArr HashSorterArr, peerList *list.List) HashSorterArr {
   163  	center := HashFromPeerID(target)
   164  	for e := peerList.Front(); e != nil; e = e.Next() {
   165  		h := HashFromPeerID(e.Value.(peer.ID))
   166  		pd := HashDistance{
   167  			Hash:     h,
   168  			Distance: HashXORDistance(h, center),
   169  		}
   170  		hashArr = append(hashArr, &pd)
   171  	}
   172  	return hashArr
   173  }
   174  
   175  func SortClosestPeers(peers []peer.ID, target Hash) []peer.ID {
   176  	var hsarr HashSorterArr
   177  	for _, p := range peers {
   178  		h := HashFromPeerID(p)
   179  		hd := &HashDistance{
   180  			Hash:     h,
   181  			Distance: HashXORDistance(h, target),
   182  		}
   183  		hsarr = append(hsarr, hd)
   184  	}
   185  	sort.Sort(hsarr)
   186  	var out []peer.ID
   187  	for _, p := range hsarr {
   188  		out = append(out, PeerIDFromHash(p.Hash.(Hash)))
   189  	}
   190  	return out
   191  }
   192  
   193  // NearestPeers returns a list of the 'count' closest peers to the given ID
   194  func (rt *RoutingTable) NearestPeers(hash Hash, count int) []peer.ID {
   195  	id := PeerIDFromHash(hash)
   196  	cpl := commonPrefixLen(id, rt.local)
   197  
   198  	rt.tabLock.RLock()
   199  
   200  	// Get bucket at cpl index or last bucket
   201  	var bucket *Bucket
   202  	if cpl >= len(rt.Buckets) {
   203  		cpl = len(rt.Buckets) - 1
   204  	}
   205  	bucket = rt.Buckets[cpl]
   206  
   207  	var hashArr HashSorterArr
   208  	hashArr = copyPeersFromList(id, hashArr, bucket.list)
   209  	if len(hashArr) < count {
   210  		// In the case of an unusual split, one bucket may be short or empty.
   211  		// if this happens, search both surrounding buckets for nearby peers
   212  		if cpl > 0 {
   213  			plist := rt.Buckets[cpl-1].list
   214  			hashArr = copyPeersFromList(id, hashArr, plist)
   215  		}
   216  
   217  		if cpl < len(rt.Buckets)-1 {
   218  			plist := rt.Buckets[cpl+1].list
   219  			hashArr = copyPeersFromList(id, hashArr, plist)
   220  		}
   221  	}
   222  	rt.tabLock.RUnlock()
   223  
   224  	// Sort by distance to local peer
   225  	sort.Sort(hashArr)
   226  
   227  	/*s := ""
   228  	for _, c := range hashArr {
   229  		p := PeerIDFromHash(c.Hash.(Hash))
   230  		s += fmt.Sprintf("%v ", p.Pretty()[2:4])
   231  	}
   232  	fmt.Printf("%s\n", s)
   233  	*/
   234  
   235  	var out []peer.ID
   236  	for i := 0; i < count && i < hashArr.Len(); i++ {
   237  		p := PeerIDFromHash(hashArr[i].Hash.(Hash))
   238  		out = append(out, p)
   239  	}
   240  
   241  	return out
   242  }
   243  
   244  // Size returns the total number of peers in the routing table
   245  func (rt *RoutingTable) Size() int {
   246  	var tot int
   247  	rt.tabLock.RLock()
   248  	for _, buck := range rt.Buckets {
   249  		tot += buck.Len()
   250  	}
   251  	rt.tabLock.RUnlock()
   252  	return tot
   253  }
   254  
   255  // IsEmpty returns bool
   256  func (rt *RoutingTable) IsEmpty() (empty bool) {
   257  	rt.tabLock.RLock()
   258  	empty = true
   259  	for _, buck := range rt.Buckets {
   260  		if buck.Len() > 0 {
   261  			empty = false
   262  			break
   263  		}
   264  	}
   265  	rt.tabLock.RUnlock()
   266  	return
   267  }
   268  
   269  // ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table.
   270  // NOTE: This is potentially unsafe... use at your own risk
   271  func (rt *RoutingTable) ListPeers() []peer.ID {
   272  	var peers []peer.ID
   273  	rt.tabLock.RLock()
   274  	for _, buck := range rt.Buckets {
   275  		peers = append(peers, buck.Peers()...)
   276  	}
   277  	rt.tabLock.RUnlock()
   278  	return peers
   279  }
   280  
   281  // Print prints a descriptive statement about the provided RoutingTable
   282  func (rt *RoutingTable) Print() {
   283  	fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency)
   284  	rt.tabLock.RLock()
   285  
   286  	for i, b := range rt.Buckets {
   287  		fmt.Printf("\tbucket: %d\n", i)
   288  
   289  		b.lk.RLock()
   290  		for e := b.list.Front(); e != nil; e = e.Next() {
   291  			p := e.Value.(peer.ID)
   292  			fmt.Printf("\t\t- %s %s\n", p.Pretty(), rt.metrics.LatencyEWMA(p).String())
   293  		}
   294  		b.lk.RUnlock()
   295  	}
   296  	rt.tabLock.RUnlock()
   297  }