github.com/klaytn/klaytn@v1.12.1/networks/p2p/discover/discover_storage_kademlia.go (about)

     1  // Modifications Copyright 2019 The klaytn Authors
     2  // Copyright 2015 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from p2p/discover/table.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package discover
    22  
    23  import (
    24  	"crypto/rand"
    25  	"net"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/klaytn/klaytn/common"
    30  	"github.com/klaytn/klaytn/crypto"
    31  	"github.com/klaytn/klaytn/log"
    32  	"github.com/klaytn/klaytn/networks/p2p/netutil"
    33  )
    34  
    35  const (
    36  	tableIPLimit, tableSubnet   = 10, 24
    37  	bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
    38  	// We keep buckets for the upper 1/15 of distances because
    39  	// it's very unlikely we'll ever encounter a node that's closer.
    40  	hashBits          = len(common.Hash{}) * 8
    41  	nBuckets          = hashBits / 15       // Number of buckets
    42  	bucketMinDistance = hashBits - nBuckets // Log distance of closest bucket
    43  
    44  	seedMinTableTime = 5 * time.Minute
    45  )
    46  
    47  type KademliaStorage struct {
    48  	targetType  NodeType
    49  	tab         *Table
    50  	buckets     [nBuckets]*bucket
    51  	bucketsMu   sync.Mutex
    52  	ips         netutil.DistinctNetSet
    53  	noDiscover  bool // if noDiscover is true, doesn't lookup new node.
    54  	localLogger log.Logger
    55  }
    56  
    57  func (s *KademliaStorage) init() {
    58  	s.localLogger = logger.NewWith("Discover", "Kademlia")
    59  	s.ips = netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit}
    60  	s.bucketsMu.Lock()
    61  	defer s.bucketsMu.Unlock()
    62  	for i := range s.buckets {
    63  		s.buckets[i] = &bucket{
    64  			ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
    65  		}
    66  	}
    67  }
    68  
    69  func (s *KademliaStorage) lookup(targetID NodeID, refreshIfEmpty bool, targetType NodeType) []*Node {
    70  	s.localLogger.Debug("lookup start", "StorageName", s.name(), "targetID", targetID,
    71  		"targetNodeType", nodeTypeName(targetType), "refreshIfEmpty", refreshIfEmpty)
    72  	var (
    73  		target = crypto.Keccak256Hash(targetID[:])
    74  		result *nodesByDistance
    75  	)
    76  
    77  	for {
    78  		// generate initial result set
    79  		result = s.closest(target, bucketSize)
    80  		if len(result.entries) > 0 || !refreshIfEmpty {
    81  			break
    82  		}
    83  		// The result set is empty, all nodes were dropped, refresh.
    84  		// We actually wait for the refresh to complete here. The very
    85  		// first query will hit this case and run the bootstrapping
    86  		// logic.
    87  		<-s.tab.refresh()
    88  		refreshIfEmpty = false
    89  	}
    90  	return s.tab.findNewNode(result, targetID, targetType, true, bucketSize)
    91  }
    92  
    93  func (s *KademliaStorage) getNodes(max int) []*Node {
    94  	nbd := s.closest(crypto.Keccak256Hash(s.tab.self.ID[:]), max)
    95  	var ret []*Node
    96  	for _, nd := range nbd.entries {
    97  		if nd.NType == s.targetType {
    98  			ret = append(ret, nd)
    99  		}
   100  	}
   101  	return ret
   102  }
   103  
   104  func (s *KademliaStorage) doRevalidate() {
   105  	s.bucketsMu.Lock()
   106  	defer s.bucketsMu.Unlock()
   107  
   108  	last, bi := s.nodeToRevalidate()
   109  	if last == nil {
   110  		// No non-empty bucket found.
   111  		return
   112  	}
   113  
   114  	holdingTime := s.tab.db.bondTime(last.ID).Add(10 * time.Second)
   115  	if time.Now().Before(holdingTime) {
   116  		s.localLogger.Debug("skip revalidate", "StorageName", s.name())
   117  		return
   118  	}
   119  
   120  	// Ping the selected node and wait for a pong.
   121  	err := s.tab.ping(last.ID, last.addr())
   122  	b := s.bucketByIdx(bi)
   123  	if err == nil {
   124  		// The node responded, move it to the front.
   125  		s.localLogger.Debug("Revalidated node", "StorageName", s.name(), "bucketIdx", bi, "nodeId", last.ID)
   126  		b.bump(last)
   127  		return
   128  	}
   129  	// No reply received, pick a replacement or delete the node if there aren't
   130  	// any replacements.
   131  	if r := s.replace(b, last); r != nil {
   132  		s.localLogger.Debug("Replaced the node without any response", "StorageName", s.name(), "bucketIdx", bi, "nodeId", last.ID, "ip", last.IP, "r", r.ID, "rip", r.IP)
   133  	} else {
   134  		s.localLogger.Debug("Removed the node without any response", "StorageName", s.name(), "bucketIdx", bi, "nodeId", last.ID, "ip", last.IP)
   135  	}
   136  }
   137  
   138  func (s *KademliaStorage) setTargetNodeType(tType NodeType) {
   139  	s.targetType = tType
   140  }
   141  
   142  func (s *KademliaStorage) doRefresh() {
   143  	if s.noDiscover {
   144  		return
   145  	}
   146  
   147  	// Run self lookup to discover new neighbor nodes.
   148  	s.lookup(s.tab.self.ID, false, s.targetType)
   149  
   150  	// The Kademlia paper specifies that the bucket refresh should
   151  	// perform a lookup in the least recently used bucket. We cannot
   152  	// adhere to this because the findnode target is a 512bit value
   153  	// (not hash-sized) and it is not easily possible to generate a
   154  	// sha3 preimage that falls into a chosen bucket.
   155  	// We perform a few lookups with a random target instead.
   156  	for i := 0; i < 3; i++ {
   157  		var target NodeID
   158  		rand.Read(target[:])
   159  		s.lookup(target, false, s.targetType)
   160  	}
   161  }
   162  
   163  func (s *KademliaStorage) nodeAll() (nodes []*Node) {
   164  	for _, b := range s.buckets {
   165  		nodes = append(nodes, b.entries...)
   166  	}
   167  	return nodes
   168  }
   169  
   170  // The caller must hold s.bucketMu
   171  func (s *KademliaStorage) bucketByIdx(bi int) *bucket {
   172  	// TODO-Klaytn-Node range check
   173  	return s.buckets[bi]
   174  }
   175  
   176  // The caller must hold s.bucketMu
   177  func (s *KademliaStorage) nodeToRevalidate() (n *Node, bi int) {
   178  	s.tab.randMu.Lock()
   179  	defer s.tab.randMu.Unlock()
   180  
   181  	for _, bi = range s.tab.rand.Perm(len(s.buckets)) { // TODO
   182  		b := s.buckets[bi]
   183  		if len(b.entries) > 0 {
   184  			last := b.entries[len(b.entries)-1]
   185  			return last, bi
   186  		}
   187  	}
   188  	return nil, 0
   189  }
   190  
   191  func (s *KademliaStorage) copyBondedNodes() {
   192  	s.bucketsMu.Lock()
   193  	defer s.bucketsMu.Unlock()
   194  	now := time.Now()
   195  	for _, b := range &s.buckets {
   196  		for _, n := range b.entries {
   197  			if now.Sub(n.addedAt) >= seedMinTableTime {
   198  				s.tab.db.updateNode(n)
   199  			}
   200  		}
   201  	}
   202  }
   203  
   204  func (s *KademliaStorage) len() (n int) {
   205  	for _, b := range &s.buckets {
   206  		n += len(b.entries)
   207  	}
   208  	return n
   209  }
   210  
   211  func (s *KademliaStorage) getReplacements() []*Node {
   212  	s.bucketsMu.Lock()
   213  	defer s.bucketsMu.Unlock()
   214  	var nodes []*Node
   215  	for i := 0; i < nBuckets; i++ {
   216  		nodes = append(nodes, s.buckets[i].replacements...)
   217  	}
   218  	return nodes
   219  }
   220  
   221  func (s *KademliaStorage) getBucketEntries() []*Node {
   222  	s.bucketsMu.Lock()
   223  	defer s.bucketsMu.Unlock()
   224  
   225  	var nodes []*Node
   226  	for i := 0; i < nBuckets; i++ {
   227  		nodes = append(nodes, s.buckets[i].entries...)
   228  	}
   229  	return nodes
   230  }
   231  
   232  func (s *KademliaStorage) stuff(nodes []*Node) {
   233  	s.bucketsMu.Lock()
   234  	defer s.bucketsMu.Unlock()
   235  
   236  	for _, n := range nodes {
   237  		if n.ID == s.tab.self.ID {
   238  			continue // don't add self
   239  		}
   240  		b := s.bucket(n.sha)
   241  		if len(b.entries) < bucketSize {
   242  			s.bumpOrAdd(b, n)
   243  		}
   244  
   245  	}
   246  }
   247  
   248  // replace removes n from the replacement list and replaces 'last' with it if it is the
   249  // last entry in the bucket. If 'last' isn't the last entry, it has either been replaced
   250  // with someone else or became active.
   251  func (s *KademliaStorage) replace(b *bucket, last *Node) *Node {
   252  	if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID != last.ID {
   253  		// Entry has moved, don't replace it.
   254  		return nil
   255  	}
   256  	// Still the last entry.
   257  	if len(b.replacements) == 0 {
   258  		s.deleteInBucket(b, last)
   259  		return nil
   260  	}
   261  	s.tab.randMu.Lock()
   262  	r := b.replacements[s.tab.rand.Intn(len(b.replacements))]
   263  	s.tab.randMu.Unlock()
   264  	b.replacements = deleteNode(b.replacements, r)
   265  	b.entries[len(b.entries)-1] = r
   266  	s.removeIP(b, last.IP)
   267  	return r
   268  }
   269  
   270  func (s *KademliaStorage) delete(n *Node) {
   271  	s.bucketsMu.Lock()
   272  	defer s.bucketsMu.Unlock()
   273  	s.deleteInBucket(s.bucket(n.sha), n)
   274  }
   275  
   276  // The caller must hold tab.mutex.
   277  func (s *KademliaStorage) deleteInBucket(b *bucket, n *Node) {
   278  	b.entries = deleteNode(b.entries, n)
   279  	s.removeIP(b, n.IP) // TODO-Klaytn-Node Does the IP is not lock?
   280  }
   281  
   282  // closest returns the n nodes in the table that are closest to the
   283  // given id. The caller must hold s.bucketMu.
   284  func (s *KademliaStorage) closest(target common.Hash, nresults int) *nodesByDistance {
   285  	// This is a very wasteful way to find the closest nodes but
   286  	// obviously correct. I believe that tree-based buckets would make
   287  	// this easier to implement efficiently.
   288  	// TODO-Klaytn-Node more efficient ways to obtain the closest nodes could be considered.
   289  	close := &nodesByDistance{target: target}
   290  	s.bucketsMu.Lock()
   291  	defer s.bucketsMu.Unlock()
   292  
   293  	for _, b := range &s.buckets {
   294  		for _, n := range b.entries {
   295  			close.push(n, nresults)
   296  		}
   297  	}
   298  	return close
   299  }
   300  
   301  func (s *KademliaStorage) setTable(t *Table) {
   302  	s.tab = t
   303  }
   304  
   305  func (s *KademliaStorage) add(n *Node) {
   306  	s.bucketsMu.Lock()
   307  	defer s.bucketsMu.Unlock()
   308  	b := s.bucket(n.sha)
   309  	if !s.bumpOrAdd(b, n) {
   310  		// Node is not in table. Add it to the replacement list.
   311  		s.addReplacement(b, n)
   312  	}
   313  }
   314  
   315  func (s *KademliaStorage) readRandomNodes(buf []*Node) (n int) {
   316  	s.bucketsMu.Lock()
   317  	defer s.bucketsMu.Unlock()
   318  
   319  	// Find all non-empty buckets and get a fresh slice of their entries.
   320  	var buckets [][]*Node
   321  	for _, b := range &s.buckets {
   322  		if len(b.entries) > 0 {
   323  			buckets = append(buckets, b.entries[:])
   324  		}
   325  	}
   326  	if len(buckets) == 0 {
   327  		return 0
   328  	}
   329  	// Shuffle the buckets.
   330  	s.tab.randMu.Lock()
   331  	for i := len(buckets) - 1; i > 0; i-- {
   332  		j := s.tab.rand.Intn(len(buckets))
   333  		buckets[i], buckets[j] = buckets[j], buckets[i]
   334  	}
   335  	s.tab.randMu.Unlock()
   336  	// Move head of each bucket into buf, removing buckets that become empty.
   337  	var i, j int
   338  	for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) {
   339  		b := buckets[j]
   340  		buf[i] = &(*b[0])
   341  		buckets[j] = b[1:]
   342  		if len(b) == 1 {
   343  			buckets = append(buckets[:j], buckets[j+1:]...)
   344  		}
   345  		if len(buckets) == 0 {
   346  			break
   347  		}
   348  	}
   349  	return i + 1
   350  }
   351  
   352  // The caller must hold s.bucketMu
   353  func (s *KademliaStorage) bucket(sha common.Hash) *bucket {
   354  	d := logdist(s.tab.self.sha, sha)
   355  	if d <= bucketMinDistance {
   356  		return s.buckets[0]
   357  	}
   358  	return s.buckets[d-bucketMinDistance-1]
   359  }
   360  
   361  // bumpOrAdd moves n to the front of the bucket entry list or adds it if the list isn't
   362  // full. The return value is true if n is in the bucket.
   363  // The caller must hold s.bucketMu
   364  func (s *KademliaStorage) bumpOrAdd(b *bucket, n *Node) bool {
   365  	if b.bump(n) {
   366  		s.localLogger.Trace("Add(Bumped)", "StorageName", s.name(), "node", n)
   367  		return true
   368  	}
   369  	if len(b.entries) >= bucketSize || !s.addIP(b, n.IP) {
   370  		s.localLogger.Debug("Add(New) -Exceed Max", "StorageName", s.name(), "node", n)
   371  		return false
   372  	}
   373  	s.localLogger.Trace("Add(New)", "StorageName", s.name(), "node", n)
   374  	b.entries, _ = pushNode(b.entries, n, bucketSize)
   375  	b.replacements = deleteNode(b.replacements, n)
   376  	n.addedAt = time.Now()
   377  	if s.tab.nodeAddedHook != nil {
   378  		s.tab.nodeAddedHook(n)
   379  	}
   380  	return true
   381  }
   382  
   383  // The caller must hold s.bucketMu.
   384  func (s *KademliaStorage) addReplacement(b *bucket, n *Node) {
   385  	for _, e := range b.replacements {
   386  		if e.ID == n.ID {
   387  			return // already in list
   388  		}
   389  	}
   390  	if !s.addIP(b, n.IP) {
   391  		return
   392  	}
   393  	var removed *Node
   394  	b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
   395  	if removed != nil {
   396  		s.removeIP(b, removed.IP)
   397  	}
   398  }
   399  
   400  func (s *KademliaStorage) addIP(b *bucket, ip net.IP) bool {
   401  	if netutil.IsLAN(ip) {
   402  		return true
   403  	}
   404  	if !s.ips.Add(ip) {
   405  		s.localLogger.Debug("IP exceeds table limit", "StorageName", s.name(), "ip", ip)
   406  		return false
   407  	}
   408  	if !b.ips.Add(ip) {
   409  		s.localLogger.Debug("IP exceeds bucket limit", "StorageName", s.name(), "ip", ip)
   410  		s.ips.Remove(ip)
   411  		return false
   412  	}
   413  	return true
   414  }
   415  
   416  func (s *KademliaStorage) removeIP(b *bucket, ip net.IP) {
   417  	if netutil.IsLAN(ip) {
   418  		return
   419  	}
   420  	s.ips.Remove(ip)
   421  	b.ips.Remove(ip)
   422  }
   423  
   424  func (s *KademliaStorage) name() string {
   425  	return nodeTypeName(s.targetType)
   426  }
   427  
   428  // bucket contains nodes, ordered by their last activity. the entry
   429  // that was most recently active is the first element in entries.
   430  type bucket struct {
   431  	entries      []*Node // live entries, sorted by time of last contact
   432  	replacements []*Node // recently seen nodes to be used if revalidation fails
   433  	ips          netutil.DistinctNetSet
   434  }
   435  
   436  // bump moves the given node to the front of the bucket entry list
   437  // if it is contained in that list.
   438  // caller
   439  func (b *bucket) bump(n *Node) bool {
   440  	for i := range b.entries {
   441  		if b.entries[i].ID == n.ID {
   442  			// move it to the front
   443  			copy(b.entries[1:], b.entries[:i])
   444  			b.entries[0] = n
   445  			return true
   446  		}
   447  	}
   448  	return false
   449  }
   450  
   451  func (s *KademliaStorage) isAuthorized(id NodeID) bool { return true }
   452  func (s *KademliaStorage) getAuthorizedNodes() []*Node { return nil }
   453  func (s *KademliaStorage) putAuthorizedNode(*Node)     {}
   454  func (s *KademliaStorage) deleteAuthorizedNode(NodeID) {}