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