github.com/ethereum-optimism/optimism/l2geth@v0.0.0-20230612200230-50b04ade19e3/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 "math/rand" 23 "reflect" 24 "testing" 25 "time" 26 27 "github.com/davecgh/go-spew/spew" 28 "github.com/ethereum-optimism/optimism/l2geth/common/mclock" 29 "github.com/ethereum-optimism/optimism/l2geth/crypto" 30 "github.com/ethereum-optimism/optimism/l2geth/internal/testlog" 31 "github.com/ethereum-optimism/optimism/l2geth/log" 32 "github.com/ethereum-optimism/optimism/l2geth/p2p/enode" 33 "github.com/ethereum-optimism/optimism/l2geth/p2p/enr" 34 ) 35 36 const ( 37 signingKeySeed = 0x111111 38 nodesSeed1 = 0x2945237 39 nodesSeed2 = 0x4567299 40 ) 41 42 func TestClientSyncTree(t *testing.T) { 43 r := mapResolver{ 44 "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", 45 "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", 46 "JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24", 47 "2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", 48 "H4FHT4B454P6UXFD7JCYQ5PWDY.n": "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI", 49 "MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", 50 } 51 var ( 52 wantNodes = testNodes(0x29452, 3) 53 wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"} 54 wantSeq = uint(1) 55 ) 56 57 c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 58 stree, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n") 59 if err != nil { 60 t.Fatal("sync error:", err) 61 } 62 if !reflect.DeepEqual(sortByID(stree.Nodes()), sortByID(wantNodes)) { 63 t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes)) 64 } 65 if !reflect.DeepEqual(stree.Links(), wantLinks) { 66 t.Errorf("wrong links in synced tree: %v", stree.Links()) 67 } 68 if stree.Seq() != wantSeq { 69 t.Errorf("synced tree has wrong seq: %d", stree.Seq()) 70 } 71 } 72 73 // In this test, syncing the tree fails because it contains an invalid ENR entry. 74 func TestClientSyncTreeBadNode(t *testing.T) { 75 // var b strings.Builder 76 // b.WriteString(enrPrefix) 77 // b.WriteString("-----") 78 // badHash := subdomain(&b) 79 // tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}) 80 // tree.entries[badHash] = &b 81 // tree.root.eroot = badHash 82 // url, _ := tree.Sign(testKey(signingKeySeed), "n") 83 // fmt.Println(url) 84 // fmt.Printf("%#v\n", tree.ToTXT("n")) 85 86 r := mapResolver{ 87 "n": "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA", 88 "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", 89 "INDMVBZEEQ4ESVYAKGIYU74EAA.n": "enr:-----", 90 } 91 c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 92 _, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n") 93 wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}} 94 if err != wantErr { 95 t.Fatalf("expected sync error %q, got %q", wantErr, err) 96 } 97 } 98 99 // This test checks that randomIterator finds all entries. 100 func TestIterator(t *testing.T) { 101 nodes := testNodes(nodesSeed1, 30) 102 tree, url := makeTestTree("n", nodes, nil) 103 r := mapResolver(tree.ToTXT("n")) 104 c := NewClient(Config{ 105 Resolver: r, 106 Logger: testlog.Logger(t, log.LvlTrace), 107 RateLimit: 500, 108 }) 109 it, err := c.NewIterator(url) 110 if err != nil { 111 t.Fatal(err) 112 } 113 114 checkIterator(t, it, nodes) 115 } 116 117 // This test checks if closing randomIterator races. 118 func TestIteratorClose(t *testing.T) { 119 nodes := testNodes(nodesSeed1, 500) 120 tree1, url1 := makeTestTree("t1", nodes, nil) 121 c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) 122 it, err := c.NewIterator(url1) 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 done := make(chan struct{}) 128 go func() { 129 for it.Next() { 130 _ = it.Node() 131 } 132 close(done) 133 }() 134 135 time.Sleep(50 * time.Millisecond) 136 it.Close() 137 <-done 138 } 139 140 // This test checks that randomIterator traverses linked trees as well as explicitly added trees. 141 func TestIteratorLinks(t *testing.T) { 142 nodes := testNodes(nodesSeed1, 40) 143 tree1, url1 := makeTestTree("t1", nodes[:10], nil) 144 tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1}) 145 c := NewClient(Config{ 146 Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")), 147 Logger: testlog.Logger(t, log.LvlTrace), 148 RateLimit: 500, 149 }) 150 it, err := c.NewIterator(url2) 151 if err != nil { 152 t.Fatal(err) 153 } 154 155 checkIterator(t, it, nodes) 156 } 157 158 // This test verifies that randomIterator re-checks the root of the tree to catch 159 // updates to nodes. 160 func TestIteratorNodeUpdates(t *testing.T) { 161 var ( 162 clock = new(mclock.Simulated) 163 nodes = testNodes(nodesSeed1, 30) 164 resolver = newMapResolver() 165 c = NewClient(Config{ 166 Resolver: resolver, 167 Logger: testlog.Logger(t, log.LvlTrace), 168 RecheckInterval: 20 * time.Minute, 169 RateLimit: 500, 170 }) 171 ) 172 c.clock = clock 173 tree1, url := makeTestTree("n", nodes[:25], nil) 174 it, err := c.NewIterator(url) 175 if err != nil { 176 t.Fatal(err) 177 } 178 179 // sync the original tree. 180 resolver.add(tree1.ToTXT("n")) 181 checkIterator(t, it, nodes[:25]) 182 183 // Update some nodes and ensure RandomNode returns the new nodes as well. 184 keys := testKeys(nodesSeed1, len(nodes)) 185 for i, n := range nodes[:len(nodes)/2] { 186 r := n.Record() 187 r.Set(enr.IP{127, 0, 0, 1}) 188 r.SetSeq(55) 189 enode.SignV4(r, keys[i]) 190 n2, _ := enode.New(enode.ValidSchemes, r) 191 nodes[i] = n2 192 } 193 tree2, _ := makeTestTree("n", nodes, nil) 194 clock.Run(c.cfg.RecheckInterval + 1*time.Second) 195 resolver.clear() 196 resolver.add(tree2.ToTXT("n")) 197 checkIterator(t, it, nodes) 198 } 199 200 // This test verifies that randomIterator re-checks the root of the tree to catch 201 // updates to links. 202 func TestIteratorLinkUpdates(t *testing.T) { 203 var ( 204 clock = new(mclock.Simulated) 205 nodes = testNodes(nodesSeed1, 30) 206 resolver = newMapResolver() 207 c = NewClient(Config{ 208 Resolver: resolver, 209 Logger: testlog.Logger(t, log.LvlTrace), 210 RecheckInterval: 20 * time.Minute, 211 RateLimit: 500, 212 }) 213 ) 214 c.clock = clock 215 tree3, url3 := makeTestTree("t3", nodes[20:30], nil) 216 tree2, url2 := makeTestTree("t2", nodes[10:20], nil) 217 tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2}) 218 resolver.add(tree1.ToTXT("t1")) 219 resolver.add(tree2.ToTXT("t2")) 220 resolver.add(tree3.ToTXT("t3")) 221 222 it, err := c.NewIterator(url1) 223 if err != nil { 224 t.Fatal(err) 225 } 226 227 // Sync tree1 using RandomNode. 228 checkIterator(t, it, nodes[:20]) 229 230 // Add link to tree3, remove link to tree2. 231 tree1, _ = makeTestTree("t1", nodes[:10], []string{url3}) 232 resolver.add(tree1.ToTXT("t1")) 233 clock.Run(c.cfg.RecheckInterval + 1*time.Second) 234 t.Log("tree1 updated") 235 236 var wantNodes []*enode.Node 237 wantNodes = append(wantNodes, tree1.Nodes()...) 238 wantNodes = append(wantNodes, tree3.Nodes()...) 239 checkIterator(t, it, wantNodes) 240 241 // Check that linked trees are GCed when they're no longer referenced. 242 knownTrees := it.(*randomIterator).trees 243 if len(knownTrees) != 2 { 244 t.Errorf("client knows %d trees, want 2", len(knownTrees)) 245 } 246 } 247 248 func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) { 249 t.Helper() 250 251 var ( 252 want = make(map[enode.ID]*enode.Node) 253 maxCalls = len(wantNodes) * 3 254 calls = 0 255 ) 256 for _, n := range wantNodes { 257 want[n.ID()] = n 258 } 259 for ; len(want) > 0 && calls < maxCalls; calls++ { 260 if !it.Next() { 261 t.Fatalf("Next returned false (call %d)", calls) 262 } 263 n := it.Node() 264 delete(want, n.ID()) 265 } 266 t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes)) 267 for _, n := range want { 268 t.Errorf("iterator didn't discover node %v", n.ID()) 269 } 270 } 271 272 func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) { 273 tree, err := MakeTree(1, nodes, links) 274 if err != nil { 275 panic(err) 276 } 277 url, err := tree.Sign(testKey(signingKeySeed), domain) 278 if err != nil { 279 panic(err) 280 } 281 return tree, url 282 } 283 284 // testKeys creates deterministic private keys for testing. 285 func testKeys(seed int64, n int) []*ecdsa.PrivateKey { 286 rand := rand.New(rand.NewSource(seed)) 287 keys := make([]*ecdsa.PrivateKey, n) 288 for i := 0; i < n; i++ { 289 key, err := ecdsa.GenerateKey(crypto.S256(), rand) 290 if err != nil { 291 panic("can't generate key: " + err.Error()) 292 } 293 keys[i] = key 294 } 295 return keys 296 } 297 298 func testKey(seed int64) *ecdsa.PrivateKey { 299 return testKeys(seed, 1)[0] 300 } 301 302 func testNodes(seed int64, n int) []*enode.Node { 303 keys := testKeys(seed, n) 304 nodes := make([]*enode.Node, n) 305 for i, key := range keys { 306 record := new(enr.Record) 307 record.SetSeq(uint64(i)) 308 enode.SignV4(record, key) 309 n, err := enode.New(enode.ValidSchemes, record) 310 if err != nil { 311 panic(err) 312 } 313 nodes[i] = n 314 } 315 return nodes 316 } 317 318 func testNode(seed int64) *enode.Node { 319 return testNodes(seed, 1)[0] 320 } 321 322 type mapResolver map[string]string 323 324 func newMapResolver(maps ...map[string]string) mapResolver { 325 mr := make(mapResolver) 326 for _, m := range maps { 327 mr.add(m) 328 } 329 return mr 330 } 331 332 func (mr mapResolver) clear() { 333 for k := range mr { 334 delete(mr, k) 335 } 336 } 337 338 func (mr mapResolver) add(m map[string]string) { 339 for k, v := range m { 340 mr[k] = v 341 } 342 } 343 344 func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) { 345 if record, ok := mr[name]; ok { 346 return []string{record}, nil 347 } 348 return nil, nil 349 }