aboutsummaryrefslogblamecommitdiffstats
path: root/swarm/network/kademlia.go
blob: 146f39106769ff67a2bd7605d21f28136f90a0c6 (plain) (tree)




























                                                                                  
                                                          





















                                                                                
                             



                                                 






                                                                                   
                                                            
                                                




                                                           






                                                         





                                                                           







                                                                                               
















                                                             
                                                                    
                   

                     



                         

                                                
                      

                                    


         






                                                             
                                             

 
                                                                
                                   
                                                      









                                                                          
                                                                                          
















                                                                       




                                        

                                                                                              

                             

















                                                                                                      
                 



                                                      
                 



                                                                                              
                 






                                                                       
                 







                                                                                     
          






                                                                                     

                                    







                                                                                        
                 



















                                                                                                           
                         











                                                                                           
                  
         



                                                           
         
                                      

 
                                                             
                                              

                             
                    
                                                                              



                                                            
                                



                                                     
                                        

                                        
                                                    
                                                                                      
                                







                                                                            
                                      












                                                                    
                                                                                                
                                                     

                             












                                                                                                        
                                                                           











                                                                       


                             






                                             
                                 


                             
                                 
                                                                                      







                                                                                    
                          
         

                
                                                                                      










                                                                           


                                                                              
                                                                          




                              
                                                                          


                             
                                                                         


                                   
                                         



                                                                                  
                                                               
                                                
                                                                             




                              
                                                                             


                             
                                                                         


                                   
                                                  


          
                                                                    


                                                     
                                                                

 




                                                                                           
                            
                                                                                                 
                                          

                        
                                             

                                          



                                                                       
                      


                                                                             
                                              
                                 

                                    
 

                           
                                          
















                                                                                           



                                                                                        
                                                                                                








                                              
 


                    

                                                                  
                                                              

                                                      












                                                                                                                                                                                   
                            

                                                            
                                                          
                                                                                                        
                            



                                                                                
                   













                                                                   

                                                                   




                                                                                                        


                                                                                 
                                                                                                                                               
                                                                                                                                                                                                       



                                                     
                                                                  
                              
                                                                                                      





                                                         
                                          
                                        









                                                                             
                                                                                                      








                                                         
                                          


























                                                                                                                                
                                                       
                        
                                           
                     

                            

 
                                                                  
                                                 
                                                       
                        
                                           
                                                                               
 


                                                       
                                                 


                                          

                                                                 
                                 

                                        
                                                              

                               
                                
                                                 
 

                                                                                           

                                                                               
                                                            


                                           

                                                                             

                                                       



                                                                                          
                         
                                   
                  
 
                                                                                                         
                                                      

                                                 
                 



                    

                                                                                            
                                     
                  

                                                                                                      
                      


                                    
                                                         
          

                        



                   
































                                                                                                          

                                                        
                                                  

                                                                                       
                                   
                                                                  
                                                                                


                                                                                       
                               

                                    
                                                   


                             













                                                                                                        

                 
                                                 

 

                                                             
                                                  
                                                                                            
                                   




                                                                                             
                                                         
                               

                                    
                                                   


                             



                                                                    


                                 
                                         


                              
                                                                                       






                                                      
                        
                    





                                                                                                    



                                                                                              

 
                                                                      





                                                                                          
                        
                                                       

                              


                                                             

                                                                           
                                                                  



                                                         









                                                                                                                           
         
 









                                                                                
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package network

import (
    "bytes"
    "fmt"
    "math/rand"
    "strings"
    "sync"
    "time"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/swarm/log"
    "github.com/ethereum/go-ethereum/swarm/pot"
    sv "github.com/ethereum/go-ethereum/swarm/version"
)

/*

Taking the proximity order relative to a fix point x classifies the points in
the space (n byte long byte sequences) into bins. Items in each are at
most half as distant from x as items in the previous bin. Given a sample of
uniformly distributed items (a hash function over arbitrary sequence) the
proximity scale maps onto series of subsets with cardinalities on a negative
exponential scale.

It also has the property that any two item belonging to the same bin are at
most half as distant from each other as they are from x.

If we think of random sample of items in the bins as connections in a network of
interconnected nodes then relative proximity can serve as the basis for local
decisions for graph traversal where the task is to find a route between two
points. Since in every hop, the finite distance halves, there is
a guaranteed constant maximum limit on the number of hops needed to reach one
node from the other.
*/

var Pof = pot.DefaultPof(256)

// KadParams holds the config params for Kademlia
type KadParams struct {
    // adjustable parameters
    MaxProxDisplay    int   // number of rows the table shows
    NeighbourhoodSize int   // nearest neighbour core minimum cardinality
    MinBinSize        int   // minimum number of peers in a row
    MaxBinSize        int   // maximum number of peers in a row before pruning
    RetryInterval     int64 // initial interval before a peer is first redialed
    RetryExponent     int   // exponent to multiply retry intervals with
    MaxRetries        int   // maximum number of redial attempts
    // function to sanction or prevent suggesting a peer
    Reachable func(*BzzAddr) bool `json:"-"`
}

// NewKadParams returns a params struct with default values
func NewKadParams() *KadParams {
    return &KadParams{
        MaxProxDisplay:    16,
        NeighbourhoodSize: 2,
        MinBinSize:        2,
        MaxBinSize:        4,
        RetryInterval:     4200000000, // 4.2 sec
        MaxRetries:        42,
        RetryExponent:     2,
    }
}

// Kademlia is a table of live peers and a db of known peers (node records)
type Kademlia struct {
    lock       sync.RWMutex
    *KadParams          // Kademlia configuration parameters
    base       []byte   // immutable baseaddress of the table
    addrs      *pot.Pot // pots container for known peer addresses
    conns      *pot.Pot // pots container for live peer connections
    depth      uint8    // stores the last current depth of saturation
    nDepth     int      // stores the last neighbourhood depth
    nDepthC    chan int // returned by DepthC function to signal neighbourhood depth change
    addrCountC chan int // returned by AddrCountC function to signal peer count change
}

// NewKademlia creates a Kademlia table for base address addr
// with parameters as in params
// if params is nil, it uses default values
func NewKademlia(addr []byte, params *KadParams) *Kademlia {
    if params == nil {
        params = NewKadParams()
    }
    return &Kademlia{
        base:      addr,
        KadParams: params,
        addrs:     pot.NewPot(nil, 0),
        conns:     pot.NewPot(nil, 0),
    }
}

// entry represents a Kademlia table entry (an extension of BzzAddr)
type entry struct {
    *BzzAddr
    conn    *Peer
    seenAt  time.Time
    retries int
}

// newEntry creates a kademlia peer from a *Peer
func newEntry(p *BzzAddr) *entry {
    return &entry{
        BzzAddr: p,
        seenAt:  time.Now(),
    }
}

// Label is a short tag for the entry for debug
func Label(e *entry) string {
    return fmt.Sprintf("%s (%d)", e.Hex()[:4], e.retries)
}

// Hex is the hexadecimal serialisation of the entry address
func (e *entry) Hex() string {
    return fmt.Sprintf("%x", e.Address())
}

// Register enters each address as kademlia peer record into the
// database of known peer addresses
func (k *Kademlia) Register(peers ...*BzzAddr) error {
    k.lock.Lock()
    defer k.lock.Unlock()
    var known, size int
    for _, p := range peers {
        // error if self received, peer should know better
        // and should be punished for this
        if bytes.Equal(p.Address(), k.base) {
            return fmt.Errorf("add peers: %x is self", k.base)
        }
        var found bool
        k.addrs, _, found, _ = pot.Swap(k.addrs, p, Pof, func(v pot.Val) pot.Val {
            // if not found
            if v == nil {
                // insert new offline peer into conns
                return newEntry(p)
            }
            // found among known peers, do nothing
            return v
        })
        if found {
            known++
        }
        size++
    }
    // send new address count value only if there are new addresses
    if k.addrCountC != nil && size-known > 0 {
        k.addrCountC <- k.addrs.Size()
    }

    k.sendNeighbourhoodDepthChange()
    return nil
}

// SuggestPeer returns an unconnected peer address as a peer suggestion for connection
func (k *Kademlia) SuggestPeer() (suggestedPeer *BzzAddr, saturationDepth int, changed bool) {
    k.lock.Lock()
    defer k.lock.Unlock()
    radius := neighbourhoodRadiusForPot(k.conns, k.NeighbourhoodSize, k.base)
    // collect undersaturated bins in ascending order of number of connected peers
    // and from shallow to deep (ascending order of PO)
    // insert them in a map of bin arrays, keyed with the number of connected peers
    saturation := make(map[int][]int)
    var lastPO int       // the last non-empty PO bin in the iteration
    saturationDepth = -1 // the deepest PO such that all shallower bins have >= k.MinBinSize peers
    var pastDepth bool   // whether po of iteration >= depth
    k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
        // process skipped empty bins
        for ; lastPO < po; lastPO++ {
            // find the lowest unsaturated bin
            if saturationDepth == -1 {
                saturationDepth = lastPO
            }
            // if there is an empty bin, depth is surely passed
            pastDepth = true
            saturation[0] = append(saturation[0], lastPO)
        }
        lastPO = po + 1
        // past radius, depth is surely passed
        if po >= radius {
            pastDepth = true
        }
        // beyond depth the bin is treated as unsaturated even if size >= k.MinBinSize
        // in order to achieve full connectivity to all neighbours
        if pastDepth && size >= k.MinBinSize {
            size = k.MinBinSize - 1
        }
        // process non-empty unsaturated bins
        if size < k.MinBinSize {
            // find the lowest unsaturated bin
            if saturationDepth == -1 {
                saturationDepth = po
            }
            saturation[size] = append(saturation[size], po)
        }
        return true
    })
    // to trigger peer requests for peers closer than closest connection, include
    // all bins from nearest connection upto nearest address as unsaturated
    var nearestAddrAt int
    k.addrs.EachNeighbour(k.base, Pof, func(_ pot.Val, po int) bool {
        nearestAddrAt = po
        return false
    })
    // including bins as size 0 has the effect that requesting connection
    // is prioritised over non-empty shallower bins
    for ; lastPO <= nearestAddrAt; lastPO++ {
        saturation[0] = append(saturation[0], lastPO)
    }
    // all PO bins are saturated, ie., minsize >= k.MinBinSize, no peer suggested
    if len(saturation) == 0 {
        return nil, 0, false
    }
    // find the first callable peer in the address book
    // starting from the bins with smallest size proceeding from shallow to deep
    // for each bin (up until neighbourhood radius) we find callable candidate peers
    for size := 0; size < k.MinBinSize && suggestedPeer == nil; size++ {
        bins, ok := saturation[size]
        if !ok {
            // no bin with this size
            continue
        }
        cur := 0
        curPO := bins[0]
        k.addrs.EachBin(k.base, Pof, curPO, func(po, _ int, f func(func(pot.Val) bool) bool) bool {
            curPO = bins[cur]
            // find the next bin that has size size
            if curPO == po {
                cur++
            } else {
                // skip bins that have no addresses
                for ; cur < len(bins) && curPO < po; cur++ {
                    curPO = bins[cur]
                }
                if po < curPO {
                    cur--
                    return true
                }
                // stop if there are no addresses
                if curPO < po {
                    return false
                }
            }
            // curPO found
            // find a callable peer out of the addresses in the unsaturated bin
            // stop if found
            f(func(val pot.Val) bool {
                e := val.(*entry)
                if k.callable(e) {
                    suggestedPeer = e.BzzAddr
                    return false
                }
                return true
            })
            return cur < len(bins) && suggestedPeer == nil
        })
    }

    if uint8(saturationDepth) < k.depth {
        k.depth = uint8(saturationDepth)
        return suggestedPeer, saturationDepth, true
    }
    return suggestedPeer, 0, false
}

// On inserts the peer as a kademlia peer into the live peers
func (k *Kademlia) On(p *Peer) (uint8, bool) {
    k.lock.Lock()
    defer k.lock.Unlock()
    var ins bool
    k.conns, _, _, _ = pot.Swap(k.conns, p, Pof, func(v pot.Val) pot.Val {
        // if not found live
        if v == nil {
            ins = true
            // insert new online peer into conns
            return p
        }
        // found among live peers, do nothing
        return v
    })
    if ins && !p.BzzPeer.LightNode {
        a := newEntry(p.BzzAddr)
        a.conn = p
        // insert new online peer into addrs
        k.addrs, _, _, _ = pot.Swap(k.addrs, p, Pof, func(v pot.Val) pot.Val {
            return a
        })
        // send new address count value only if the peer is inserted
        if k.addrCountC != nil {
            k.addrCountC <- k.addrs.Size()
        }
    }
    log.Trace(k.string())
    // calculate if depth of saturation changed
    depth := uint8(k.saturation())
    var changed bool
    if depth != k.depth {
        changed = true
        k.depth = depth
    }
    k.sendNeighbourhoodDepthChange()
    return k.depth, changed
}

// NeighbourhoodDepthC returns the channel that sends a new kademlia
// neighbourhood depth on each change.
// Not receiving from the returned channel will block On function
// when the neighbourhood depth is changed.
// TODO: Why is this exported, and if it should be; why can't we have more subscribers than one?
func (k *Kademlia) NeighbourhoodDepthC() <-chan int {
    k.lock.Lock()
    defer k.lock.Unlock()
    if k.nDepthC == nil {
        k.nDepthC = make(chan int)
    }
    return k.nDepthC
}

// sendNeighbourhoodDepthChange sends new neighbourhood depth to k.nDepth channel
// if it is initialized.
func (k *Kademlia) sendNeighbourhoodDepthChange() {
    // nDepthC is initialized when NeighbourhoodDepthC is called and returned by it.
    // It provides signaling of neighbourhood depth change.
    // This part of the code is sending new neighbourhood depth to nDepthC if that condition is met.
    if k.nDepthC != nil {
        nDepth := depthForPot(k.conns, k.NeighbourhoodSize, k.base)
        if nDepth != k.nDepth {
            k.nDepth = nDepth
            k.nDepthC <- nDepth
        }
    }
}

// AddrCountC returns the channel that sends a new
// address count value on each change.
// Not receiving from the returned channel will block Register function
// when address count value changes.
func (k *Kademlia) AddrCountC() <-chan int {
    k.lock.Lock()
    defer k.lock.Unlock()

    if k.addrCountC == nil {
        k.addrCountC = make(chan int)
    }
    return k.addrCountC
}

// Off removes a peer from among live peers
func (k *Kademlia) Off(p *Peer) {
    k.lock.Lock()
    defer k.lock.Unlock()
    var del bool
    if !p.BzzPeer.LightNode {
        k.addrs, _, _, _ = pot.Swap(k.addrs, p, Pof, func(v pot.Val) pot.Val {
            // v cannot be nil, must check otherwise we overwrite entry
            if v == nil {
                panic(fmt.Sprintf("connected peer not found %v", p))
            }
            del = true
            return newEntry(p.BzzAddr)
        })
    } else {
        del = true
    }

    if del {
        k.conns, _, _, _ = pot.Swap(k.conns, p, Pof, func(_ pot.Val) pot.Val {
            // v cannot be nil, but no need to check
            return nil
        })
        // send new address count value only if the peer is deleted
        if k.addrCountC != nil {
            k.addrCountC <- k.addrs.Size()
        }
        k.sendNeighbourhoodDepthChange()
    }
}

// EachConn is an iterator with args (base, po, f) applies f to each live peer
// that has proximity order po or less as measured from the base
// if base is nil, kademlia base address is used
func (k *Kademlia) EachConn(base []byte, o int, f func(*Peer, int) bool) {
    k.lock.RLock()
    defer k.lock.RUnlock()
    k.eachConn(base, o, f)
}

func (k *Kademlia) eachConn(base []byte, o int, f func(*Peer, int) bool) {
    if len(base) == 0 {
        base = k.base
    }
    k.conns.EachNeighbour(base, Pof, func(val pot.Val, po int) bool {
        if po > o {
            return true
        }
        return f(val.(*Peer), po)
    })
}

// EachAddr called with (base, po, f) is an iterator applying f to each known peer
// that has proximity order o or less as measured from the base
// if base is nil, kademlia base address is used
func (k *Kademlia) EachAddr(base []byte, o int, f func(*BzzAddr, int) bool) {
    k.lock.RLock()
    defer k.lock.RUnlock()
    k.eachAddr(base, o, f)
}

func (k *Kademlia) eachAddr(base []byte, o int, f func(*BzzAddr, int) bool) {
    if len(base) == 0 {
        base = k.base
    }
    k.addrs.EachNeighbour(base, Pof, func(val pot.Val, po int) bool {
        if po > o {
            return true
        }
        return f(val.(*entry).BzzAddr, po)
    })
}

// NeighbourhoodDepth returns the depth for the pot, see depthForPot
func (k *Kademlia) NeighbourhoodDepth() (depth int) {
    k.lock.RLock()
    defer k.lock.RUnlock()
    return depthForPot(k.conns, k.NeighbourhoodSize, k.base)
}

// neighbourhoodRadiusForPot returns the neighbourhood radius of the kademlia
// neighbourhood radius encloses the nearest neighbour set with size >= neighbourhoodSize
// i.e., neighbourhood radius is the deepest PO such that all bins not shallower altogether
// contain at least neighbourhoodSize connected peers
// if there is altogether less than neighbourhoodSize peers connected, it returns 0
// caller must hold the lock
func neighbourhoodRadiusForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int) {
    if p.Size() <= neighbourhoodSize {
        return 0
    }
    // total number of peers in iteration
    var size int
    f := func(v pot.Val, i int) bool {
        // po == 256 means that addr is the pivot address(self)
        if i == 256 {
            return true
        }
        size++

        // this means we have all nn-peers.
        // depth is by default set to the bin of the farthest nn-peer
        if size == neighbourhoodSize {
            depth = i
            return false
        }

        return true
    }
    p.EachNeighbour(pivotAddr, Pof, f)
    return depth
}

// depthForPot returns the depth for the pot
// depth is the radius of the minimal extension of nearest neighbourhood that
// includes all empty PO bins. I.e., depth is the deepest PO such that
// - it is not deeper than neighbourhood radius
// - all bins shallower than depth are not empty
// caller must hold the lock
func depthForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int) {
    if p.Size() <= neighbourhoodSize {
        return 0
    }
    // determining the depth is a two-step process
    // first we find the proximity bin of the shallowest of the neighbourhoodSize peers
    // the numeric value of depth cannot be higher than this
    maxDepth := neighbourhoodRadiusForPot(p, neighbourhoodSize, pivotAddr)

    // the second step is to test for empty bins in order from shallowest to deepest
    // if an empty bin is found, this will be the actual depth
    // we stop iterating if we hit the maxDepth determined in the first step
    p.EachBin(pivotAddr, Pof, 0, func(po int, _ int, f func(func(pot.Val) bool) bool) bool {
        if po == depth {
            if maxDepth == depth {
                return false
            }
            depth++
            return true
        }
        return false
    })

    return depth
}

// callable decides if an address entry represents a callable peer
func (k *Kademlia) callable(e *entry) bool {
    // not callable if peer is live or exceeded maxRetries
    if e.conn != nil || e.retries > k.MaxRetries {
        return false
    }
    // calculate the allowed number of retries based on time lapsed since last seen
    timeAgo := int64(time.Since(e.seenAt))
    div := int64(k.RetryExponent)
    div += (150000 - rand.Int63n(300000)) * div / 1000000
    var retries int
    for delta := timeAgo; delta > k.RetryInterval; delta /= div {
        retries++
    }
    // this is never called concurrently, so safe to increment
    // peer can be retried again
    if retries < e.retries {
        log.Trace(fmt.Sprintf("%08x: %v long time since last try (at %v) needed before retry %v, wait only warrants %v", k.BaseAddr()[:4], e, timeAgo, e.retries, retries))
        return false
    }
    // function to sanction or prevent suggesting a peer
    if k.Reachable != nil && !k.Reachable(e.BzzAddr) {
        log.Trace(fmt.Sprintf("%08x: peer %v is temporarily not callable", k.BaseAddr()[:4], e))
        return false
    }
    e.retries++
    log.Trace(fmt.Sprintf("%08x: peer %v is callable", k.BaseAddr()[:4], e))

    return true
}

// BaseAddr return the kademlia base address
func (k *Kademlia) BaseAddr() []byte {
    return k.base
}

// String returns kademlia table + kaddb table displayed with ascii
func (k *Kademlia) String() string {
    k.lock.RLock()
    defer k.lock.RUnlock()
    return k.string()
}

// string returns kademlia table + kaddb table displayed with ascii
// caller must hold the lock
func (k *Kademlia) string() string {
    wsrow := "                          "
    var rows []string

    rows = append(rows, "=========================================================================")
    if len(sv.GitCommit) > 0 {
        rows = append(rows, fmt.Sprintf("commit hash: %s", sv.GitCommit))
    }
    rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %x", time.Now().UTC().Format(time.UnixDate), k.BaseAddr()[:3]))
    rows = append(rows, fmt.Sprintf("population: %d (%d), NeighbourhoodSize: %d, MinBinSize: %d, MaxBinSize: %d", k.conns.Size(), k.addrs.Size(), k.NeighbourhoodSize, k.MinBinSize, k.MaxBinSize))

    liverows := make([]string, k.MaxProxDisplay)
    peersrows := make([]string, k.MaxProxDisplay)

    depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base)
    rest := k.conns.Size()
    k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
        var rowlen int
        if po >= k.MaxProxDisplay {
            po = k.MaxProxDisplay - 1
        }
        row := []string{fmt.Sprintf("%2d", size)}
        rest -= size
        f(func(val pot.Val) bool {
            e := val.(*Peer)
            row = append(row, fmt.Sprintf("%x", e.Address()[:2]))
            rowlen++
            return rowlen < 4
        })
        r := strings.Join(row, " ")
        r = r + wsrow
        liverows[po] = r[:31]
        return true
    })

    k.addrs.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
        var rowlen int
        if po >= k.MaxProxDisplay {
            po = k.MaxProxDisplay - 1
        }
        if size < 0 {
            panic("wtf")
        }
        row := []string{fmt.Sprintf("%2d", size)}
        // we are displaying live peers too
        f(func(val pot.Val) bool {
            e := val.(*entry)
            row = append(row, Label(e))
            rowlen++
            return rowlen < 4
        })
        peersrows[po] = strings.Join(row, " ")
        return true
    })

    for i := 0; i < k.MaxProxDisplay; i++ {
        if i == depth {
            rows = append(rows, fmt.Sprintf("============ DEPTH: %d ==========================================", i))
        }
        left := liverows[i]
        right := peersrows[i]
        if len(left) == 0 {
            left = " 0                             "
        }
        if len(right) == 0 {
            right = " 0"
        }
        rows = append(rows, fmt.Sprintf("%03d %v | %v", i, left, right))
    }
    rows = append(rows, "=========================================================================")
    return "\n" + strings.Join(rows, "\n")
}

// PeerPot keeps info about expected nearest neighbours
// used for testing only
// TODO move to separate testing tools file
type PeerPot struct {
    NNSet       [][]byte
    PeersPerBin []int
}

// NewPeerPotMap creates a map of pot record of *BzzAddr with keys
// as hexadecimal representations of the address.
// the NeighbourhoodSize of the passed kademlia is used
// used for testing only
// TODO move to separate testing tools file
func NewPeerPotMap(neighbourhoodSize int, addrs [][]byte) map[string]*PeerPot {

    // create a table of all nodes for health check
    np := pot.NewPot(nil, 0)
    for _, addr := range addrs {
        np, _, _ = pot.Add(np, addr, Pof)
    }
    ppmap := make(map[string]*PeerPot)

    // generate an allknowing source of truth for connections
    // for every kademlia passed
    for i, a := range addrs {

        // actual kademlia depth
        depth := depthForPot(np, neighbourhoodSize, a)

        // all nn-peers
        var nns [][]byte
        peersPerBin := make([]int, depth)

        // iterate through the neighbours, going from the deepest to the shallowest
        np.EachNeighbour(a, Pof, func(val pot.Val, po int) bool {
            addr := val.([]byte)
            // po == 256 means that addr is the pivot address(self)
            // we do not include self in the map
            if po == 256 {
                return true
            }
            // append any neighbors found
            // a neighbor is any peer in or deeper than the depth
            if po >= depth {
                nns = append(nns, addr)
            } else {
                // for peers < depth, we just count the number in each bin
                // the bin is the index of the slice
                peersPerBin[po]++
            }
            return true
        })

        log.Trace(fmt.Sprintf("%x PeerPotMap NNS: %s, peersPerBin", addrs[i][:4], LogAddrs(nns)))
        ppmap[common.Bytes2Hex(a)] = &PeerPot{
            NNSet:       nns,
            PeersPerBin: peersPerBin,
        }
    }
    return ppmap
}

// saturation returns the smallest po value in which the node has less than MinBinSize peers
// if the iterator reaches neighbourhood radius, then the last bin + 1 is returned
func (k *Kademlia) saturation() int {
    prev := -1
    radius := neighbourhoodRadiusForPot(k.conns, k.NeighbourhoodSize, k.base)
    k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {
        prev++
        if po >= radius {
            return false
        }
        return prev == po && size >= k.MinBinSize
    })
    if prev < 0 {
        return 0
    }
    return prev
}

// isSaturated returns true if the kademlia is considered saturated, or false if not.
// It checks this by checking an array of ints called unsaturatedBins; each item in that array corresponds
// to the bin which is unsaturated (number of connections < k.MinBinSize).
// The bin is considered unsaturated only if there are actual peers in that PeerPot's bin (peersPerBin)
// (if there is no peer for a given bin, then no connection could ever be established;
// in a God's view this is relevant as no more peers will ever appear on that bin)
func (k *Kademlia) isSaturated(peersPerBin []int, depth int) bool {
    // depth could be calculated from k but as this is called from `GetHealthInfo()`,
    // the depth has already been calculated so we can require it as a parameter

    // early check for depth
    if depth != len(peersPerBin) {
        return false
    }
    unsaturatedBins := make([]int, 0)
    k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool {

        if po >= depth {
            return false
        }
        log.Trace("peers per bin", "peersPerBin[po]", peersPerBin[po], "po", po)
        // if there are actually peers in the PeerPot who can fulfill k.MinBinSize
        if size < k.MinBinSize && size < peersPerBin[po] {
            log.Trace("connections for po", "po", po, "size", size)
            unsaturatedBins = append(unsaturatedBins, po)
        }
        return true
    })

    log.Trace("list of unsaturated bins", "unsaturatedBins", unsaturatedBins)
    return len(unsaturatedBins) == 0
}

// knowNeighbours tests if all neighbours in the peerpot
// are found among the peers known to the kademlia
// It is used in Healthy function for testing only
// TODO move to separate testing tools file
func (k *Kademlia) knowNeighbours(addrs [][]byte) (got bool, n int, missing [][]byte) {
    pm := make(map[string]bool)
    depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base)
    // create a map with all peers at depth and deeper known in the kademlia
    k.eachAddr(nil, 255, func(p *BzzAddr, po int) bool {
        // in order deepest to shallowest compared to the kademlia base address
        // all bins (except self) are included (0 <= bin <= 255)
        if po < depth {
            return false
        }
        pk := common.Bytes2Hex(p.Address())
        pm[pk] = true
        return true
    })

    // iterate through nearest neighbors in the peerpot map
    // if we can't find the neighbor in the map we created above
    // then we don't know all our neighbors
    // (which sadly is all too common in modern society)
    var gots int
    var culprits [][]byte
    for _, p := range addrs {
        pk := common.Bytes2Hex(p)
        if pm[pk] {
            gots++
        } else {
            log.Trace(fmt.Sprintf("%08x: known nearest neighbour %s not found", k.base, pk))
            culprits = append(culprits, p)
        }
    }
    return gots == len(addrs), gots, culprits
}

// connectedNeighbours tests if all neighbours in the peerpot
// are currently connected in the kademlia
// It is used in Healthy function for testing only
func (k *Kademlia) connectedNeighbours(peers [][]byte) (got bool, n int, missing [][]byte) {
    pm := make(map[string]bool)

    // create a map with all peers at depth and deeper that are connected in the kademlia
    // in order deepest to shallowest compared to the kademlia base address
    // all bins (except self) are included (0 <= bin <= 255)
    depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base)
    k.eachConn(nil, 255, func(p *Peer, po int) bool {
        if po < depth {
            return false
        }
        pk := common.Bytes2Hex(p.Address())
        pm[pk] = true
        return true
    })

    // iterate through nearest neighbors in the peerpot map
    // if we can't find the neighbor in the map we created above
    // then we don't know all our neighbors
    var gots int
    var culprits [][]byte
    for _, p := range peers {
        pk := common.Bytes2Hex(p)
        if pm[pk] {
            gots++
        } else {
            log.Trace(fmt.Sprintf("%08x: ExpNN: %s not found", k.base, pk))
            culprits = append(culprits, p)
        }
    }
    return gots == len(peers), gots, culprits
}

// Health state of the Kademlia
// used for testing only
type Health struct {
    KnowNN           bool     // whether node knows all its neighbours
    CountKnowNN      int      // amount of neighbors known
    MissingKnowNN    [][]byte // which neighbours we should have known but we don't
    ConnectNN        bool     // whether node is connected to all its neighbours
    CountConnectNN   int      // amount of neighbours connected to
    MissingConnectNN [][]byte // which neighbours we should have been connected to but we're not
    // Saturated: if in all bins < depth number of connections >= MinBinsize or,
    // if number of connections < MinBinSize, to the number of available peers in that bin
    Saturated bool
    Hive      string
}

// GetHealthInfo reports the health state of the kademlia connectivity
//
// The PeerPot argument provides an all-knowing view of the network
// The resulting Health object is a result of comparisons between
// what is the actual composition of the kademlia in question (the receiver), and
// what SHOULD it have been when we take all we know about the network into consideration.
//
// used for testing only
func (k *Kademlia) GetHealthInfo(pp *PeerPot) *Health {
    k.lock.RLock()
    defer k.lock.RUnlock()
    if len(pp.NNSet) < k.NeighbourhoodSize {
        log.Warn("peerpot NNSet < NeighbourhoodSize")
    }
    gotnn, countgotnn, culpritsgotnn := k.connectedNeighbours(pp.NNSet)
    knownn, countknownn, culpritsknownn := k.knowNeighbours(pp.NNSet)
    depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base)

    // check saturation
    saturated := k.isSaturated(pp.PeersPerBin, depth)

    log.Trace(fmt.Sprintf("%08x: healthy: knowNNs: %v, gotNNs: %v, saturated: %v\n", k.base, knownn, gotnn, saturated))
    return &Health{
        KnowNN:           knownn,
        CountKnowNN:      countknownn,
        MissingKnowNN:    culpritsknownn,
        ConnectNN:        gotnn,
        CountConnectNN:   countgotnn,
        MissingConnectNN: culpritsgotnn,
        Saturated:        saturated,
        Hive:             k.string(),
    }
}

// Healthy return the strict interpretation of `Healthy` given a `Health` struct
// definition of strict health: all conditions must be true:
// - we at least know one peer
// - we know all neighbors
// - we are connected to all known neighbors
// - it is saturated
func (h *Health) Healthy() bool {
    return h.KnowNN && h.ConnectNN && h.CountKnowNN > 0 && h.Saturated
}