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

     1  // Modifications Copyright 2018 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/database.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  // Contains the node database, storing previously seen nodes and any collected
    22  // metadata about them for QoS purposes.
    23  
    24  package discover
    25  
    26  import (
    27  	"bytes"
    28  	"crypto/rand"
    29  	"encoding/binary"
    30  	"os"
    31  	"sync"
    32  	"time"
    33  
    34  	"github.com/klaytn/klaytn/crypto"
    35  	"github.com/klaytn/klaytn/log"
    36  	"github.com/klaytn/klaytn/rlp"
    37  	"github.com/syndtr/goleveldb/leveldb"
    38  	"github.com/syndtr/goleveldb/leveldb/errors"
    39  	"github.com/syndtr/goleveldb/leveldb/iterator"
    40  	"github.com/syndtr/goleveldb/leveldb/opt"
    41  	"github.com/syndtr/goleveldb/leveldb/storage"
    42  	"github.com/syndtr/goleveldb/leveldb/util"
    43  )
    44  
    45  var (
    46  	nodeDBNilNodeID      = NodeID{}       // Special node ID to use as a nil element.
    47  	nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
    48  	nodeDBCleanupCycle   = time.Hour      // Time period for running the expiration task.
    49  	logger               = log.NewModuleLogger(log.NetworksP2PDiscover)
    50  )
    51  
    52  // nodeDB stores all nodes we know about.
    53  type nodeDB struct {
    54  	lvl    *leveldb.DB   // Interface to the database itself
    55  	self   NodeID        // Own node id to prevent adding it into the database
    56  	runner sync.Once     // Ensures we can start at most one expirer
    57  	quit   chan struct{} // Channel to signal the expiring thread to stop
    58  }
    59  
    60  // Schema layout for the node database
    61  var (
    62  	nodeDBVersionKey = []byte("version") // Version of the database to flush if changes
    63  	nodeDBItemPrefix = []byte("n:")      // Identifier to prefix node entries with
    64  
    65  	nodeDBDiscoverRoot      = ":discover"
    66  	nodeDBDiscoverPing      = nodeDBDiscoverRoot + ":lastping"
    67  	nodeDBDiscoverPong      = nodeDBDiscoverRoot + ":lastpong"
    68  	nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
    69  )
    70  
    71  // newNodeDB creates a new node database for storing and retrieving infos about
    72  // known peers in the network. If no path is given, an in-memory, temporary
    73  // database is constructed.
    74  func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
    75  	if path == "" {
    76  		return newMemoryNodeDB(self)
    77  	}
    78  	return newPersistentNodeDB(path, version, self)
    79  }
    80  
    81  // newMemoryNodeDB creates a new in-memory node database without a persistent
    82  // backend.
    83  func newMemoryNodeDB(self NodeID) (*nodeDB, error) {
    84  	db, err := leveldb.Open(storage.NewMemStorage(), nil)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return &nodeDB{
    89  		lvl:  db,
    90  		self: self,
    91  		quit: make(chan struct{}),
    92  	}, nil
    93  }
    94  
    95  // newPersistentNodeDB creates/opens a leveldb backed persistent node database,
    96  // also flushing its contents in case of a version mismatch.
    97  func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
    98  	opts := &opt.Options{OpenFilesCacheCapacity: 5}
    99  	db, err := leveldb.OpenFile(path, opts)
   100  	if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
   101  		db, err = leveldb.RecoverFile(path, nil)
   102  	}
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	// The nodes contained in the cache correspond to a certain protocol version.
   107  	// Flush all nodes if the version doesn't match.
   108  	currentVer := make([]byte, binary.MaxVarintLen64)
   109  	currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))]
   110  
   111  	blob, err := db.Get(nodeDBVersionKey, nil)
   112  	switch err {
   113  	case leveldb.ErrNotFound:
   114  		// Version not found (i.e. empty cache), insert it
   115  		if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil {
   116  			db.Close()
   117  			return nil, err
   118  		}
   119  
   120  	case nil:
   121  		// Version present, flush if different
   122  		if !bytes.Equal(blob, currentVer) {
   123  			db.Close()
   124  			if err = os.RemoveAll(path); err != nil {
   125  				return nil, err
   126  			}
   127  			return newPersistentNodeDB(path, version, self)
   128  		}
   129  	}
   130  	return &nodeDB{
   131  		lvl:  db,
   132  		self: self,
   133  		quit: make(chan struct{}),
   134  	}, nil
   135  }
   136  
   137  // makeKey generates the leveldb key-blob from a node id and its particular
   138  // field of interest.
   139  func makeKey(id NodeID, field string) []byte {
   140  	if bytes.Equal(id[:], nodeDBNilNodeID[:]) {
   141  		return []byte(field)
   142  	}
   143  	return append(nodeDBItemPrefix, append(id[:], field...)...)
   144  }
   145  
   146  // splitKey tries to split a database key into a node id and a field part.
   147  func splitKey(key []byte) (id NodeID, field string) {
   148  	// If the key is not of a node, return it plainly
   149  	if !bytes.HasPrefix(key, nodeDBItemPrefix) {
   150  		return NodeID{}, string(key)
   151  	}
   152  	// Otherwise split the id and field
   153  	item := key[len(nodeDBItemPrefix):]
   154  	copy(id[:], item[:len(id)])
   155  	field = string(item[len(id):])
   156  
   157  	return id, field
   158  }
   159  
   160  // fetchInt64 retrieves an integer instance associated with a particular
   161  // database key.
   162  func (db *nodeDB) fetchInt64(key []byte) int64 {
   163  	blob, err := db.lvl.Get(key, nil)
   164  	if err != nil {
   165  		return 0
   166  	}
   167  	val, read := binary.Varint(blob)
   168  	if read <= 0 {
   169  		return 0
   170  	}
   171  	return val
   172  }
   173  
   174  // storeInt64 update a specific database entry to the current time instance as a
   175  // unix timestamp.
   176  func (db *nodeDB) storeInt64(key []byte, n int64) error {
   177  	blob := make([]byte, binary.MaxVarintLen64)
   178  	blob = blob[:binary.PutVarint(blob, n)]
   179  
   180  	return db.lvl.Put(key, blob, nil)
   181  }
   182  
   183  // node retrieves a node with a given id from the database.
   184  func (db *nodeDB) node(id NodeID) *Node {
   185  	blob, err := db.lvl.Get(makeKey(id, nodeDBDiscoverRoot), nil)
   186  	if err != nil {
   187  		return nil
   188  	}
   189  	node := new(Node)
   190  	if err := rlp.DecodeBytes(blob, node); err != nil {
   191  		logger.Error("Failed to decode node RLP, It removed in the node database", "id", id, "err", err)
   192  		db.deleteNode(id)
   193  		return nil
   194  	}
   195  	node.sha = crypto.Keccak256Hash(node.ID[:])
   196  	return node
   197  }
   198  
   199  // updateNode inserts - potentially overwriting - a node into the peer database.
   200  func (db *nodeDB) updateNode(node *Node) error {
   201  	blob, err := rlp.EncodeToBytes(node)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	return db.lvl.Put(makeKey(node.ID, nodeDBDiscoverRoot), blob, nil)
   206  }
   207  
   208  // deleteNode deletes all information/keys associated with a node.
   209  func (db *nodeDB) deleteNode(id NodeID) error {
   210  	deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil)
   211  	for deleter.Next() {
   212  		if err := db.lvl.Delete(deleter.Key(), nil); err != nil {
   213  			return err
   214  		}
   215  	}
   216  	return nil
   217  }
   218  
   219  // ensureExpirer is a small helper method ensuring that the data expiration
   220  // mechanism is running. If the expiration goroutine is already running, this
   221  // method simply returns.
   222  //
   223  // The goal is to start the data evacuation only after the network successfully
   224  // bootstrapped itself (to prevent dumping potentially useful seed nodes). Since
   225  // it would require significant overhead to exactly trace the first successful
   226  // convergence, it's simpler to "ensure" the correct state when an appropriate
   227  // condition occurs (i.e. a successful bonding), and discard further events.
   228  func (db *nodeDB) ensureExpirer() {
   229  	db.runner.Do(func() { go db.expirer() })
   230  }
   231  
   232  // expirer should be started in a go routine, and is responsible for looping ad
   233  // infinitum and dropping stale data from the database.
   234  func (db *nodeDB) expirer() {
   235  	tick := time.NewTicker(nodeDBCleanupCycle)
   236  	defer tick.Stop()
   237  	for {
   238  		select {
   239  		case <-tick.C:
   240  			if err := db.expireNodes(); err != nil {
   241  				logger.Error("Failed to expire nodedb items", "err", err)
   242  			}
   243  		case <-db.quit:
   244  			return
   245  		}
   246  	}
   247  }
   248  
   249  // expireNodes iterates over the database and deletes all nodes that have not
   250  // been seen (i.e. received a pong from) for some allotted time.
   251  func (db *nodeDB) expireNodes() error {
   252  	threshold := time.Now().Add(-nodeDBNodeExpiration)
   253  
   254  	// Find discovered nodes that are older than the allowance
   255  	it := db.lvl.NewIterator(nil, nil)
   256  	defer it.Release()
   257  
   258  	for it.Next() {
   259  		// Skip the item if not a discovery node
   260  		id, field := splitKey(it.Key())
   261  		if field != nodeDBDiscoverRoot {
   262  			continue
   263  		}
   264  		// Skip the node if not expired yet (and not self)
   265  		if !bytes.Equal(id[:], db.self[:]) {
   266  			if seen := db.bondTime(id); seen.After(threshold) {
   267  				continue
   268  			}
   269  		}
   270  		// Otherwise delete all associated information
   271  		db.deleteNode(id)
   272  	}
   273  	return nil
   274  }
   275  
   276  // lastPing retrieves the time of the last ping packet send to a remote node,
   277  // requesting binding.
   278  func (db *nodeDB) lastPing(id NodeID) time.Time {
   279  	return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
   280  }
   281  
   282  // updateLastPing updates the last time we tried contacting a remote node.
   283  func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error {
   284  	return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
   285  }
   286  
   287  // bondTime retrieves the time of the last successful pong from remote node.
   288  func (db *nodeDB) bondTime(id NodeID) time.Time {
   289  	return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
   290  }
   291  
   292  // hasBond reports whether the given node is considered bonded.
   293  func (db *nodeDB) hasBond(id NodeID) bool {
   294  	return time.Since(db.bondTime(id)) < nodeDBNodeExpiration
   295  }
   296  
   297  // updateBondTime updates the last pong time of a node.
   298  func (db *nodeDB) updateBondTime(id NodeID, instance time.Time) error {
   299  	return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
   300  }
   301  
   302  // findFails retrieves the number of findnode failures since bonding.
   303  func (db *nodeDB) findFails(id NodeID) int {
   304  	return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
   305  }
   306  
   307  // updateFindFails updates the number of findnode failures since bonding.
   308  func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
   309  	return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
   310  }
   311  
   312  // querySeeds retrieves random nodes to be used as potential seed nodes
   313  // for bootstrapping.
   314  func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node {
   315  	var (
   316  		now   = time.Now()
   317  		nodes = make([]*Node, 0, n)
   318  		it    = db.lvl.NewIterator(nil, nil)
   319  		id    NodeID
   320  	)
   321  	defer it.Release()
   322  
   323  seek:
   324  	for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
   325  		// Seek to a random entry. The first byte is incremented by a
   326  		// random amount each time in order to increase the likelihood
   327  		// of hitting all existing nodes in very small databases.
   328  		ctr := id[0]
   329  		rand.Read(id[:])
   330  		id[0] = ctr + id[0]%16
   331  		it.Seek(makeKey(id, nodeDBDiscoverRoot))
   332  
   333  		n := nextNode(db, it)
   334  		if n == nil {
   335  			id[0] = 0
   336  			continue seek // iterator exhausted
   337  		}
   338  		if n.ID == db.self {
   339  			continue seek
   340  		}
   341  		if now.Sub(db.bondTime(n.ID)) > maxAge {
   342  			continue seek
   343  		}
   344  		for i := range nodes {
   345  			if nodes[i].ID == n.ID {
   346  				continue seek // duplicate
   347  			}
   348  		}
   349  		nodes = append(nodes, n)
   350  	}
   351  	return nodes
   352  }
   353  
   354  // reads the next node record from the iterator, skipping over other
   355  // database entries.
   356  func nextNode(db *nodeDB, it iterator.Iterator) *Node {
   357  	for end := false; !end; end = !it.Next() {
   358  		id, field := splitKey(it.Key())
   359  		if field != nodeDBDiscoverRoot {
   360  			continue
   361  		}
   362  		var n Node
   363  		if err := rlp.DecodeBytes(it.Value(), &n); err != nil {
   364  			logger.Error("Failed to decode node RLP, It removed in node the database.", "id", id, "err", err)
   365  			db.deleteNode(id)
   366  			continue
   367  		}
   368  		return &n
   369  	}
   370  	return nil
   371  }
   372  
   373  // close flushes and closes the database files.
   374  func (db *nodeDB) close() {
   375  	close(db.quit)
   376  	db.lvl.Close()
   377  }