github.com/Embreum/go-ethereum@v1.9.6/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/go-ethereum/common/mclock" 29 "github.com/ethereum/go-ethereum/crypto" 30 "github.com/ethereum/go-ethereum/internal/testlog" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/p2p/enode" 33 "github.com/ethereum/go-ethereum/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 "3CA2MBMUQ55ZCT74YEEQLANJDI.n": "enr=-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI=", 45 "53HBTPGGZ4I76UEPCNQGZWIPTQ.n": "enr=-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA=", 46 "BG7SVUBUAJ3UAWD2ATEBLMRNEE.n": "enrtree=53HBTPGGZ4I76UEPCNQGZWIPTQ,3CA2MBMUQ55ZCT74YEEQLANJDI,HNHR6UTVZF5TJKK3FV27ZI76P4", 47 "HNHR6UTVZF5TJKK3FV27ZI76P4.n": "enr=-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o=", 48 "JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", 49 "n": "enrtree-root=v1 e=BG7SVUBUAJ3UAWD2ATEBLMRNEE l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=1 sig=gacuU0nTy9duIdu1IFDyF5Lv9CFHqHiNcj91n0frw70tZo3tZZsCVkE3j1ILYyVOHRLWGBmawo_SEkThZ9PgcQE=", 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 if len(c.trees) > 0 { 72 t.Errorf("tree from SyncTree added to client") 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 r := mapResolver{ 79 "n": "enrtree-root=v1 e=ZFJZDQKSOMJRYYQSZKJZC54HCF l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=WEy8JTZ2dHmXM2qeBZ7D2ECK7SGbnurl1ge_S_5GQBAqnADk0gLTcg8Lm5QNqLHZjJKGAb443p996idlMcBqEQA=", 80 "JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", 81 "ZFJZDQKSOMJRYYQSZKJZC54HCF.n": "enr=gggggggggggggg=", 82 } 83 84 c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 85 _, err := c.SyncTree("enrtree://APFGGTFOBVE2ZNAB3CSMNNX6RRK3ODIRLP2AA5U4YFAA6MSYZUYTQ@n") 86 wantErr := nameError{name: "ZFJZDQKSOMJRYYQSZKJZC54HCF.n", err: entryError{typ: "enr", err: errInvalidENR}} 87 if err != wantErr { 88 t.Fatalf("expected sync error %q, got %q", wantErr, err) 89 } 90 } 91 92 // This test checks that RandomNode hits all entries. 93 func TestClientRandomNode(t *testing.T) { 94 nodes := testNodes(nodesSeed1, 30) 95 tree, url := makeTestTree("n", nodes, nil) 96 r := mapResolver(tree.ToTXT("n")) 97 c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 98 if err := c.AddTree(url); err != nil { 99 t.Fatal(err) 100 } 101 102 checkRandomNode(t, c, nodes) 103 } 104 105 // This test checks that RandomNode traverses linked trees as well as explicitly added trees. 106 func TestClientRandomNodeLinks(t *testing.T) { 107 nodes := testNodes(nodesSeed1, 40) 108 tree1, url1 := makeTestTree("t1", nodes[:10], nil) 109 tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1}) 110 cfg := Config{ 111 Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")), 112 Logger: testlog.Logger(t, log.LvlTrace), 113 } 114 c, _ := NewClient(cfg) 115 if err := c.AddTree(url2); err != nil { 116 t.Fatal(err) 117 } 118 119 checkRandomNode(t, c, nodes) 120 } 121 122 // This test verifies that RandomNode re-checks the root of the tree to catch 123 // updates to nodes. 124 func TestClientRandomNodeUpdates(t *testing.T) { 125 var ( 126 clock = new(mclock.Simulated) 127 nodes = testNodes(nodesSeed1, 30) 128 resolver = newMapResolver() 129 cfg = Config{ 130 Resolver: resolver, 131 Logger: testlog.Logger(t, log.LvlTrace), 132 RecheckInterval: 20 * time.Minute, 133 } 134 c, _ = NewClient(cfg) 135 ) 136 c.clock = clock 137 tree1, url := makeTestTree("n", nodes[:25], nil) 138 139 // Sync the original tree. 140 resolver.add(tree1.ToTXT("n")) 141 c.AddTree(url) 142 checkRandomNode(t, c, nodes[:25]) 143 144 // Update some nodes and ensure RandomNode returns the new nodes as well. 145 keys := testKeys(nodesSeed1, len(nodes)) 146 for i, n := range nodes[:len(nodes)/2] { 147 r := n.Record() 148 r.Set(enr.IP{127, 0, 0, 1}) 149 r.SetSeq(55) 150 enode.SignV4(r, keys[i]) 151 n2, _ := enode.New(enode.ValidSchemes, r) 152 nodes[i] = n2 153 } 154 tree2, _ := makeTestTree("n", nodes, nil) 155 clock.Run(cfg.RecheckInterval + 1*time.Second) 156 resolver.clear() 157 resolver.add(tree2.ToTXT("n")) 158 checkRandomNode(t, c, nodes) 159 } 160 161 // This test verifies that RandomNode re-checks the root of the tree to catch 162 // updates to links. 163 func TestClientRandomNodeLinkUpdates(t *testing.T) { 164 var ( 165 clock = new(mclock.Simulated) 166 nodes = testNodes(nodesSeed1, 30) 167 resolver = newMapResolver() 168 cfg = Config{ 169 Resolver: resolver, 170 Logger: testlog.Logger(t, log.LvlTrace), 171 RecheckInterval: 20 * time.Minute, 172 } 173 c, _ = NewClient(cfg) 174 ) 175 c.clock = clock 176 tree3, url3 := makeTestTree("t3", nodes[20:30], nil) 177 tree2, url2 := makeTestTree("t2", nodes[10:20], nil) 178 tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2}) 179 resolver.add(tree1.ToTXT("t1")) 180 resolver.add(tree2.ToTXT("t2")) 181 resolver.add(tree3.ToTXT("t3")) 182 183 // Sync tree1 using RandomNode. 184 c.AddTree(url1) 185 checkRandomNode(t, c, nodes[:20]) 186 187 // Add link to tree3, remove link to tree2. 188 tree1, _ = makeTestTree("t1", nodes[:10], []string{url3}) 189 resolver.add(tree1.ToTXT("t1")) 190 clock.Run(cfg.RecheckInterval + 1*time.Second) 191 t.Log("tree1 updated") 192 193 var wantNodes []*enode.Node 194 wantNodes = append(wantNodes, tree1.Nodes()...) 195 wantNodes = append(wantNodes, tree3.Nodes()...) 196 checkRandomNode(t, c, wantNodes) 197 198 // Check that linked trees are GCed when they're no longer referenced. 199 if len(c.trees) != 2 { 200 t.Errorf("client knows %d trees, want 2", len(c.trees)) 201 } 202 } 203 204 func checkRandomNode(t *testing.T, c *Client, wantNodes []*enode.Node) { 205 t.Helper() 206 207 var ( 208 want = make(map[enode.ID]*enode.Node) 209 maxCalls = len(wantNodes) * 2 210 calls = 0 211 ctx = context.Background() 212 ) 213 for _, n := range wantNodes { 214 want[n.ID()] = n 215 } 216 for ; len(want) > 0 && calls < maxCalls; calls++ { 217 n := c.RandomNode(ctx) 218 if n == nil { 219 t.Fatalf("RandomNode returned nil (call %d)", calls) 220 } 221 delete(want, n.ID()) 222 } 223 t.Logf("checkRandomNode called RandomNode %d times to find %d nodes", calls, len(wantNodes)) 224 for _, n := range want { 225 t.Errorf("RandomNode didn't discover node %v", n.ID()) 226 } 227 } 228 229 func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) { 230 tree, err := MakeTree(1, nodes, links) 231 if err != nil { 232 panic(err) 233 } 234 url, err := tree.Sign(testKey(signingKeySeed), domain) 235 if err != nil { 236 panic(err) 237 } 238 return tree, url 239 } 240 241 // testKeys creates deterministic private keys for testing. 242 func testKeys(seed int64, n int) []*ecdsa.PrivateKey { 243 rand := rand.New(rand.NewSource(seed)) 244 keys := make([]*ecdsa.PrivateKey, n) 245 for i := 0; i < n; i++ { 246 key, err := ecdsa.GenerateKey(crypto.S256(), rand) 247 if err != nil { 248 panic("can't generate key: " + err.Error()) 249 } 250 keys[i] = key 251 } 252 return keys 253 } 254 255 func testKey(seed int64) *ecdsa.PrivateKey { 256 return testKeys(seed, 1)[0] 257 } 258 259 func testNodes(seed int64, n int) []*enode.Node { 260 keys := testKeys(seed, n) 261 nodes := make([]*enode.Node, n) 262 for i, key := range keys { 263 record := new(enr.Record) 264 record.SetSeq(uint64(i)) 265 enode.SignV4(record, key) 266 n, err := enode.New(enode.ValidSchemes, record) 267 if err != nil { 268 panic(err) 269 } 270 nodes[i] = n 271 } 272 return nodes 273 } 274 275 func testNode(seed int64) *enode.Node { 276 return testNodes(seed, 1)[0] 277 } 278 279 type mapResolver map[string]string 280 281 func newMapResolver(maps ...map[string]string) mapResolver { 282 mr := make(mapResolver) 283 for _, m := range maps { 284 mr.add(m) 285 } 286 return mr 287 } 288 289 func (mr mapResolver) clear() { 290 for k := range mr { 291 delete(mr, k) 292 } 293 } 294 295 func (mr mapResolver) add(m map[string]string) { 296 for k, v := range m { 297 mr[k] = v 298 } 299 } 300 301 func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) { 302 if record, ok := mr[name]; ok { 303 return []string{record}, nil 304 } 305 return nil, nil 306 }