github.com/core-coin/go-core/v2@v2.1.9/p2p/dnsdisc/client_test.go (about) 1 // Copyright 2019 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 dnsdisc 18 19 import ( 20 "context" 21 "errors" 22 "math/rand" 23 "reflect" 24 "testing" 25 "time" 26 27 "github.com/davecgh/go-spew/spew" 28 29 "github.com/core-coin/go-core/v2/common/mclock" 30 "github.com/core-coin/go-core/v2/crypto" 31 "github.com/core-coin/go-core/v2/internal/testlog" 32 "github.com/core-coin/go-core/v2/log" 33 "github.com/core-coin/go-core/v2/p2p/enode" 34 "github.com/core-coin/go-core/v2/p2p/enr" 35 ) 36 37 const ( 38 signingKeySeed = 0x111111 39 nodesSeed1 = 0x2945237 40 nodesSeed2 = 0x4567299 41 ) 42 43 func TestClientSyncTree(t *testing.T) { 44 r := mapResolver{ 45 "AZ2AA7473KDOKV7PC3GMHBXZSE.n": "enr:-Pm4q1Jkab2APz8czlz1s8NxAZYGjIutJ0Or9tucWsbQnzA5uGrgqeOt2jmV3zC423-V6pGEto25A6fwgH-BxlojPGJwKfBv-kAt3p2WpWCuXsrv58mGFDV18Si0RPzVk04Jact6D2STKiyQDhasrWw94dUcAHxVxTpa-ZmsVbDRxaijR6cRzDr6EuJ_zNZ5PIJtnip-37g-dI_K44yrJZK97PnOaqlrtHrjykxVAICCaWSCdjSJc2VjcDI1NmsxuDl8VcU6WvmZrFWw0cWoo0enEcw6-hLif8zWeTyCbZ4qft-4PnSPyuOMqyWSvez5zmqpa7R648pMVQA", 46 "FFWEK2OQ5VIQR6UVZCTXPYTWI4.n": "enr:-Pm4q-56O3Ed1neHk_0leSRDqNm-uCqxBbekTF7vZJoVC3UAKbs_ozkQaDMBa7yIQdrhGfbHlah7wAOPAIjBJ6JHRJNQBevMWIbLCY3XoGROUrmVhoyUdB3LL8STkVSurs5VLJbWuQF2IUac7UOILSsD3Xg2AKMHAoZkYa-RtQVo4_9_l47u_sO-Ggberg4RJkamAZS-cMx5boa9vO7_8Cxk6YZLRzXEXGqhJa0tgAKCaWSCdjSJc2VjcDI1NmsxuDmjBwKGZGGvkbUFaOP_f5eO7v7DvhoG3q4OESZGpgGUvnDMeW6Gvbzu__AsZOmGS0c1xFxqoSWtLYA", 47 "RESRN326FX43MSLLCGJTO64QNE.n": "enr:-Pm4q4K4eHfS_nw1XZzCEzcx2wE3xwgOcEcrbD8xduY9lahR9Y99FKuo38r73EcyFashWcCvw6KFOd_ggPKhgG0g9D-iBt5oH27IRFWhfes-mhh41SGAxn2hOolcRH6N-QZma9orGk9b6wFnyikIEtf3DsMsANbRV07JV2YasbfV8oJTJyN3DSaRp3Mocpg1LJl6aV9sapqrZR46_JRrNZPfG8oWrU8YJAbfcxHBAAGCaWSCdjSJc2VjcDI1NmsxuDnW0VdOyVdmGrG31fKCUycjdw0mkadzKHKYNSyZemlfbGqaq2UeOvyUazWT3xvKFq1PGCQG33MRwQA", 48 "NBR5UGUKPKHBEV6MNKYLWA76UA.n": "enrtree-branch:AZ2AA7473KDOKV7PC3GMHBXZSE,RESRN326FX43MSLLCGJTO64QNE,FFWEK2OQ5VIQR6UVZCTXPYTWI4", 49 "HBRIBBF355HFMMLJX4ATX3PRRY.n": "enrtree://JJ5QVSTC2YDAIZSUEKUDYBBZHADEQTEWDG4TNQTIWUJGMOT52SWKKRUYMIE4KVJMTJYKRA4G4SW7QZTXNKEPAL3HIQAA@morenodes.example.org", 50 "n": "enrtree-root:v1 e=NBR5UGUKPKHBEV6MNKYLWA76UA l=HBRIBBF355HFMMLJX4ATX3PRRY seq=1 sig=0-nBiy1P3GB-CsXUL4vR_WKoY7UK0bYIjFCvhrxTd64A_Cg1Y3UOPQpWo-FLdLdEHWbri-xL_ocAS7yG_Mn8-QCmQZAG_7apeT9NYp4dAbscIdX0J1YJdjxeWSVN1t4UU5a5ixdkNHfOthEtGN8TjSIAhR3c_l0JEw3XUFpFrCjAQu-MCmR7bhcFQ_-KFwrG6r_1oQ2Q_-glxs9NYZz2ueAgQ8Lm0te4tZsA", 51 } 52 var ( 53 wantNodes = testNodes(0x29452, 3) 54 wantLinks = []string{"enrtree://JJ5QVSTC2YDAIZSUEKUDYBBZHADEQTEWDG4TNQTIWUJGMOT52SWKKRUYMIE4KVJMTJYKRA4G4SW7QZTXNKEPAL3HIQAA@morenodes.example.org"} 55 wantSeq = uint(1) 56 ) 57 58 c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 59 stree, err := c.SyncTree("enrtree://QUO5Z7S5BEJQ3V2QLJC2YKGAILXYYCTEPNXBOBKD76FBOCWG5K77LIINSD76QJOGZ5GWDHHWXHQCAQ6C43JNPOFVTMAA@n") 60 if err != nil { 61 t.Fatal("sync error:", err) 62 } 63 if !reflect.DeepEqual(sortByID(stree.Nodes()), sortByID(wantNodes)) { 64 t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes)) 65 } 66 if !reflect.DeepEqual(stree.Links(), wantLinks) { 67 t.Errorf("wrong links in synced tree: %v", stree.Links()) 68 } 69 if stree.Seq() != wantSeq { 70 t.Errorf("synced tree has wrong seq: %d", stree.Seq()) 71 } 72 } 73 74 // In this test, syncing the tree fails because it contains an invalid ENR entry. 75 func TestClientSyncTreeBadNode(t *testing.T) { 76 // var b strings.Builder 77 // b.WriteString(enrPrefix) 78 // b.WriteString("-----") 79 // badHash := subdomain(&b) 80 // tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}) 81 // tree.entries[badHash] = &b 82 // tree.root.eroot = badHash 83 // url, _ := tree.Sign(testKey(signingKeySeed), "n") 84 // fmt.Println(url) 85 // fmt.Printf("%#v\n", tree.ToTXT("n")) 86 87 r := mapResolver{ 88 "LOOO2ZWKOLDJW2LQVMYL6UT77U.n": "enrtree-branch:", 89 "JZ7RET254NLCICBLV5AZ3WVUHA.n": "enr:-----", 90 "LCR3TNLW2SMSEFKARSGSOWUOEI.n": "enrtree://VOCWVGXWWC3FDXJPIO26CIMTMUXMC4A4JWTPDQGSUNTKYS45VPEUGPXQTZA4UEUVKK6SYAUQQ3M3ANQE32DSUOZUGICA@morenodes.example.org", 91 "n": "enrtree-root:v1 e=JZ7RET254NLCICBLV5AZ3WVUHA l=LCR3TNLW2SMSEFKARSGSOWUOEI seq=3 sig=mmG0XKqV42QjGtfKY0LmkstO8As69v0XqFhY-Nv64AMu1Uj0SRXLn-B6bX6HDGhqxgVYNxLwEvcA_Z_EAP6MnRly1tj_QL7kKyF8lNlQtNSj6Rd6U7fvlO-ItaKfXT-Kgvx1ao3L2NWFwFwJtOXAphwAhR3c_l0JEw3XUFpFrCjAQu-MCmR7bhcFQ_-KFwrG6r_1oQ2Q_-glxs9NYZz2ueAgQ8Lm0te4tZsA", 92 } 93 c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 94 _, err := c.SyncTree("enrtree://QUO5Z7S5BEJQ3V2QLJC2YKGAILXYYCTEPNXBOBKD76FBOCWG5K77LIINSD76QJOGZ5GWDHHWXHQCAQ6C43JNPOFVTMAA@n") 95 wantErr := nameError{name: "JZ7RET254NLCICBLV5AZ3WVUHA.n", err: entryError{typ: "enr", err: errInvalidENR}} 96 if err != wantErr { 97 t.Fatalf("expected sync error %q, got %q", wantErr, err) 98 } 99 } 100 101 // This test checks that randomIterator finds all entries. 102 func TestIterator(t *testing.T) { 103 nodes := testNodes(nodesSeed1, 30) 104 tree, url := makeTestTree("n", nodes, nil) 105 r := mapResolver(tree.ToTXT("n")) 106 c := NewClient(Config{ 107 Resolver: r, 108 Logger: testlog.Logger(t, log.LvlTrace), 109 RateLimit: 500, 110 }) 111 it, err := c.NewIterator(url) 112 if err != nil { 113 t.Fatal(err) 114 } 115 116 checkIterator(t, it, nodes) 117 } 118 119 // This test checks if closing randomIterator races. 120 func TestIteratorClose(t *testing.T) { 121 nodes := testNodes(nodesSeed1, 500) 122 tree1, url1 := makeTestTree("t1", nodes, nil) 123 c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) 124 it, err := c.NewIterator(url1) 125 if err != nil { 126 t.Fatal(err) 127 } 128 129 done := make(chan struct{}) 130 go func() { 131 for it.Next() { 132 _ = it.Node() 133 } 134 close(done) 135 }() 136 137 time.Sleep(50 * time.Millisecond) 138 it.Close() 139 <-done 140 } 141 142 // This test checks that randomIterator traverses linked trees as well as explicitly added trees. 143 func TestIteratorLinks(t *testing.T) { 144 nodes := testNodes(nodesSeed1, 40) 145 tree1, url1 := makeTestTree("t1", nodes[:10], nil) 146 tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1}) 147 c := NewClient(Config{ 148 Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")), 149 Logger: testlog.Logger(t, log.LvlTrace), 150 RateLimit: 500, 151 }) 152 it, err := c.NewIterator(url2) 153 if err != nil { 154 t.Fatal(err) 155 } 156 157 checkIterator(t, it, nodes) 158 } 159 160 // This test verifies that randomIterator re-checks the root of the tree to catch 161 // updates to nodes. 162 func TestIteratorNodeUpdates(t *testing.T) { 163 var ( 164 clock = new(mclock.Simulated) 165 nodes = testNodes(nodesSeed1, 30) 166 resolver = newMapResolver() 167 c = NewClient(Config{ 168 Resolver: resolver, 169 Logger: testlog.Logger(t, log.LvlTrace), 170 RecheckInterval: 20 * time.Minute, 171 RateLimit: 500, 172 }) 173 ) 174 c.clock = clock 175 tree1, url := makeTestTree("n", nodes[:25], nil) 176 it, err := c.NewIterator(url) 177 if err != nil { 178 t.Fatal(err) 179 } 180 181 // Sync the original tree. 182 resolver.add(tree1.ToTXT("n")) 183 checkIterator(t, it, nodes[:25]) 184 185 // Ensure RandomNode returns the new nodes after the tree is updated. 186 updateSomeNodes(nodesSeed1, nodes) 187 tree2, _ := makeTestTree("n", nodes, nil) 188 resolver.clear() 189 resolver.add(tree2.ToTXT("n")) 190 t.Log("tree updated") 191 192 clock.Run(c.cfg.RecheckInterval + 1*time.Second) 193 checkIterator(t, it, nodes) 194 } 195 196 // This test checks that the tree root is rechecked when a couple of leaf 197 // requests have failed. The test is just like TestIteratorNodeUpdates, but 198 // without advancing the clock by recheckInterval after the tree update. 199 func TestIteratorRootRecheckOnFail(t *testing.T) { 200 var ( 201 clock = new(mclock.Simulated) 202 nodes = testNodes(nodesSeed1, 30) 203 resolver = newMapResolver() 204 c = NewClient(Config{ 205 Resolver: resolver, 206 Logger: testlog.Logger(t, log.LvlTrace), 207 RecheckInterval: 20 * time.Minute, 208 RateLimit: 500, 209 // Disabling the cache is required for this test because the client doesn't 210 // notice leaf failures if all records are cached. 211 CacheLimit: 1, 212 }) 213 ) 214 c.clock = clock 215 tree1, url := makeTestTree("n", nodes[:25], nil) 216 it, err := c.NewIterator(url) 217 if err != nil { 218 t.Fatal(err) 219 } 220 221 // Sync the original tree. 222 resolver.add(tree1.ToTXT("n")) 223 checkIterator(t, it, nodes[:25]) 224 225 // Ensure RandomNode returns the new nodes after the tree is updated. 226 updateSomeNodes(nodesSeed1, nodes) 227 tree2, _ := makeTestTree("n", nodes, nil) 228 resolver.clear() 229 resolver.add(tree2.ToTXT("n")) 230 t.Log("tree updated") 231 232 checkIterator(t, it, nodes) 233 } 234 235 // updateSomeNodes applies ENR updates to some of the given nodes. 236 func updateSomeNodes(keySeed int64, nodes []*enode.Node) { 237 keys := testKeys(nodesSeed1, len(nodes)) 238 for i, n := range nodes[:len(nodes)/2] { 239 r := n.Record() 240 r.Set(enr.IP{127, 0, 0, 1}) 241 r.SetSeq(55) 242 enode.SignV4(r, keys[i]) 243 n2, _ := enode.New(enode.ValidSchemes, r) 244 nodes[i] = n2 245 } 246 } 247 248 // This test verifies that randomIterator re-checks the root of the tree to catch 249 // updates to links. 250 func TestIteratorLinkUpdates(t *testing.T) { 251 var ( 252 clock = new(mclock.Simulated) 253 nodes = testNodes(nodesSeed1, 30) 254 resolver = newMapResolver() 255 c = NewClient(Config{ 256 Resolver: resolver, 257 Logger: testlog.Logger(t, log.LvlTrace), 258 RecheckInterval: 20 * time.Minute, 259 RateLimit: 500, 260 }) 261 ) 262 c.clock = clock 263 tree3, url3 := makeTestTree("t3", nodes[20:30], nil) 264 tree2, url2 := makeTestTree("t2", nodes[10:20], nil) 265 tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2}) 266 resolver.add(tree1.ToTXT("t1")) 267 resolver.add(tree2.ToTXT("t2")) 268 resolver.add(tree3.ToTXT("t3")) 269 270 it, err := c.NewIterator(url1) 271 if err != nil { 272 t.Fatal(err) 273 } 274 275 // Sync tree1 using RandomNode. 276 checkIterator(t, it, nodes[:20]) 277 278 // Add link to tree3, remove link to tree2. 279 tree1, _ = makeTestTree("t1", nodes[:10], []string{url3}) 280 resolver.add(tree1.ToTXT("t1")) 281 t.Log("tree1 updated") 282 283 clock.Run(c.cfg.RecheckInterval + 1*time.Second) 284 285 var wantNodes []*enode.Node 286 wantNodes = append(wantNodes, tree1.Nodes()...) 287 wantNodes = append(wantNodes, tree3.Nodes()...) 288 checkIterator(t, it, wantNodes) 289 290 // Check that linked trees are GCed when they're no longer referenced. 291 knownTrees := it.(*randomIterator).trees 292 if len(knownTrees) != 2 { 293 t.Errorf("client knows %d trees, want 2", len(knownTrees)) 294 } 295 } 296 297 func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) { 298 t.Helper() 299 300 var ( 301 want = make(map[enode.ID]*enode.Node) 302 maxCalls = len(wantNodes) * 3 303 calls = 0 304 ) 305 for _, n := range wantNodes { 306 want[n.ID()] = n 307 } 308 for ; len(want) > 0 && calls < maxCalls; calls++ { 309 if !it.Next() { 310 t.Fatalf("Next returned false (call %d)", calls) 311 } 312 n := it.Node() 313 delete(want, n.ID()) 314 } 315 t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes)) 316 for _, n := range want { 317 t.Errorf("iterator didn't discover node %v", n.ID()) 318 } 319 } 320 321 func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) { 322 tree, err := MakeTree(1, nodes, links) 323 if err != nil { 324 panic(err) 325 } 326 url, err := tree.Sign(testKey(signingKeySeed), domain) 327 if err != nil { 328 panic(err) 329 } 330 return tree, url 331 } 332 333 // testKeys creates deterministic private keys for testing. 334 func testKeys(seed int64, n int) []*crypto.PrivateKey { 335 rand := rand.New(rand.NewSource(seed)) 336 keys := make([]*crypto.PrivateKey, n) 337 for i := 0; i < n; i++ { 338 key, err := crypto.GenerateKey(rand) 339 if err != nil { 340 panic("can't generate key: " + err.Error()) 341 } 342 keys[i] = key 343 } 344 return keys 345 } 346 347 func testKey(seed int64) *crypto.PrivateKey { 348 return testKeys(seed, 1)[0] 349 } 350 351 func testNodes(seed int64, n int) []*enode.Node { 352 keys := testKeys(seed, n) 353 nodes := make([]*enode.Node, n) 354 for i, key := range keys { 355 record := new(enr.Record) 356 record.SetSeq(uint64(i)) 357 enode.SignV4(record, key) 358 n, err := enode.New(enode.ValidSchemes, record) 359 if err != nil { 360 panic(err) 361 } 362 nodes[i] = n 363 } 364 return nodes 365 } 366 367 func testNode(seed int64) *enode.Node { 368 return testNodes(seed, 1)[0] 369 } 370 371 type mapResolver map[string]string 372 373 func newMapResolver(maps ...map[string]string) mapResolver { 374 mr := make(mapResolver) 375 for _, m := range maps { 376 mr.add(m) 377 } 378 return mr 379 } 380 381 func (mr mapResolver) clear() { 382 for k := range mr { 383 delete(mr, k) 384 } 385 } 386 387 func (mr mapResolver) add(m map[string]string) { 388 for k, v := range m { 389 mr[k] = v 390 } 391 } 392 393 func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) { 394 if record, ok := mr[name]; ok { 395 return []string{record}, nil 396 } 397 return nil, errors.New("not found") 398 }