github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/network/kademlia/kaddb.go (about)

     1  // Copyright 2016 The Spectrum Authors
     2  // This file is part of the Spectrum library.
     3  //
     4  // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package kademlia
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/SmartMeshFoundation/Spectrum/log"
    28  )
    29  
    30  type NodeData interface {
    31  	json.Marshaler
    32  	json.Unmarshaler
    33  }
    34  
    35  // allow inactive peers under
    36  type NodeRecord struct {
    37  	Addr  Address          // address of node
    38  	Url   string           // Url, used to connect to node
    39  	After time.Time        // next call after time
    40  	Seen  time.Time        // last connected at time
    41  	Meta  *json.RawMessage // arbitrary metadata saved for a peer
    42  
    43  	node Node
    44  }
    45  
    46  func (self *NodeRecord) setSeen() {
    47  	t := time.Now()
    48  	self.Seen = t
    49  	self.After = t
    50  }
    51  
    52  func (self *NodeRecord) String() string {
    53  	return fmt.Sprintf("<%v>", self.Addr)
    54  }
    55  
    56  // persisted node record database ()
    57  type KadDb struct {
    58  	Address              Address
    59  	Nodes                [][]*NodeRecord
    60  	index                map[Address]*NodeRecord
    61  	cursors              []int
    62  	lock                 sync.RWMutex
    63  	purgeInterval        time.Duration
    64  	initialRetryInterval time.Duration
    65  	connRetryExp         int
    66  }
    67  
    68  func newKadDb(addr Address, params *KadParams) *KadDb {
    69  	return &KadDb{
    70  		Address:              addr,
    71  		Nodes:                make([][]*NodeRecord, params.MaxProx+1), // overwritten by load
    72  		cursors:              make([]int, params.MaxProx+1),
    73  		index:                make(map[Address]*NodeRecord),
    74  		purgeInterval:        params.PurgeInterval,
    75  		initialRetryInterval: params.InitialRetryInterval,
    76  		connRetryExp:         params.ConnRetryExp,
    77  	}
    78  }
    79  
    80  func (self *KadDb) findOrCreate(index int, a Address, url string) *NodeRecord {
    81  	defer self.lock.Unlock()
    82  	self.lock.Lock()
    83  
    84  	record, found := self.index[a]
    85  	if !found {
    86  		record = &NodeRecord{
    87  			Addr: a,
    88  			Url:  url,
    89  		}
    90  		log.Info(fmt.Sprintf("add new record %v to kaddb", record))
    91  		// insert in kaddb
    92  		self.index[a] = record
    93  		self.Nodes[index] = append(self.Nodes[index], record)
    94  	} else {
    95  		log.Info(fmt.Sprintf("found record %v in kaddb", record))
    96  	}
    97  	// update last seen time
    98  	record.setSeen()
    99  	// update with url in case IP/port changes
   100  	record.Url = url
   101  	return record
   102  }
   103  
   104  // add adds node records to kaddb (persisted node record db)
   105  func (self *KadDb) add(nrs []*NodeRecord, proximityBin func(Address) int) {
   106  	defer self.lock.Unlock()
   107  	self.lock.Lock()
   108  	var n int
   109  	var nodes []*NodeRecord
   110  	for _, node := range nrs {
   111  		_, found := self.index[node.Addr]
   112  		if !found && node.Addr != self.Address {
   113  			node.setSeen()
   114  			self.index[node.Addr] = node
   115  			index := proximityBin(node.Addr)
   116  			dbcursor := self.cursors[index]
   117  			nodes = self.Nodes[index]
   118  			// this is inefficient for allocation, need to just append then shift
   119  			newnodes := make([]*NodeRecord, len(nodes)+1)
   120  			copy(newnodes[:], nodes[:dbcursor])
   121  			newnodes[dbcursor] = node
   122  			copy(newnodes[dbcursor+1:], nodes[dbcursor:])
   123  			log.Trace(fmt.Sprintf("new nodes: %v, nodes: %v", newnodes, nodes))
   124  			self.Nodes[index] = newnodes
   125  			n++
   126  		}
   127  	}
   128  	if n > 0 {
   129  		log.Debug(fmt.Sprintf("%d/%d node records (new/known)", n, len(nrs)))
   130  	}
   131  }
   132  
   133  /*
   134  next return one node record with the highest priority for desired
   135  connection.
   136  This is used to pick candidates for live nodes that are most wanted for
   137  a higly connected low centrality network structure for Swarm which best suits
   138  for a Kademlia-style routing.
   139  
   140  * Starting as naive node with empty db, this implements Kademlia bootstrapping
   141  * As a mature node, it fills short lines. All on demand.
   142  
   143  The candidate is chosen using the following strategy:
   144  We check for missing online nodes in the buckets for 1 upto Max BucketSize rounds.
   145  On each round we proceed from the low to high proximity order buckets.
   146  If the number of active nodes (=connected peers) is < rounds, then start looking
   147  for a known candidate. To determine if there is a candidate to recommend the
   148  kaddb node record database row corresponding to the bucket is checked.
   149  
   150  If the row cursor is on position i, the ith element in the row is chosen.
   151  If the record is scheduled not to be retried before NOW, the next element is taken.
   152  If the record is scheduled to be retried, it is set as checked, scheduled for
   153  checking and is returned. The time of the next check is in X (duration) such that
   154  X = ConnRetryExp * delta where delta is the time past since the last check and
   155  ConnRetryExp is constant obsoletion factor. (Note that when node records are added
   156  from peer messages, they are marked as checked and placed at the cursor, ie.
   157  given priority over older entries). Entries which were checked more than
   158  purgeInterval ago are deleted from the kaddb row. If no candidate is found after
   159  a full round of checking the next bucket up is considered. If no candidate is
   160  found when we reach the maximum-proximity bucket, the next round starts.
   161  
   162  node record a is more favoured to b a > b iff a is a passive node (record of
   163  offline past peer)
   164  |proxBin(a)| < |proxBin(b)|
   165  || (proxBin(a) < proxBin(b) && |proxBin(a)| == |proxBin(b)|)
   166  || (proxBin(a) == proxBin(b) && lastChecked(a) < lastChecked(b))
   167  
   168  
   169  The second argument returned names the first missing slot found
   170  */
   171  func (self *KadDb) findBest(maxBinSize int, binSize func(int) int) (node *NodeRecord, need bool, proxLimit int) {
   172  	// return nil, proxLimit indicates that all buckets are filled
   173  	defer self.lock.Unlock()
   174  	self.lock.Lock()
   175  
   176  	var interval time.Duration
   177  	var found bool
   178  	var purge []bool
   179  	var delta time.Duration
   180  	var cursor int
   181  	var count int
   182  	var after time.Time
   183  
   184  	// iterate over columns maximum bucketsize times
   185  	for rounds := 1; rounds <= maxBinSize; rounds++ {
   186  	ROUND:
   187  		// iterate over rows from PO 0 upto MaxProx
   188  		for po, dbrow := range self.Nodes {
   189  			// if row has rounds connected peers, then take the next
   190  			if binSize(po) >= rounds {
   191  				continue ROUND
   192  			}
   193  			if !need {
   194  				// set proxlimit to the PO where the first missing slot is found
   195  				proxLimit = po
   196  				need = true
   197  			}
   198  			purge = make([]bool, len(dbrow))
   199  
   200  			// there is a missing slot - finding a node to connect to
   201  			// select a node record from the relavant kaddb row (of identical prox order)
   202  		ROW:
   203  			for cursor = self.cursors[po]; !found && count < len(dbrow); cursor = (cursor + 1) % len(dbrow) {
   204  				count++
   205  				node = dbrow[cursor]
   206  
   207  				// skip already connected nodes
   208  				if node.node != nil {
   209  					log.Debug(fmt.Sprintf("kaddb record %v (PO%03d:%d/%d) already connected", node.Addr, po, cursor, len(dbrow)))
   210  					continue ROW
   211  				}
   212  
   213  				// if node is scheduled to connect
   214  				if node.After.After(time.Now()) {
   215  					log.Debug(fmt.Sprintf("kaddb record %v (PO%03d:%d) skipped. seen at %v (%v ago), scheduled at %v", node.Addr, po, cursor, node.Seen, delta, node.After))
   216  					continue ROW
   217  				}
   218  
   219  				delta = time.Since(node.Seen)
   220  				if delta < self.initialRetryInterval {
   221  					delta = self.initialRetryInterval
   222  				}
   223  				if delta > self.purgeInterval {
   224  					// remove node
   225  					purge[cursor] = true
   226  					log.Debug(fmt.Sprintf("kaddb record %v (PO%03d:%d) unreachable since %v. Removed", node.Addr, po, cursor, node.Seen))
   227  					continue ROW
   228  				}
   229  
   230  				log.Debug(fmt.Sprintf("kaddb record %v (PO%03d:%d) ready to be tried. seen at %v (%v ago), scheduled at %v", node.Addr, po, cursor, node.Seen, delta, node.After))
   231  
   232  				// scheduling next check
   233  				interval = delta * time.Duration(self.connRetryExp)
   234  				after = time.Now().Add(interval)
   235  
   236  				log.Debug(fmt.Sprintf("kaddb record %v (PO%03d:%d) selected as candidate connection %v. seen at %v (%v ago), selectable since %v, retry after %v (in %v)", node.Addr, po, cursor, rounds, node.Seen, delta, node.After, after, interval))
   237  				node.After = after
   238  				found = true
   239  			} // ROW
   240  			self.cursors[po] = cursor
   241  			self.delete(po, purge)
   242  			if found {
   243  				return node, need, proxLimit
   244  			}
   245  		} // ROUND
   246  	} // ROUNDS
   247  
   248  	return nil, need, proxLimit
   249  }
   250  
   251  // deletes the noderecords of a kaddb row corresponding to the indexes
   252  // caller must hold the dblock
   253  // the call is unsafe, no index checks
   254  func (self *KadDb) delete(row int, purge []bool) {
   255  	var nodes []*NodeRecord
   256  	dbrow := self.Nodes[row]
   257  	for i, del := range purge {
   258  		if i == self.cursors[row] {
   259  			//reset cursor
   260  			self.cursors[row] = len(nodes)
   261  		}
   262  		// delete the entry to be purged
   263  		if del {
   264  			delete(self.index, dbrow[i].Addr)
   265  			continue
   266  		}
   267  		// otherwise append to new list
   268  		nodes = append(nodes, dbrow[i])
   269  	}
   270  	self.Nodes[row] = nodes
   271  }
   272  
   273  // save persists kaddb on disk (written to file on path in json format.
   274  func (self *KadDb) save(path string, cb func(*NodeRecord, Node)) error {
   275  	defer self.lock.Unlock()
   276  	self.lock.Lock()
   277  
   278  	var n int
   279  
   280  	for _, b := range self.Nodes {
   281  		for _, node := range b {
   282  			n++
   283  			node.After = time.Now()
   284  			node.Seen = time.Now()
   285  			if cb != nil {
   286  				cb(node, node.node)
   287  			}
   288  		}
   289  	}
   290  
   291  	data, err := json.MarshalIndent(self, "", " ")
   292  	if err != nil {
   293  		return err
   294  	}
   295  	err = ioutil.WriteFile(path, data, os.ModePerm)
   296  	if err != nil {
   297  		log.Warn(fmt.Sprintf("unable to save kaddb with %v nodes to %v: %v", n, path, err))
   298  	} else {
   299  		log.Info(fmt.Sprintf("saved kaddb with %v nodes to %v", n, path))
   300  	}
   301  	return err
   302  }
   303  
   304  // Load(path) loads the node record database (kaddb) from file on path.
   305  func (self *KadDb) load(path string, cb func(*NodeRecord, Node) error) (err error) {
   306  	defer self.lock.Unlock()
   307  	self.lock.Lock()
   308  
   309  	var data []byte
   310  	data, err = ioutil.ReadFile(path)
   311  	if err != nil {
   312  		return
   313  	}
   314  
   315  	err = json.Unmarshal(data, self)
   316  	if err != nil {
   317  		return
   318  	}
   319  	var n int
   320  	var purge []bool
   321  	for po, b := range self.Nodes {
   322  		purge = make([]bool, len(b))
   323  	ROW:
   324  		for i, node := range b {
   325  			if cb != nil {
   326  				err = cb(node, node.node)
   327  				if err != nil {
   328  					purge[i] = true
   329  					continue ROW
   330  				}
   331  			}
   332  			n++
   333  			if node.After.IsZero() {
   334  				node.After = time.Now()
   335  			}
   336  			self.index[node.Addr] = node
   337  		}
   338  		self.delete(po, purge)
   339  	}
   340  	log.Info(fmt.Sprintf("loaded kaddb with %v nodes from %v", n, path))
   341  
   342  	return
   343  }
   344  
   345  // accessor for KAD offline db count
   346  func (self *KadDb) count() int {
   347  	defer self.lock.Unlock()
   348  	self.lock.Lock()
   349  	return len(self.index)
   350  }