github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/p2p/enode/nodedb.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:41</date> 10 //</624450104455073792> 11 12 13 package enode 14 15 import ( 16 "bytes" 17 "crypto/rand" 18 "encoding/binary" 19 "fmt" 20 "os" 21 "sync" 22 "time" 23 24 "github.com/ethereum/go-ethereum/log" 25 "github.com/ethereum/go-ethereum/rlp" 26 "github.com/syndtr/goleveldb/leveldb" 27 "github.com/syndtr/goleveldb/leveldb/errors" 28 "github.com/syndtr/goleveldb/leveldb/iterator" 29 "github.com/syndtr/goleveldb/leveldb/opt" 30 "github.com/syndtr/goleveldb/leveldb/storage" 31 "github.com/syndtr/goleveldb/leveldb/util" 32 ) 33 34 //节点数据库中的键。 35 const ( 36 dbVersionKey = "version" //更改时要刷新的数据库版本 37 dbItemPrefix = "n:" //为节点条目加前缀的标识符 38 39 dbDiscoverRoot = ":discover" 40 dbDiscoverSeq = dbDiscoverRoot + ":seq" 41 dbDiscoverPing = dbDiscoverRoot + ":lastping" 42 dbDiscoverPong = dbDiscoverRoot + ":lastpong" 43 dbDiscoverFindFails = dbDiscoverRoot + ":findfail" 44 dbLocalRoot = ":local" 45 dbLocalSeq = dbLocalRoot + ":seq" 46 ) 47 48 var ( 49 dbNodeExpiration = 24 * time.Hour //删除未查看节点的时间。 50 dbCleanupCycle = time.Hour //运行过期任务的时间段。 51 dbVersion = 7 52 ) 53 54 //db是节点数据库,存储以前看到的节点和有关 55 //它们用于QoS目的。 56 type DB struct { 57 lvl *leveldb.DB //与数据库本身的接口 58 runner sync.Once //确保我们最多可以启动一个到期日 59 quit chan struct{} //向过期线程发出停止信号的通道 60 } 61 62 //opendb打开一个节点数据库,用于存储和检索 63 //网络。如果没有给内存中的路径,则构建临时数据库。 64 func OpenDB(path string) (*DB, error) { 65 if path == "" { 66 return newMemoryDB() 67 } 68 return newPersistentDB(path) 69 } 70 71 //newmemorynodedb创建一个没有持久后端的新内存节点数据库。 72 func newMemoryDB() (*DB, error) { 73 db, err := leveldb.Open(storage.NewMemStorage(), nil) 74 if err != nil { 75 return nil, err 76 } 77 return &DB{lvl: db, quit: make(chan struct{})}, nil 78 } 79 80 //newpersistentnodedb创建/打开一个支持leveldb的持久节点数据库, 81 //同时在版本不匹配的情况下刷新其内容。 82 func newPersistentDB(path string) (*DB, error) { 83 opts := &opt.Options{OpenFilesCacheCapacity: 5} 84 db, err := leveldb.OpenFile(path, opts) 85 if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { 86 db, err = leveldb.RecoverFile(path, nil) 87 } 88 if err != nil { 89 return nil, err 90 } 91 //缓存中包含的节点对应于某个协议版本。 92 //如果版本不匹配,则刷新所有节点。 93 currentVer := make([]byte, binary.MaxVarintLen64) 94 currentVer = currentVer[:binary.PutVarint(currentVer, int64(dbVersion))] 95 96 blob, err := db.Get([]byte(dbVersionKey), nil) 97 switch err { 98 case leveldb.ErrNotFound: 99 //找不到版本(即空缓存),请将其插入 100 if err := db.Put([]byte(dbVersionKey), currentVer, nil); err != nil { 101 db.Close() 102 return nil, err 103 } 104 105 case nil: 106 //存在版本,如果不同则刷新 107 if !bytes.Equal(blob, currentVer) { 108 db.Close() 109 if err = os.RemoveAll(path); err != nil { 110 return nil, err 111 } 112 return newPersistentDB(path) 113 } 114 } 115 return &DB{lvl: db, quit: make(chan struct{})}, nil 116 } 117 118 //makekey从节点ID及其特定的 119 //感兴趣的领域。 120 func makeKey(id ID, field string) []byte { 121 if (id == ID{}) { 122 return []byte(field) 123 } 124 return append([]byte(dbItemPrefix), append(id[:], field...)...) 125 } 126 127 //SplitKey尝试将数据库键拆分为节点ID和字段部分。 128 func splitKey(key []byte) (id ID, field string) { 129 //如果键不是节点的,则直接返回 130 if !bytes.HasPrefix(key, []byte(dbItemPrefix)) { 131 return ID{}, string(key) 132 } 133 //否则拆分ID和字段 134 item := key[len(dbItemPrefix):] 135 copy(id[:], item[:len(id)]) 136 field = string(item[len(id):]) 137 138 return id, field 139 } 140 141 //fetchint64检索与特定键关联的整数。 142 func (db *DB) fetchInt64(key []byte) int64 { 143 blob, err := db.lvl.Get(key, nil) 144 if err != nil { 145 return 0 146 } 147 val, read := binary.Varint(blob) 148 if read <= 0 { 149 return 0 150 } 151 return val 152 } 153 154 //storeInt64在给定的键中存储一个整数。 155 func (db *DB) storeInt64(key []byte, n int64) error { 156 blob := make([]byte, binary.MaxVarintLen64) 157 blob = blob[:binary.PutVarint(blob, n)] 158 return db.lvl.Put(key, blob, nil) 159 } 160 161 //fetchuint64检索与特定键关联的整数。 162 func (db *DB) fetchUint64(key []byte) uint64 { 163 blob, err := db.lvl.Get(key, nil) 164 if err != nil { 165 return 0 166 } 167 val, _ := binary.Uvarint(blob) 168 return val 169 } 170 171 //storeUInt64在给定的键中存储一个整数。 172 func (db *DB) storeUint64(key []byte, n uint64) error { 173 blob := make([]byte, binary.MaxVarintLen64) 174 blob = blob[:binary.PutUvarint(blob, n)] 175 return db.lvl.Put(key, blob, nil) 176 } 177 178 //节点从数据库中检索具有给定ID的节点。 179 func (db *DB) Node(id ID) *Node { 180 blob, err := db.lvl.Get(makeKey(id, dbDiscoverRoot), nil) 181 if err != nil { 182 return nil 183 } 184 return mustDecodeNode(id[:], blob) 185 } 186 187 func mustDecodeNode(id, data []byte) *Node { 188 node := new(Node) 189 if err := rlp.DecodeBytes(data, &node.r); err != nil { 190 panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err)) 191 } 192 //还原节点ID缓存。 193 copy(node.id[:], id) 194 return node 195 } 196 197 //updateNode将节点插入到对等数据库中(可能会覆盖)。 198 func (db *DB) UpdateNode(node *Node) error { 199 if node.Seq() < db.NodeSeq(node.ID()) { 200 return nil 201 } 202 blob, err := rlp.EncodeToBytes(&node.r) 203 if err != nil { 204 return err 205 } 206 if err := db.lvl.Put(makeKey(node.ID(), dbDiscoverRoot), blob, nil); err != nil { 207 return err 208 } 209 return db.storeUint64(makeKey(node.ID(), dbDiscoverSeq), node.Seq()) 210 } 211 212 //nodeseq返回给定节点的存储记录序列号。 213 func (db *DB) NodeSeq(id ID) uint64 { 214 return db.fetchUint64(makeKey(id, dbDiscoverSeq)) 215 } 216 217 //如果节点具有较大的序列,resolve将返回该节点的存储记录 218 //比N多 219 func (db *DB) Resolve(n *Node) *Node { 220 if n.Seq() > db.NodeSeq(n.ID()) { 221 return n 222 } 223 return db.Node(n.ID()) 224 } 225 226 //删除节点删除与节点关联的所有信息/键。 227 func (db *DB) DeleteNode(id ID) error { 228 deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil) 229 for deleter.Next() { 230 if err := db.lvl.Delete(deleter.Key(), nil); err != nil { 231 return err 232 } 233 } 234 return nil 235 } 236 237 //EnsureExpirer是一个小助手方法,可确保数据过期 238 //机制正在运行。如果到期goroutine已经在运行,则 239 //方法只返回。 240 // 241 //目标是在网络成功后才开始数据疏散 242 //引导自身(以防止转储可能有用的种子节点)。自从 243 //准确跟踪第一个成功的 244 //收敛性,在适当的时候“确保”正确的状态比较简单 245 //条件发生(即成功结合),并丢弃进一步的事件。 246 func (db *DB) ensureExpirer() { 247 db.runner.Do(func() { go db.expirer() }) 248 } 249 250 //Expirer应该在Go例程中启动,并负责循环广告 251 //无穷大并从数据库中删除过时数据。 252 func (db *DB) expirer() { 253 tick := time.NewTicker(dbCleanupCycle) 254 defer tick.Stop() 255 for { 256 select { 257 case <-tick.C: 258 if err := db.expireNodes(); err != nil { 259 log.Error("Failed to expire nodedb items", "err", err) 260 } 261 case <-db.quit: 262 return 263 } 264 } 265 } 266 267 //expirenodes迭代数据库并删除所有没有 268 //在一段指定的时间内被看见(即收到乒乓球)。 269 func (db *DB) expireNodes() error { 270 threshold := time.Now().Add(-dbNodeExpiration) 271 272 //查找发现的早于允许的节点 273 it := db.lvl.NewIterator(nil, nil) 274 defer it.Release() 275 276 for it.Next() { 277 //如果不是发现节点,则跳过该项 278 id, field := splitKey(it.Key()) 279 if field != dbDiscoverRoot { 280 continue 281 } 282 //如果尚未过期(而不是自己),则跳过节点 283 if seen := db.LastPongReceived(id); seen.After(threshold) { 284 continue 285 } 286 //否则删除所有相关信息 287 db.DeleteNode(id) 288 } 289 return nil 290 } 291 292 //LastpingReceived检索从中接收的最后一个ping数据包的时间 293 //远程节点。 294 func (db *DB) LastPingReceived(id ID) time.Time { 295 return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPing)), 0) 296 } 297 298 //上一次尝试联系远程节点时,UpdateLastpingReceived更新。 299 func (db *DB) UpdateLastPingReceived(id ID, instance time.Time) error { 300 return db.storeInt64(makeKey(id, dbDiscoverPing), instance.Unix()) 301 } 302 303 //lastpongreceived从远程节点检索上次成功的pong的时间。 304 func (db *DB) LastPongReceived(id ID) time.Time { 305 //发射呼气 306 db.ensureExpirer() 307 return time.Unix(db.fetchInt64(makeKey(id, dbDiscoverPong)), 0) 308 } 309 310 //updateLastPongreeived更新节点的最后一次挂起时间。 311 func (db *DB) UpdateLastPongReceived(id ID, instance time.Time) error { 312 return db.storeInt64(makeKey(id, dbDiscoverPong), instance.Unix()) 313 } 314 315 //findfails检索自绑定以来findnode失败的次数。 316 func (db *DB) FindFails(id ID) int { 317 return int(db.fetchInt64(makeKey(id, dbDiscoverFindFails))) 318 } 319 320 //updatefindfails更新自绑定以来findnode失败的次数。 321 func (db *DB) UpdateFindFails(id ID, fails int) error { 322 return db.storeInt64(makeKey(id, dbDiscoverFindFails), int64(fails)) 323 } 324 325 //localseq检索本地记录序列计数器。 326 func (db *DB) localSeq(id ID) uint64 { 327 return db.fetchUint64(makeKey(id, dbLocalSeq)) 328 } 329 330 //storelocalseq存储本地记录序列计数器。 331 func (db *DB) storeLocalSeq(id ID, n uint64) { 332 db.storeUint64(makeKey(id, dbLocalSeq), n) 333 } 334 335 //queryseeds检索用作潜在种子节点的随机节点 336 //用于引导。 337 func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node { 338 var ( 339 now = time.Now() 340 nodes = make([]*Node, 0, n) 341 it = db.lvl.NewIterator(nil, nil) 342 id ID 343 ) 344 defer it.Release() 345 346 seek: 347 for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ { 348 //寻找一个随机条目。第一个字节的增量为 349 //每次随机数以增加可能性 350 //攻击非常小的数据库中的所有现有节点。 351 ctr := id[0] 352 rand.Read(id[:]) 353 id[0] = ctr + id[0]%16 354 it.Seek(makeKey(id, dbDiscoverRoot)) 355 356 n := nextNode(it) 357 if n == nil { 358 id[0] = 0 359 continue seek //迭代器已用完 360 } 361 if now.Sub(db.LastPongReceived(n.ID())) > maxAge { 362 continue seek 363 } 364 for i := range nodes { 365 if nodes[i].ID() == n.ID() { 366 continue seek //复制品 367 } 368 } 369 nodes = append(nodes, n) 370 } 371 return nodes 372 } 373 374 //从迭代器读取下一个节点记录,跳过其他节点记录 375 //数据库条目。 376 func nextNode(it iterator.Iterator) *Node { 377 for end := false; !end; end = !it.Next() { 378 id, field := splitKey(it.Key()) 379 if field != dbDiscoverRoot { 380 continue 381 } 382 return mustDecodeNode(id[:], it.Value()) 383 } 384 return nil 385 } 386 387 //关闭刷新并关闭数据库文件。 388 func (db *DB) Close() { 389 close(db.quit) 390 db.lvl.Close() 391 } 392