github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/dnsdisc/client_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package dnsdisc 19 20 import ( 21 "context" 22 "crypto/ecdsa" 23 "math/rand" 24 "reflect" 25 "testing" 26 "time" 27 28 "github.com/AigarNetwork/aigar/common/mclock" 29 "github.com/AigarNetwork/aigar/crypto" 30 "github.com/AigarNetwork/aigar/internal/testlog" 31 "github.com/AigarNetwork/aigar/log" 32 "github.com/AigarNetwork/aigar/p2p/enode" 33 "github.com/AigarNetwork/aigar/p2p/enr" 34 "github.com/davecgh/go-spew/spew" 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 "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", 46 "C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org", 47 "JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24", 48 "2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", 49 "H4FHT4B454P6UXFD7JCYQ5PWDY.n": "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI", 50 "MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", 51 } 52 var ( 53 wantNodes = testNodes(0x29452, 3) 54 wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@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://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@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 if len(c.trees) > 0 { 73 t.Errorf("tree from SyncTree added to client") 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(testKey(signingKeySeed), "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 RandomNode hits all entries. 104 func TestClientRandomNode(t *testing.T) { 105 nodes := testNodes(nodesSeed1, 30) 106 tree, url := makeTestTree("n", nodes, nil) 107 r := mapResolver(tree.ToTXT("n")) 108 c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)}) 109 if err := c.AddTree(url); err != nil { 110 t.Fatal(err) 111 } 112 113 checkRandomNode(t, c, nodes) 114 } 115 116 // This test checks that RandomNode traverses linked trees as well as explicitly added trees. 117 func TestClientRandomNodeLinks(t *testing.T) { 118 nodes := testNodes(nodesSeed1, 40) 119 tree1, url1 := makeTestTree("t1", nodes[:10], nil) 120 tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1}) 121 cfg := Config{ 122 Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")), 123 Logger: testlog.Logger(t, log.LvlTrace), 124 } 125 c, _ := NewClient(cfg) 126 if err := c.AddTree(url2); err != nil { 127 t.Fatal(err) 128 } 129 130 checkRandomNode(t, c, nodes) 131 } 132 133 // This test verifies that RandomNode re-checks the root of the tree to catch 134 // updates to nodes. 135 func TestClientRandomNodeUpdates(t *testing.T) { 136 var ( 137 clock = new(mclock.Simulated) 138 nodes = testNodes(nodesSeed1, 30) 139 resolver = newMapResolver() 140 cfg = Config{ 141 Resolver: resolver, 142 Logger: testlog.Logger(t, log.LvlTrace), 143 RecheckInterval: 20 * time.Minute, 144 } 145 c, _ = NewClient(cfg) 146 ) 147 c.clock = clock 148 tree1, url := makeTestTree("n", nodes[:25], nil) 149 150 // Sync the original tree. 151 resolver.add(tree1.ToTXT("n")) 152 c.AddTree(url) 153 checkRandomNode(t, c, nodes[:25]) 154 155 // Update some nodes and ensure RandomNode returns the new nodes as well. 156 keys := testKeys(nodesSeed1, len(nodes)) 157 for i, n := range nodes[:len(nodes)/2] { 158 r := n.Record() 159 r.Set(enr.IP{127, 0, 0, 1}) 160 r.SetSeq(55) 161 enode.SignV4(r, keys[i]) 162 n2, _ := enode.New(enode.ValidSchemes, r) 163 nodes[i] = n2 164 } 165 tree2, _ := makeTestTree("n", nodes, nil) 166 clock.Run(cfg.RecheckInterval + 1*time.Second) 167 resolver.clear() 168 resolver.add(tree2.ToTXT("n")) 169 checkRandomNode(t, c, nodes) 170 } 171 172 // This test verifies that RandomNode re-checks the root of the tree to catch 173 // updates to links. 174 func TestClientRandomNodeLinkUpdates(t *testing.T) { 175 var ( 176 clock = new(mclock.Simulated) 177 nodes = testNodes(nodesSeed1, 30) 178 resolver = newMapResolver() 179 cfg = Config{ 180 Resolver: resolver, 181 Logger: testlog.Logger(t, log.LvlTrace), 182 RecheckInterval: 20 * time.Minute, 183 } 184 c, _ = NewClient(cfg) 185 ) 186 c.clock = clock 187 tree3, url3 := makeTestTree("t3", nodes[20:30], nil) 188 tree2, url2 := makeTestTree("t2", nodes[10:20], nil) 189 tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2}) 190 resolver.add(tree1.ToTXT("t1")) 191 resolver.add(tree2.ToTXT("t2")) 192 resolver.add(tree3.ToTXT("t3")) 193 194 // Sync tree1 using RandomNode. 195 c.AddTree(url1) 196 checkRandomNode(t, c, nodes[:20]) 197 198 // Add link to tree3, remove link to tree2. 199 tree1, _ = makeTestTree("t1", nodes[:10], []string{url3}) 200 resolver.add(tree1.ToTXT("t1")) 201 clock.Run(cfg.RecheckInterval + 1*time.Second) 202 t.Log("tree1 updated") 203 204 var wantNodes []*enode.Node 205 wantNodes = append(wantNodes, tree1.Nodes()...) 206 wantNodes = append(wantNodes, tree3.Nodes()...) 207 checkRandomNode(t, c, wantNodes) 208 209 // Check that linked trees are GCed when they're no longer referenced. 210 if len(c.trees) != 2 { 211 t.Errorf("client knows %d trees, want 2", len(c.trees)) 212 } 213 } 214 215 func checkRandomNode(t *testing.T, c *Client, wantNodes []*enode.Node) { 216 t.Helper() 217 218 var ( 219 want = make(map[enode.ID]*enode.Node) 220 maxCalls = len(wantNodes) * 2 221 calls = 0 222 ctx = context.Background() 223 ) 224 for _, n := range wantNodes { 225 want[n.ID()] = n 226 } 227 for ; len(want) > 0 && calls < maxCalls; calls++ { 228 n := c.RandomNode(ctx) 229 if n == nil { 230 t.Fatalf("RandomNode returned nil (call %d)", calls) 231 } 232 delete(want, n.ID()) 233 } 234 t.Logf("checkRandomNode called RandomNode %d times to find %d nodes", calls, len(wantNodes)) 235 for _, n := range want { 236 t.Errorf("RandomNode didn't discover node %v", n.ID()) 237 } 238 } 239 240 func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) { 241 tree, err := MakeTree(1, nodes, links) 242 if err != nil { 243 panic(err) 244 } 245 url, err := tree.Sign(testKey(signingKeySeed), domain) 246 if err != nil { 247 panic(err) 248 } 249 return tree, url 250 } 251 252 // testKeys creates deterministic private keys for testing. 253 func testKeys(seed int64, n int) []*ecdsa.PrivateKey { 254 rand := rand.New(rand.NewSource(seed)) 255 keys := make([]*ecdsa.PrivateKey, n) 256 for i := 0; i < n; i++ { 257 key, err := ecdsa.GenerateKey(crypto.S256(), rand) 258 if err != nil { 259 panic("can't generate key: " + err.Error()) 260 } 261 keys[i] = key 262 } 263 return keys 264 } 265 266 func testKey(seed int64) *ecdsa.PrivateKey { 267 return testKeys(seed, 1)[0] 268 } 269 270 func testNodes(seed int64, n int) []*enode.Node { 271 keys := testKeys(seed, n) 272 nodes := make([]*enode.Node, n) 273 for i, key := range keys { 274 record := new(enr.Record) 275 record.SetSeq(uint64(i)) 276 enode.SignV4(record, key) 277 n, err := enode.New(enode.ValidSchemes, record) 278 if err != nil { 279 panic(err) 280 } 281 nodes[i] = n 282 } 283 return nodes 284 } 285 286 func testNode(seed int64) *enode.Node { 287 return testNodes(seed, 1)[0] 288 } 289 290 type mapResolver map[string]string 291 292 func newMapResolver(maps ...map[string]string) mapResolver { 293 mr := make(mapResolver) 294 for _, m := range maps { 295 mr.add(m) 296 } 297 return mr 298 } 299 300 func (mr mapResolver) clear() { 301 for k := range mr { 302 delete(mr, k) 303 } 304 } 305 306 func (mr mapResolver) add(m map[string]string) { 307 for k, v := range m { 308 mr[k] = v 309 } 310 } 311 312 func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) { 313 if record, ok := mr[name]; ok { 314 return []string{record}, nil 315 } 316 return nil, nil 317 }