github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/p2p/dnsdisc/client_test.go (about) 1 // Copyright 2019 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 package dnsdisc 18 19 import ( 20 "context" 21 "crypto/ecdsa" 22 "errors" 23 "maps" 24 "reflect" 25 "testing" 26 "time" 27 28 "github.com/davecgh/go-spew/spew" 29 "github.com/ethereum/go-ethereum/common/hexutil" 30 "github.com/ethereum/go-ethereum/common/mclock" 31 "github.com/ethereum/go-ethereum/crypto" 32 "github.com/ethereum/go-ethereum/internal/testlog" 33 "github.com/ethereum/go-ethereum/log" 34 "github.com/ethereum/go-ethereum/p2p/enode" 35 "github.com/ethereum/go-ethereum/p2p/enr" 36 ) 37 38 var signingKeyForTesting, _ = crypto.ToECDSA(hexutil.MustDecode("0xdc599867fc513f8f5e2c2c9c489cde5e71362d1d9ec6e693e0de063236ed1240")) 39 40 func TestClientSyncTree(t *testing.T) { 41 nodes := []string{ 42 "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", 43 "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI", 44 "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", 45 } 46 47 r := mapResolver{ 48 "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", 49 "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", 50 "JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24", 51 "2XS2367YHAXJFGLZHVAWLQD4ZY.n": nodes[0], 52 "H4FHT4B454P6UXFD7JCYQ5PWDY.n": nodes[1], 53 "MHTDO6TMUBRIA2XWG5LUDACK24.n": nodes[2], 54 } 55 var ( 56 wantNodes = sortByID(parseNodes(nodes)) 57 wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"} 58 wantSeq = uint(1) 59 ) 60 61 c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 62 stree, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n") 63 if err != nil { 64 t.Fatal("sync error:", err) 65 } 66 if !reflect.DeepEqual(sortByID(stree.Nodes()), wantNodes) { 67 t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes)) 68 } 69 if !reflect.DeepEqual(stree.Links(), wantLinks) { 70 t.Errorf("wrong links in synced tree: %v", stree.Links()) 71 } 72 if stree.Seq() != wantSeq { 73 t.Errorf("synced tree has wrong seq: %d", stree.Seq()) 74 } 75 } 76 77 // In this test, syncing the tree fails because it contains an invalid ENR entry. 78 func TestClientSyncTreeBadNode(t *testing.T) { 79 // var b strings.Builder 80 // b.WriteString(enrPrefix) 81 // b.WriteString("-----") 82 // badHash := subdomain(&b) 83 // tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}) 84 // tree.entries[badHash] = &b 85 // tree.root.eroot = badHash 86 // url, _ := tree.Sign(signingKeyForTesting, "n") 87 // fmt.Println(url) 88 // fmt.Printf("%#v\n", tree.ToTXT("n")) 89 90 r := mapResolver{ 91 "n": "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA", 92 "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", 93 "INDMVBZEEQ4ESVYAKGIYU74EAA.n": "enr:-----", 94 } 95 c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 96 _, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n") 97 wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}} 98 if err != wantErr { 99 t.Fatalf("expected sync error %q, got %q", wantErr, err) 100 } 101 } 102 103 // This test checks that randomIterator finds all entries. 104 func TestIterator(t *testing.T) { 105 var ( 106 keys = testKeys(30) 107 nodes = testNodes(keys) 108 tree, url = makeTestTree("n", nodes, nil) 109 r = mapResolver(tree.ToTXT("n")) 110 ) 111 112 c := NewClient(Config{ 113 Resolver: r, 114 Logger: testlog.Logger(t, log.LvlTrace), 115 RateLimit: 500, 116 }) 117 it, err := c.NewIterator(url) 118 if err != nil { 119 t.Fatal(err) 120 } 121 122 checkIterator(t, it, nodes) 123 } 124 125 func TestIteratorCloseWithoutNext(t *testing.T) { 126 tree1, url1 := makeTestTree("t1", nil, nil) 127 c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) 128 it, err := c.NewIterator(url1) 129 if err != nil { 130 t.Fatal(err) 131 } 132 133 it.Close() 134 ok := it.Next() 135 if ok { 136 t.Fatal("Next returned true after Close") 137 } 138 } 139 140 // This test checks if closing randomIterator races. 141 func TestIteratorClose(t *testing.T) { 142 var ( 143 keys = testKeys(500) 144 nodes = testNodes(keys) 145 tree1, url1 = makeTestTree("t1", nodes, nil) 146 ) 147 148 c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) 149 it, err := c.NewIterator(url1) 150 if err != nil { 151 t.Fatal(err) 152 } 153 154 done := make(chan struct{}) 155 go func() { 156 for it.Next() { 157 _ = it.Node() 158 } 159 close(done) 160 }() 161 162 time.Sleep(50 * time.Millisecond) 163 it.Close() 164 <-done 165 } 166 167 // This test checks that randomIterator traverses linked trees as well as explicitly added trees. 168 func TestIteratorLinks(t *testing.T) { 169 var ( 170 keys = testKeys(40) 171 nodes = testNodes(keys) 172 tree1, url1 = makeTestTree("t1", nodes[:10], nil) 173 tree2, url2 = makeTestTree("t2", nodes[10:], []string{url1}) 174 ) 175 176 c := NewClient(Config{ 177 Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")), 178 Logger: testlog.Logger(t, log.LvlTrace), 179 RateLimit: 500, 180 }) 181 it, err := c.NewIterator(url2) 182 if err != nil { 183 t.Fatal(err) 184 } 185 186 checkIterator(t, it, nodes) 187 } 188 189 // This test verifies that randomIterator re-checks the root of the tree to catch 190 // updates to nodes. 191 func TestIteratorNodeUpdates(t *testing.T) { 192 var ( 193 clock = new(mclock.Simulated) 194 keys = testKeys(30) 195 nodes = testNodes(keys) 196 resolver = newMapResolver() 197 c = NewClient(Config{ 198 Resolver: resolver, 199 Logger: testlog.Logger(t, log.LvlTrace), 200 RecheckInterval: 20 * time.Minute, 201 RateLimit: 500, 202 }) 203 ) 204 c.clock = clock 205 tree1, url := makeTestTree("n", nodes[:25], nil) 206 it, err := c.NewIterator(url) 207 if err != nil { 208 t.Fatal(err) 209 } 210 211 // Sync the original tree. 212 resolver.add(tree1.ToTXT("n")) 213 checkIterator(t, it, nodes[:25]) 214 215 // Ensure RandomNode returns the new nodes after the tree is updated. 216 updateSomeNodes(keys, nodes) 217 tree2, _ := makeTestTree("n", nodes, nil) 218 clear(resolver) 219 resolver.add(tree2.ToTXT("n")) 220 t.Log("tree updated") 221 222 clock.Run(c.cfg.RecheckInterval + 1*time.Second) 223 checkIterator(t, it, nodes) 224 } 225 226 // This test checks that the tree root is rechecked when a couple of leaf 227 // requests have failed. The test is just like TestIteratorNodeUpdates, but 228 // without advancing the clock by recheckInterval after the tree update. 229 func TestIteratorRootRecheckOnFail(t *testing.T) { 230 var ( 231 clock = new(mclock.Simulated) 232 keys = testKeys(30) 233 nodes = testNodes(keys) 234 resolver = newMapResolver() 235 c = NewClient(Config{ 236 Resolver: resolver, 237 Logger: testlog.Logger(t, log.LvlTrace), 238 RecheckInterval: 20 * time.Minute, 239 RateLimit: 500, 240 // Disabling the cache is required for this test because the client doesn't 241 // notice leaf failures if all records are cached. 242 CacheLimit: 1, 243 }) 244 ) 245 c.clock = clock 246 tree1, url := makeTestTree("n", nodes[:25], nil) 247 it, err := c.NewIterator(url) 248 if err != nil { 249 t.Fatal(err) 250 } 251 252 // Sync the original tree. 253 resolver.add(tree1.ToTXT("n")) 254 checkIterator(t, it, nodes[:25]) 255 256 // Ensure RandomNode returns the new nodes after the tree is updated. 257 updateSomeNodes(keys, nodes) 258 tree2, _ := makeTestTree("n", nodes, nil) 259 clear(resolver) 260 resolver.add(tree2.ToTXT("n")) 261 t.Log("tree updated") 262 263 checkIterator(t, it, nodes) 264 } 265 266 // This test checks that the iterator works correctly when the tree is initially empty. 267 func TestIteratorEmptyTree(t *testing.T) { 268 var ( 269 clock = new(mclock.Simulated) 270 keys = testKeys(1) 271 nodes = testNodes(keys) 272 resolver = newMapResolver() 273 c = NewClient(Config{ 274 Resolver: resolver, 275 Logger: testlog.Logger(t, log.LvlTrace), 276 RecheckInterval: 20 * time.Minute, 277 RateLimit: 500, 278 }) 279 ) 280 c.clock = clock 281 tree1, url := makeTestTree("n", nil, nil) 282 tree2, _ := makeTestTree("n", nodes, nil) 283 resolver.add(tree1.ToTXT("n")) 284 285 // Start the iterator. 286 node := make(chan *enode.Node, 1) 287 it, err := c.NewIterator(url) 288 if err != nil { 289 t.Fatal(err) 290 } 291 go func() { 292 it.Next() 293 node <- it.Node() 294 }() 295 296 // Wait for the client to get stuck in waitForRootUpdates. 297 clock.WaitForTimers(1) 298 299 // Now update the root. 300 resolver.add(tree2.ToTXT("n")) 301 302 // Wait for it to pick up the root change. 303 clock.Run(c.cfg.RecheckInterval) 304 select { 305 case n := <-node: 306 if n.ID() != nodes[0].ID() { 307 t.Fatalf("wrong node returned") 308 } 309 case <-time.After(5 * time.Second): 310 t.Fatal("it.Next() did not unblock within 5s of real time") 311 } 312 } 313 314 // updateSomeNodes applies ENR updates to some of the given nodes. 315 func updateSomeNodes(keys []*ecdsa.PrivateKey, nodes []*enode.Node) { 316 for i, n := range nodes[:len(nodes)/2] { 317 r := n.Record() 318 r.Set(enr.IP{127, 0, 0, 1}) 319 r.SetSeq(55) 320 enode.SignV4(r, keys[i]) 321 n2, _ := enode.New(enode.ValidSchemes, r) 322 nodes[i] = n2 323 } 324 } 325 326 // This test verifies that randomIterator re-checks the root of the tree to catch 327 // updates to links. 328 func TestIteratorLinkUpdates(t *testing.T) { 329 var ( 330 clock = new(mclock.Simulated) 331 keys = testKeys(30) 332 nodes = testNodes(keys) 333 resolver = newMapResolver() 334 c = NewClient(Config{ 335 Resolver: resolver, 336 Logger: testlog.Logger(t, log.LvlTrace), 337 RecheckInterval: 20 * time.Minute, 338 RateLimit: 500, 339 }) 340 ) 341 c.clock = clock 342 tree3, url3 := makeTestTree("t3", nodes[20:30], nil) 343 tree2, url2 := makeTestTree("t2", nodes[10:20], nil) 344 tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2}) 345 resolver.add(tree1.ToTXT("t1")) 346 resolver.add(tree2.ToTXT("t2")) 347 resolver.add(tree3.ToTXT("t3")) 348 349 it, err := c.NewIterator(url1) 350 if err != nil { 351 t.Fatal(err) 352 } 353 354 // Sync tree1 using RandomNode. 355 checkIterator(t, it, nodes[:20]) 356 357 // Add link to tree3, remove link to tree2. 358 tree1, _ = makeTestTree("t1", nodes[:10], []string{url3}) 359 resolver.add(tree1.ToTXT("t1")) 360 t.Log("tree1 updated") 361 362 clock.Run(c.cfg.RecheckInterval + 1*time.Second) 363 364 var wantNodes []*enode.Node 365 wantNodes = append(wantNodes, tree1.Nodes()...) 366 wantNodes = append(wantNodes, tree3.Nodes()...) 367 checkIterator(t, it, wantNodes) 368 369 // Check that linked trees are GCed when they're no longer referenced. 370 knownTrees := it.(*randomIterator).trees 371 if len(knownTrees) != 2 { 372 t.Errorf("client knows %d trees, want 2", len(knownTrees)) 373 } 374 } 375 376 func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) { 377 t.Helper() 378 379 var ( 380 want = make(map[enode.ID]*enode.Node) 381 maxCalls = len(wantNodes) * 3 382 calls = 0 383 ) 384 for _, n := range wantNodes { 385 want[n.ID()] = n 386 } 387 for ; len(want) > 0 && calls < maxCalls; calls++ { 388 if !it.Next() { 389 t.Fatalf("Next returned false (call %d)", calls) 390 } 391 n := it.Node() 392 delete(want, n.ID()) 393 } 394 t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes)) 395 for _, n := range want { 396 t.Errorf("iterator didn't discover node %v", n.ID()) 397 } 398 } 399 400 func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) { 401 tree, err := MakeTree(1, nodes, links) 402 if err != nil { 403 panic(err) 404 } 405 url, err := tree.Sign(signingKeyForTesting, domain) 406 if err != nil { 407 panic(err) 408 } 409 return tree, url 410 } 411 412 // testKeys creates deterministic private keys for testing. 413 func testKeys(n int) []*ecdsa.PrivateKey { 414 keys := make([]*ecdsa.PrivateKey, n) 415 for i := 0; i < n; i++ { 416 key, err := crypto.GenerateKey() 417 if err != nil { 418 panic("can't generate key: " + err.Error()) 419 } 420 keys[i] = key 421 } 422 return keys 423 } 424 425 func testNodes(keys []*ecdsa.PrivateKey) []*enode.Node { 426 nodes := make([]*enode.Node, len(keys)) 427 for i, key := range keys { 428 record := new(enr.Record) 429 record.SetSeq(uint64(i)) 430 enode.SignV4(record, key) 431 n, err := enode.New(enode.ValidSchemes, record) 432 if err != nil { 433 panic(err) 434 } 435 nodes[i] = n 436 } 437 return nodes 438 } 439 440 type mapResolver map[string]string 441 442 func newMapResolver(maps ...map[string]string) mapResolver { 443 mr := make(mapResolver, len(maps)) 444 for _, m := range maps { 445 mr.add(m) 446 } 447 return mr 448 } 449 450 func (mr mapResolver) add(m map[string]string) { 451 maps.Copy(mr, m) 452 } 453 454 func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) { 455 if record, ok := mr[name]; ok { 456 return []string{record}, nil 457 } 458 return nil, errors.New("not found") 459 } 460 461 func parseNodes(rec []string) []*enode.Node { 462 var ns []*enode.Node 463 for _, r := range rec { 464 var n enode.Node 465 if err := n.UnmarshalText([]byte(r)); err != nil { 466 panic(err) 467 } 468 ns = append(ns, &n) 469 } 470 return ns 471 }