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