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