github.com/core-coin/go-core/v2@v2.1.9/p2p/enode/nodedb_test.go (about) 1 // Copyright 2018 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package enode 18 19 import ( 20 "bytes" 21 "fmt" 22 "io/ioutil" 23 "net" 24 "os" 25 "path/filepath" 26 "reflect" 27 "testing" 28 "time" 29 ) 30 31 var keytestID = HexID("51232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439") 32 33 func TestDBNodeKey(t *testing.T) { 34 enc := nodeKey(keytestID) 35 want := []byte{ 36 'n', ':', 37 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id 38 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // 39 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // 40 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // 41 ':', 'v', '4', 42 } 43 if !bytes.Equal(enc, want) { 44 t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want) 45 } 46 id, _ := splitNodeKey(enc) 47 if id != keytestID { 48 t.Errorf("wrong ID from splitNodeKey") 49 } 50 } 51 52 func TestDBNodeItemKey(t *testing.T) { 53 wantIP := net.IP{127, 0, 0, 3} 54 wantField := "foobar" 55 enc := nodeItemKey(keytestID, wantIP, wantField) 56 want := []byte{ 57 'n', ':', 58 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // node id 59 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // 60 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // 61 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // 62 ':', 'v', '4', ':', 63 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IP 64 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x03, // 65 ':', 'f', 'o', 'o', 'b', 'a', 'r', 66 } 67 if !bytes.Equal(enc, want) { 68 t.Errorf("wrong encoded key:\ngot %q\nwant %q", enc, want) 69 } 70 id, ip, field := splitNodeItemKey(enc) 71 if id != keytestID { 72 t.Errorf("splitNodeItemKey returned wrong ID: %v", id) 73 } 74 if !ip.Equal(wantIP) { 75 t.Errorf("splitNodeItemKey returned wrong IP: %v", ip) 76 } 77 if field != wantField { 78 t.Errorf("splitNodeItemKey returned wrong field: %q", field) 79 } 80 } 81 82 var nodeDBInt64Tests = []struct { 83 key []byte 84 value int64 85 }{ 86 {key: []byte{0x01}, value: 1}, 87 {key: []byte{0x02}, value: 2}, 88 {key: []byte{0x03}, value: 3}, 89 } 90 91 func TestDBInt64(t *testing.T) { 92 db, _ := OpenDB("") 93 defer db.Close() 94 95 tests := nodeDBInt64Tests 96 for i := 0; i < len(tests); i++ { 97 // Insert the next value 98 if err := db.storeInt64(tests[i].key, tests[i].value); err != nil { 99 t.Errorf("test %d: failed to store value: %v", i, err) 100 } 101 // Check all existing and non existing values 102 for j := 0; j < len(tests); j++ { 103 num := db.fetchInt64(tests[j].key) 104 switch { 105 case j <= i && num != tests[j].value: 106 t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, tests[j].value) 107 case j > i && num != 0: 108 t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, 0) 109 } 110 } 111 } 112 } 113 114 func TestDBFetchStore(t *testing.T) { 115 node := NewV4( 116 hexPubkey("ab856a9af6b0b651dd2f43b5e12193652ec1701c4da6f1c0d2a366ac4b9dabc9433ef09e41ca129552bd2c029086d9b03604de872a3b343204"), 117 net.IP{192, 168, 0, 1}, 118 30300, 119 30300, 120 ) 121 inst := time.Now() 122 num := 314 123 124 db, _ := OpenDB("") 125 defer db.Close() 126 127 // Check fetch/store operations on a node ping object 128 if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != 0 { 129 t.Errorf("ping: non-existing object: %v", stored) 130 } 131 if err := db.UpdateLastPingReceived(node.ID(), node.IP(), inst); err != nil { 132 t.Errorf("ping: failed to update: %v", err) 133 } 134 if stored := db.LastPingReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() { 135 t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) 136 } 137 // Check fetch/store operations on a node pong object 138 if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != 0 { 139 t.Errorf("pong: non-existing object: %v", stored) 140 } 141 if err := db.UpdateLastPongReceived(node.ID(), node.IP(), inst); err != nil { 142 t.Errorf("pong: failed to update: %v", err) 143 } 144 if stored := db.LastPongReceived(node.ID(), node.IP()); stored.Unix() != inst.Unix() { 145 t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) 146 } 147 // Check fetch/store operations on a node findnode-failure object 148 if stored := db.FindFails(node.ID(), node.IP()); stored != 0 { 149 t.Errorf("find-node fails: non-existing object: %v", stored) 150 } 151 if err := db.UpdateFindFails(node.ID(), node.IP(), num); err != nil { 152 t.Errorf("find-node fails: failed to update: %v", err) 153 } 154 if stored := db.FindFails(node.ID(), node.IP()); stored != num { 155 t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) 156 } 157 // Check fetch/store operations on an actual node object 158 if stored := db.Node(node.ID()); stored != nil { 159 t.Errorf("node: non-existing object: %v", stored) 160 } 161 if err := db.UpdateNode(node); err != nil { 162 t.Errorf("node: failed to update: %v", err) 163 } 164 if stored := db.Node(node.ID()); stored == nil { 165 t.Errorf("node: not found") 166 } else if !reflect.DeepEqual(stored, node) { 167 t.Errorf("node: data mismatch: have %v, want %v", stored, node) 168 } 169 } 170 171 var nodeDBSeedQueryNodes = []struct { 172 node *Node 173 pong time.Time 174 }{ 175 // This one should not be in the result set because its last 176 // pong time is too far in the past. 177 { 178 node: NewV4( 179 hexPubkey("ab856a9af6b0b651dd2f43b5e12193652ec1701c4da6f1c0d2a366ac4b9dabc9433ef09e41ca129552bd2c029086d9b03604de872a3b343204"), 180 net.IP{127, 0, 0, 3}, 181 30300, 182 30300, 183 ), 184 pong: time.Now().Add(-3 * time.Hour), 185 }, 186 // This one shouldn't be in the result set because its 187 // nodeID is the local node's ID. 188 { 189 node: NewV4( 190 hexPubkey("ff93ff820abacd4351b0f14e47b324bc82ff014c226f3f66a53535734a3c150e7e38ca03ef0964ba55acddc768f5e99cd59dea95ddd4defbab"), 191 net.IP{127, 0, 0, 3}, 192 30300, 193 30300, 194 ), 195 pong: time.Now().Add(-4 * time.Second), 196 }, 197 198 // These should be in the result set. 199 { 200 node: NewV4( 201 hexPubkey("c2b5eb3f5dde05f815b63777809ee3e7e0cbb20035a6b00ce327191e6eaa8f26a8d461c9112b7ab94698e7361fa19fd647e603e73239002946"), 202 net.IP{127, 0, 0, 1}, 203 30300, 204 30300, 205 ), 206 pong: time.Now().Add(-2 * time.Second), 207 }, 208 { 209 node: NewV4( 210 hexPubkey("6ca1d400c8ddf8acc94bcb0dd254911ad71a57bed5e0ae5aa205beed59b28c2339908e97990c493499613cff8ecf6c3dc7112a8ead220cdcd0"), 211 net.IP{127, 0, 0, 2}, 212 30300, 213 30300, 214 ), 215 pong: time.Now().Add(-3 * time.Second), 216 }, 217 { 218 node: NewV4( 219 hexPubkey("234dc63fe4d131212b38236c4c3411288d7bec61cbf7b120ff12c43dc60c96182882f4291d209db66f8a38e986c9c010ff59231a67f9515c7d"), 220 net.IP{127, 0, 0, 3}, 221 30300, 222 30300, 223 ), 224 pong: time.Now().Add(-1 * time.Second), 225 }, 226 { 227 node: NewV4( 228 hexPubkey("c013a50b4d1ebce5c377d8af8cb7114fd933ffc9627f96ad56d90fef5b7253ec736fd07ef9a81dc2955a997e54b7bf50afd0aa9f110595e2be"), 229 net.IP{127, 0, 0, 3}, 230 30300, 231 30300, 232 ), 233 pong: time.Now().Add(-2 * time.Second), 234 }, 235 { 236 node: NewV4( 237 hexPubkey("f141087e3e08af1aeec261ff75f48b5b1637f594ea9ad670e50051646b0416daa3b134c28788cbe98af26992a47652889cd8577ccc108ac02c"), 238 net.IP{127, 0, 0, 3}, 239 30300, 240 30300, 241 ), 242 pong: time.Now().Add(-2 * time.Second), 243 }, 244 } 245 246 func TestDBSeedQuery(t *testing.T) { 247 // Querying seeds uses seeks an might not find all nodes 248 // every time when the database is small. Run the test multiple 249 // times to avoid flakes. 250 const attempts = 15 251 var err error 252 for i := 0; i < attempts; i++ { 253 if err = testSeedQuery(); err == nil { 254 return 255 } 256 } 257 if err != nil { 258 t.Errorf("no successful run in %d attempts: %v", attempts, err) 259 } 260 } 261 262 func testSeedQuery() error { 263 db, _ := OpenDB("") 264 defer db.Close() 265 266 // Insert a batch of nodes for querying 267 for i, seed := range nodeDBSeedQueryNodes { 268 if err := db.UpdateNode(seed.node); err != nil { 269 return fmt.Errorf("node %d: failed to insert: %v", i, err) 270 } 271 if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil { 272 return fmt.Errorf("node %d: failed to insert bondTime: %v", i, err) 273 } 274 } 275 276 // Retrieve the entire batch and check for duplicates 277 seeds := db.QuerySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) 278 have := make(map[ID]struct{}) 279 for _, seed := range seeds { 280 have[seed.ID()] = struct{}{} 281 } 282 want := make(map[ID]struct{}) 283 for _, seed := range nodeDBSeedQueryNodes[1:] { 284 want[seed.node.ID()] = struct{}{} 285 } 286 if len(seeds) != len(want) { 287 return fmt.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) 288 } 289 for id := range have { 290 if _, ok := want[id]; !ok { 291 return fmt.Errorf("extra seed: %v", id) 292 } 293 } 294 for id := range want { 295 if _, ok := have[id]; !ok { 296 return fmt.Errorf("missing seed: %v", id) 297 } 298 } 299 return nil 300 } 301 302 func TestDBPersistency(t *testing.T) { 303 root, err := ioutil.TempDir("", "nodedb-") 304 if err != nil { 305 t.Fatalf("failed to create temporary data folder: %v", err) 306 } 307 defer os.RemoveAll(root) 308 309 var ( 310 testKey = []byte("somekey") 311 testInt = int64(314) 312 ) 313 314 // Create a persistent database and store some values 315 db, err := OpenDB(filepath.Join(root, "database")) 316 if err != nil { 317 t.Fatalf("failed to create persistent database: %v", err) 318 } 319 if err := db.storeInt64(testKey, testInt); err != nil { 320 t.Fatalf("failed to store value: %v.", err) 321 } 322 db.Close() 323 324 // Reopen the database and check the value 325 db, err = OpenDB(filepath.Join(root, "database")) 326 if err != nil { 327 t.Fatalf("failed to open persistent database: %v", err) 328 } 329 if val := db.fetchInt64(testKey); val != testInt { 330 t.Fatalf("value mismatch: have %v, want %v", val, testInt) 331 } 332 db.Close() 333 } 334 335 var nodeDBExpirationNodes = []struct { 336 node *Node 337 pong time.Time 338 storeNode bool 339 exp bool 340 }{ 341 // Node has new enough pong time and isn't expired: 342 { 343 node: NewV4( 344 hexPubkey("8d110e2ed4b446d9b5fb50f117e5f37fb7597af455e1dab0e6f045a6eeaa786a6781141659020d38bdc5e698ed3d4d2bafa8b5061810dfa63e"), 345 net.IP{127, 0, 0, 1}, 346 30300, 347 30300, 348 ), 349 storeNode: true, 350 pong: time.Now().Add(-dbNodeExpiration + time.Minute), 351 exp: false, 352 }, 353 // Node with pong time before expiration is removed: 354 { 355 node: NewV4( 356 hexPubkey("913a205579c32425b220dfba999d215066e5bdbf900226b11da1907eae5e93eb40616d47412cf819664e9eacbdfcca6b0c6e07e09847a38472"), 357 net.IP{127, 0, 0, 2}, 358 30300, 359 30300, 360 ), 361 storeNode: true, 362 pong: time.Now().Add(-dbNodeExpiration - time.Minute), 363 exp: true, 364 }, 365 // Just pong time, no node stored: 366 { 367 node: NewV4( 368 hexPubkey("b56670e0b6bad2c5dab9f9fe6f061a16cf78d68b6ae2cfda3144262d08d97ce5f46fd8799b6d1f709b1abe718f2863e224488bd7518e5e3b43"), 369 net.IP{127, 0, 0, 3}, 370 30300, 371 30300, 372 ), 373 storeNode: false, 374 pong: time.Now().Add(-dbNodeExpiration - time.Minute), 375 exp: true, 376 }, 377 // Node with multiple pong times, all older than expiration. 378 { 379 node: NewV4( 380 hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798"), 381 net.IP{127, 0, 0, 4}, 382 30300, 383 30300, 384 ), 385 storeNode: true, 386 pong: time.Now().Add(-dbNodeExpiration - time.Minute), 387 exp: true, 388 }, 389 { 390 node: NewV4( 391 hexPubkey("29f619cebfd32c9eab34aec797ed5e3fe15b9b45be95b4df3f5fe6a9ae892f433eb08d7698b2ef3621568b0fb70d57b515ab30d4e72583b798"), 392 net.IP{127, 0, 0, 5}, 393 30300, 394 30300, 395 ), 396 storeNode: false, 397 pong: time.Now().Add(-dbNodeExpiration - 2*time.Minute), 398 exp: true, 399 }, 400 // Node with multiple pong times, one newer, one older than expiration. 401 { 402 node: NewV4( 403 hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa0"), 404 net.IP{127, 0, 0, 6}, 405 30300, 406 30300, 407 ), 408 storeNode: true, 409 pong: time.Now().Add(-dbNodeExpiration + time.Minute), 410 exp: false, 411 }, 412 { 413 node: NewV4( 414 hexPubkey("3b73a9e5f4af6c4701c57c73cc8cfa0f4802840b24c11eba92aac3aef65644a3728b4b2aec8199f6d72bd66be2c65861c773129039bd47daa0"), 415 net.IP{127, 0, 0, 7}, 416 30300, 417 30300, 418 ), 419 storeNode: false, 420 pong: time.Now().Add(-dbNodeExpiration - time.Minute), 421 exp: true, 422 }, 423 } 424 425 func TestDBExpiration(t *testing.T) { 426 db, _ := OpenDB("") 427 defer db.Close() 428 429 // Add all the test nodes and set their last pong time. 430 for i, seed := range nodeDBExpirationNodes { 431 if seed.storeNode { 432 if err := db.UpdateNode(seed.node); err != nil { 433 t.Fatalf("node %d: failed to insert: %v", i, err) 434 } 435 } 436 if err := db.UpdateLastPongReceived(seed.node.ID(), seed.node.IP(), seed.pong); err != nil { 437 t.Fatalf("node %d: failed to update bondTime: %v", i, err) 438 } 439 } 440 441 db.expireNodes() 442 443 // Check that expired entries have been removed. 444 unixZeroTime := time.Unix(0, 0) 445 for i, seed := range nodeDBExpirationNodes { 446 node := db.Node(seed.node.ID()) 447 pong := db.LastPongReceived(seed.node.ID(), seed.node.IP()) 448 if seed.exp { 449 if seed.storeNode && node != nil { 450 t.Errorf("node %d (%s) shouldn't be present after expiration", i, seed.node.ID().TerminalString()) 451 } 452 if !pong.Equal(unixZeroTime) { 453 t.Errorf("pong time %d (%s %v) shouldn't be present after expiration", i, seed.node.ID().TerminalString(), seed.node.IP()) 454 } 455 } else { 456 if seed.storeNode && node == nil { 457 t.Errorf("node %d (%s) should be present after expiration", i, seed.node.ID().TerminalString()) 458 } 459 if !pong.Equal(seed.pong.Truncate(1 * time.Second)) { 460 t.Errorf("pong time %d (%s) should be %v after expiration, but is %v", i, seed.node.ID().TerminalString(), seed.pong, pong) 461 } 462 } 463 } 464 } 465 466 // This test checks that expiration works when discovery v5 data is present 467 // in the database. 468 func TestDBExpireV5(t *testing.T) { 469 db, _ := OpenDB("") 470 defer db.Close() 471 472 ip := net.IP{127, 0, 0, 1} 473 db.UpdateFindFailsV5(ID{}, ip, 4) 474 db.expireNodes() 475 }