github.com/janotchain/janota@v0.0.0-20220824112012-93ea4c5dee78/swarm/network/kademlia/kademlia.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package kademlia
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/ethereum/go-ethereum/log"
    27  )
    28  
    29  const (
    30  	bucketSize   = 4
    31  	proxBinSize  = 2
    32  	maxProx      = 8
    33  	connRetryExp = 2
    34  	maxPeers     = 100
    35  )
    36  
    37  var (
    38  	purgeInterval        = 42 * time.Hour
    39  	initialRetryInterval = 42 * time.Millisecond
    40  	maxIdleInterval      = 42 * 1000 * time.Millisecond
    41  	// maxIdleInterval      = 42 * 10	0 * time.Millisecond
    42  )
    43  
    44  type KadParams struct {
    45  	// adjustable parameters
    46  	MaxProx              int
    47  	ProxBinSize          int
    48  	BucketSize           int
    49  	PurgeInterval        time.Duration
    50  	InitialRetryInterval time.Duration
    51  	MaxIdleInterval      time.Duration
    52  	ConnRetryExp         int
    53  }
    54  
    55  func NewKadParams() *KadParams {
    56  	return &KadParams{
    57  		MaxProx:              maxProx,
    58  		ProxBinSize:          proxBinSize,
    59  		BucketSize:           bucketSize,
    60  		PurgeInterval:        purgeInterval,
    61  		InitialRetryInterval: initialRetryInterval,
    62  		MaxIdleInterval:      maxIdleInterval,
    63  		ConnRetryExp:         connRetryExp,
    64  	}
    65  }
    66  
    67  // Kademlia is a table of active nodes
    68  type Kademlia struct {
    69  	addr       Address      // immutable baseaddress of the table
    70  	*KadParams              // Kademlia configuration parameters
    71  	proxLimit  int          // state, the PO of the first row of the most proximate bin
    72  	proxSize   int          // state, the number of peers in the most proximate bin
    73  	count      int          // number of active peers (w live connection)
    74  	buckets    [][]Node     // the actual bins
    75  	db         *KadDb       // kaddb, node record database
    76  	lock       sync.RWMutex // mutex to access buckets
    77  }
    78  
    79  type Node interface {
    80  	Addr() Address
    81  	Url() string
    82  	LastActive() time.Time
    83  	Drop()
    84  }
    85  
    86  // public constructor
    87  // add is the base address of the table
    88  // params is KadParams configuration
    89  func New(addr Address, params *KadParams) *Kademlia {
    90  	buckets := make([][]Node, params.MaxProx+1)
    91  	return &Kademlia{
    92  		addr:      addr,
    93  		KadParams: params,
    94  		buckets:   buckets,
    95  		db:        newKadDb(addr, params),
    96  	}
    97  }
    98  
    99  // accessor for KAD base address
   100  func (self *Kademlia) Addr() Address {
   101  	return self.addr
   102  }
   103  
   104  // accessor for KAD active node count
   105  func (self *Kademlia) Count() int {
   106  	defer self.lock.Unlock()
   107  	self.lock.Lock()
   108  	return self.count
   109  }
   110  
   111  // accessor for KAD active node count
   112  func (self *Kademlia) DBCount() int {
   113  	return self.db.count()
   114  }
   115  
   116  // On is the entry point called when a new nodes is added
   117  // unsafe in that node is not checked to be already active node (to be called once)
   118  func (self *Kademlia) On(node Node, cb func(*NodeRecord, Node) error) (err error) {
   119  	log.Debug(fmt.Sprintf("%v", self))
   120  	defer self.lock.Unlock()
   121  	self.lock.Lock()
   122  
   123  	index := self.proximityBin(node.Addr())
   124  	record := self.db.findOrCreate(index, node.Addr(), node.Url())
   125  
   126  	if cb != nil {
   127  		err = cb(record, node)
   128  		log.Trace(fmt.Sprintf("cb(%v, %v) ->%v", record, node, err))
   129  		if err != nil {
   130  			return fmt.Errorf("unable to add node %v, callback error: %v", node.Addr(), err)
   131  		}
   132  		log.Debug(fmt.Sprintf("add node record %v with node %v", record, node))
   133  	}
   134  
   135  	// insert in kademlia table of active nodes
   136  	bucket := self.buckets[index]
   137  	// if bucket is full insertion replaces the worst node
   138  	// TODO: give priority to peers with active traffic
   139  	if len(bucket) < self.BucketSize { // >= allows us to add peers beyond the bucketsize limitation
   140  		self.buckets[index] = append(bucket, node)
   141  		log.Debug(fmt.Sprintf("add node %v to table", node))
   142  		self.setProxLimit(index, true)
   143  		record.node = node
   144  		self.count++
   145  		return nil
   146  	}
   147  
   148  	// always rotate peers
   149  	idle := self.MaxIdleInterval
   150  	var pos int
   151  	var replaced Node
   152  	for i, p := range bucket {
   153  		idleInt := time.Since(p.LastActive())
   154  		if idleInt > idle {
   155  			idle = idleInt
   156  			pos = i
   157  			replaced = p
   158  		}
   159  	}
   160  	if replaced == nil {
   161  		log.Debug(fmt.Sprintf("all peers wanted, PO%03d bucket full", index))
   162  		return fmt.Errorf("bucket full")
   163  	}
   164  	log.Debug(fmt.Sprintf("node %v replaced by %v (idle for %v  > %v)", replaced, node, idle, self.MaxIdleInterval))
   165  	replaced.Drop()
   166  	// actually replace in the row. When off(node) is called, the peer is no longer in the row
   167  	bucket[pos] = node
   168  	// there is no change in bucket cardinalities so no prox limit adjustment is needed
   169  	record.node = node
   170  	self.count++
   171  	return nil
   172  
   173  }
   174  
   175  // Off is the called when a node is taken offline (from the protocol main loop exit)
   176  func (self *Kademlia) Off(node Node, cb func(*NodeRecord, Node)) (err error) {
   177  	self.lock.Lock()
   178  	defer self.lock.Unlock()
   179  
   180  	index := self.proximityBin(node.Addr())
   181  	bucket := self.buckets[index]
   182  	for i := 0; i < len(bucket); i++ {
   183  		if node.Addr() == bucket[i].Addr() {
   184  			self.buckets[index] = append(bucket[:i], bucket[(i+1):]...)
   185  			self.setProxLimit(index, false)
   186  			break
   187  		}
   188  	}
   189  
   190  	record := self.db.index[node.Addr()]
   191  	// callback on remove
   192  	if cb != nil {
   193  		cb(record, record.node)
   194  	}
   195  	record.node = nil
   196  	self.count--
   197  	log.Debug(fmt.Sprintf("remove node %v from table, population now is %v", node, self.count))
   198  
   199  	return
   200  }
   201  
   202  // proxLimit is dynamically adjusted so that
   203  // 1) there is no empty buckets in bin < proxLimit and
   204  // 2) the sum of all items are the minimum possible but higher than ProxBinSize
   205  // adjust Prox (proxLimit and proxSize after an insertion/removal of nodes)
   206  // caller holds the lock
   207  func (self *Kademlia) setProxLimit(r int, on bool) {
   208  	// if the change is outside the core (PO lower)
   209  	// and the change does not leave a bucket empty then
   210  	// no adjustment needed
   211  	if r < self.proxLimit && len(self.buckets[r]) > 0 {
   212  		return
   213  	}
   214  	// if on=a node was added, then r must be within prox limit so increment cardinality
   215  	if on {
   216  		self.proxSize++
   217  		curr := len(self.buckets[self.proxLimit])
   218  		// if now core is big enough without the furthest bucket, then contract
   219  		// this can result in more than one bucket change
   220  		for self.proxSize >= self.ProxBinSize+curr && curr > 0 {
   221  			self.proxSize -= curr
   222  			self.proxLimit++
   223  			curr = len(self.buckets[self.proxLimit])
   224  
   225  			log.Trace(fmt.Sprintf("proxbin contraction (size: %v, limit: %v, bin: %v)", self.proxSize, self.proxLimit, r))
   226  		}
   227  		return
   228  	}
   229  	// otherwise
   230  	if r >= self.proxLimit {
   231  		self.proxSize--
   232  	}
   233  	// expand core by lowering prox limit until hit zero or cover the empty bucket or reached target cardinality
   234  	for (self.proxSize < self.ProxBinSize || r < self.proxLimit) &&
   235  		self.proxLimit > 0 {
   236  		//
   237  		self.proxLimit--
   238  		self.proxSize += len(self.buckets[self.proxLimit])
   239  		log.Trace(fmt.Sprintf("proxbin expansion (size: %v, limit: %v, bin: %v)", self.proxSize, self.proxLimit, r))
   240  	}
   241  }
   242  
   243  /*
   244  returns the list of nodes belonging to the same proximity bin
   245  as the target. The most proximate bin will be the union of the bins between
   246  proxLimit and MaxProx.
   247  */
   248  func (self *Kademlia) FindClosest(target Address, max int) []Node {
   249  	self.lock.Lock()
   250  	defer self.lock.Unlock()
   251  
   252  	r := nodesByDistance{
   253  		target: target,
   254  	}
   255  
   256  	po := self.proximityBin(target)
   257  	index := po
   258  	step := 1
   259  	log.Trace(fmt.Sprintf("serving %v nodes at %v (PO%02d)", max, index, po))
   260  
   261  	// if max is set to 0, just want a full bucket, dynamic number
   262  	min := max
   263  	// set limit to max
   264  	limit := max
   265  	if max == 0 {
   266  		min = 1
   267  		limit = maxPeers
   268  	}
   269  
   270  	var n int
   271  	for index >= 0 {
   272  		// add entire bucket
   273  		for _, p := range self.buckets[index] {
   274  			r.push(p, limit)
   275  			n++
   276  		}
   277  		// terminate if index reached the bottom or enough peers > min
   278  		log.Trace(fmt.Sprintf("add %v -> %v (PO%02d, PO%03d)", len(self.buckets[index]), n, index, po))
   279  		if n >= min && (step < 0 || max == 0) {
   280  			break
   281  		}
   282  		// reach top most non-empty PO bucket, turn around
   283  		if index == self.MaxProx {
   284  			index = po
   285  			step = -1
   286  		}
   287  		index += step
   288  	}
   289  	log.Trace(fmt.Sprintf("serve %d (<=%d) nodes for target lookup %v (PO%03d)", n, max, target, po))
   290  	return r.nodes
   291  }
   292  
   293  func (self *Kademlia) Suggest() (*NodeRecord, bool, int) {
   294  	defer self.lock.RUnlock()
   295  	self.lock.RLock()
   296  	return self.db.findBest(self.BucketSize, func(i int) int { return len(self.buckets[i]) })
   297  }
   298  
   299  //  adds node records to kaddb (persisted node record db)
   300  func (self *Kademlia) Add(nrs []*NodeRecord) {
   301  	self.db.add(nrs, self.proximityBin)
   302  }
   303  
   304  // nodesByDistance is a list of nodes, ordered by distance to target.
   305  type nodesByDistance struct {
   306  	nodes  []Node
   307  	target Address
   308  }
   309  
   310  func sortedByDistanceTo(target Address, slice []Node) bool {
   311  	var last Address
   312  	for i, node := range slice {
   313  		if i > 0 {
   314  			if target.ProxCmp(node.Addr(), last) < 0 {
   315  				return false
   316  			}
   317  		}
   318  		last = node.Addr()
   319  	}
   320  	return true
   321  }
   322  
   323  // push(node, max) adds the given node to the list, keeping the total size
   324  // below max elements.
   325  func (h *nodesByDistance) push(node Node, max int) {
   326  	// returns the firt index ix such that func(i) returns true
   327  	ix := sort.Search(len(h.nodes), func(i int) bool {
   328  		return h.target.ProxCmp(h.nodes[i].Addr(), node.Addr()) >= 0
   329  	})
   330  
   331  	if len(h.nodes) < max {
   332  		h.nodes = append(h.nodes, node)
   333  	}
   334  	if ix < len(h.nodes) {
   335  		copy(h.nodes[ix+1:], h.nodes[ix:])
   336  		h.nodes[ix] = node
   337  	}
   338  }
   339  
   340  /*
   341  Taking the proximity order relative to a fix point x classifies the points in
   342  the space (n byte long byte sequences) into bins. Items in each are at
   343  most half as distant from x as items in the previous bin. Given a sample of
   344  uniformly distributed items (a hash function over arbitrary sequence) the
   345  proximity scale maps onto series of subsets with cardinalities on a negative
   346  exponential scale.
   347  
   348  It also has the property that any two item belonging to the same bin are at
   349  most half as distant from each other as they are from x.
   350  
   351  If we think of random sample of items in the bins as connections in a network of interconnected nodes than relative proximity can serve as the basis for local
   352  decisions for graph traversal where the task is to find a route between two
   353  points. Since in every hop, the finite distance halves, there is
   354  a guaranteed constant maximum limit on the number of hops needed to reach one
   355  node from the other.
   356  */
   357  
   358  func (self *Kademlia) proximityBin(other Address) (ret int) {
   359  	ret = proximity(self.addr, other)
   360  	if ret > self.MaxProx {
   361  		ret = self.MaxProx
   362  	}
   363  	return
   364  }
   365  
   366  // provides keyrange for chunk db iteration
   367  func (self *Kademlia) KeyRange(other Address) (start, stop Address) {
   368  	defer self.lock.RUnlock()
   369  	self.lock.RLock()
   370  	return KeyRange(self.addr, other, self.proxLimit)
   371  }
   372  
   373  // save persists kaddb on disk (written to file on path in json format.
   374  func (self *Kademlia) Save(path string, cb func(*NodeRecord, Node)) error {
   375  	return self.db.save(path, cb)
   376  }
   377  
   378  // Load(path) loads the node record database (kaddb) from file on path.
   379  func (self *Kademlia) Load(path string, cb func(*NodeRecord, Node) error) (err error) {
   380  	return self.db.load(path, cb)
   381  }
   382  
   383  // kademlia table + kaddb table displayed with ascii
   384  func (self *Kademlia) String() string {
   385  	defer self.lock.RUnlock()
   386  	self.lock.RLock()
   387  	defer self.db.lock.RUnlock()
   388  	self.db.lock.RLock()
   389  
   390  	var rows []string
   391  	rows = append(rows, "=========================================================================")
   392  	rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %v", time.Now().UTC().Format(time.UnixDate), self.addr.String()[:6]))
   393  	rows = append(rows, fmt.Sprintf("population: %d (%d), proxLimit: %d, proxSize: %d", self.count, len(self.db.index), self.proxLimit, self.proxSize))
   394  	rows = append(rows, fmt.Sprintf("MaxProx: %d, ProxBinSize: %d, BucketSize: %d", self.MaxProx, self.ProxBinSize, self.BucketSize))
   395  
   396  	for i, bucket := range self.buckets {
   397  
   398  		if i == self.proxLimit {
   399  			rows = append(rows, fmt.Sprintf("============ PROX LIMIT: %d ==========================================", i))
   400  		}
   401  		row := []string{fmt.Sprintf("%03d", i), fmt.Sprintf("%2d", len(bucket))}
   402  		var k int
   403  		c := self.db.cursors[i]
   404  		for ; k < len(bucket); k++ {
   405  			p := bucket[(c+k)%len(bucket)]
   406  			row = append(row, p.Addr().String()[:6])
   407  			if k == 4 {
   408  				break
   409  			}
   410  		}
   411  		for ; k < 4; k++ {
   412  			row = append(row, "      ")
   413  		}
   414  		row = append(row, fmt.Sprintf("| %2d %2d", len(self.db.Nodes[i]), self.db.cursors[i]))
   415  
   416  		for j, p := range self.db.Nodes[i] {
   417  			row = append(row, p.Addr.String()[:6])
   418  			if j == 3 {
   419  				break
   420  			}
   421  		}
   422  		rows = append(rows, strings.Join(row, " "))
   423  		if i == self.MaxProx {
   424  		}
   425  	}
   426  	rows = append(rows, "=========================================================================")
   427  	return strings.Join(rows, "\n")
   428  }