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 }