client_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. package dnsdisc
  17. import (
  18. "context"
  19. "crypto/ecdsa"
  20. "math/rand"
  21. "reflect"
  22. "testing"
  23. "time"
  24. "github.com/davecgh/go-spew/spew"
  25. "github.com/ethereum/go-ethereum/common/mclock"
  26. "github.com/ethereum/go-ethereum/crypto"
  27. "github.com/ethereum/go-ethereum/internal/testlog"
  28. "github.com/ethereum/go-ethereum/log"
  29. "github.com/ethereum/go-ethereum/p2p/enode"
  30. "github.com/ethereum/go-ethereum/p2p/enr"
  31. )
  32. const (
  33. signingKeySeed = 0x111111
  34. nodesSeed1 = 0x2945237
  35. nodesSeed2 = 0x4567299
  36. )
  37. func TestClientSyncTree(t *testing.T) {
  38. r := mapResolver{
  39. "3CA2MBMUQ55ZCT74YEEQLANJDI.n": "enr=-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI=",
  40. "53HBTPGGZ4I76UEPCNQGZWIPTQ.n": "enr=-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA=",
  41. "BG7SVUBUAJ3UAWD2ATEBLMRNEE.n": "enrtree=53HBTPGGZ4I76UEPCNQGZWIPTQ,3CA2MBMUQ55ZCT74YEEQLANJDI,HNHR6UTVZF5TJKK3FV27ZI76P4",
  42. "HNHR6UTVZF5TJKK3FV27ZI76P4.n": "enr=-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o=",
  43. "JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
  44. "n": "enrtree-root=v1 e=BG7SVUBUAJ3UAWD2ATEBLMRNEE l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=1 sig=gacuU0nTy9duIdu1IFDyF5Lv9CFHqHiNcj91n0frw70tZo3tZZsCVkE3j1ILYyVOHRLWGBmawo_SEkThZ9PgcQE=",
  45. }
  46. var (
  47. wantNodes = testNodes(0x29452, 3)
  48. wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}
  49. wantSeq = uint(1)
  50. )
  51. c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
  52. stree, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
  53. if err != nil {
  54. t.Fatal("sync error:", err)
  55. }
  56. if !reflect.DeepEqual(sortByID(stree.Nodes()), sortByID(wantNodes)) {
  57. t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes))
  58. }
  59. if !reflect.DeepEqual(stree.Links(), wantLinks) {
  60. t.Errorf("wrong links in synced tree: %v", stree.Links())
  61. }
  62. if stree.Seq() != wantSeq {
  63. t.Errorf("synced tree has wrong seq: %d", stree.Seq())
  64. }
  65. if len(c.trees) > 0 {
  66. t.Errorf("tree from SyncTree added to client")
  67. }
  68. }
  69. // In this test, syncing the tree fails because it contains an invalid ENR entry.
  70. func TestClientSyncTreeBadNode(t *testing.T) {
  71. r := mapResolver{
  72. "n": "enrtree-root=v1 e=ZFJZDQKSOMJRYYQSZKJZC54HCF l=JGUFMSAGI7KZYB3P7IZW4S5Y3A seq=3 sig=WEy8JTZ2dHmXM2qeBZ7D2ECK7SGbnurl1ge_S_5GQBAqnADk0gLTcg8Lm5QNqLHZjJKGAb443p996idlMcBqEQA=",
  73. "JGUFMSAGI7KZYB3P7IZW4S5Y3A.n": "enrtree-link=AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
  74. "ZFJZDQKSOMJRYYQSZKJZC54HCF.n": "enr=gggggggggggggg=",
  75. }
  76. c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
  77. _, err := c.SyncTree("enrtree://APFGGTFOBVE2ZNAB3CSMNNX6RRK3ODIRLP2AA5U4YFAA6MSYZUYTQ@n")
  78. wantErr := nameError{name: "ZFJZDQKSOMJRYYQSZKJZC54HCF.n", err: entryError{typ: "enr", err: errInvalidENR}}
  79. if err != wantErr {
  80. t.Fatalf("expected sync error %q, got %q", wantErr, err)
  81. }
  82. }
  83. // This test checks that RandomNode hits all entries.
  84. func TestClientRandomNode(t *testing.T) {
  85. nodes := testNodes(nodesSeed1, 30)
  86. tree, url := makeTestTree("n", nodes, nil)
  87. r := mapResolver(tree.ToTXT("n"))
  88. c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
  89. if err := c.AddTree(url); err != nil {
  90. t.Fatal(err)
  91. }
  92. checkRandomNode(t, c, nodes)
  93. }
  94. // This test checks that RandomNode traverses linked trees as well as explicitly added trees.
  95. func TestClientRandomNodeLinks(t *testing.T) {
  96. nodes := testNodes(nodesSeed1, 40)
  97. tree1, url1 := makeTestTree("t1", nodes[:10], nil)
  98. tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1})
  99. cfg := Config{
  100. Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")),
  101. Logger: testlog.Logger(t, log.LvlTrace),
  102. }
  103. c, _ := NewClient(cfg)
  104. if err := c.AddTree(url2); err != nil {
  105. t.Fatal(err)
  106. }
  107. checkRandomNode(t, c, nodes)
  108. }
  109. // This test verifies that RandomNode re-checks the root of the tree to catch
  110. // updates to nodes.
  111. func TestClientRandomNodeUpdates(t *testing.T) {
  112. var (
  113. clock = new(mclock.Simulated)
  114. nodes = testNodes(nodesSeed1, 30)
  115. resolver = newMapResolver()
  116. cfg = Config{
  117. Resolver: resolver,
  118. Logger: testlog.Logger(t, log.LvlTrace),
  119. RecheckInterval: 20 * time.Minute,
  120. }
  121. c, _ = NewClient(cfg)
  122. )
  123. c.clock = clock
  124. tree1, url := makeTestTree("n", nodes[:25], nil)
  125. // Sync the original tree.
  126. resolver.add(tree1.ToTXT("n"))
  127. c.AddTree(url)
  128. checkRandomNode(t, c, nodes[:25])
  129. // Update some nodes and ensure RandomNode returns the new nodes as well.
  130. keys := testKeys(nodesSeed1, len(nodes))
  131. for i, n := range nodes[:len(nodes)/2] {
  132. r := n.Record()
  133. r.Set(enr.IP{127, 0, 0, 1})
  134. r.SetSeq(55)
  135. enode.SignV4(r, keys[i])
  136. n2, _ := enode.New(enode.ValidSchemes, r)
  137. nodes[i] = n2
  138. }
  139. tree2, _ := makeTestTree("n", nodes, nil)
  140. clock.Run(cfg.RecheckInterval + 1*time.Second)
  141. resolver.clear()
  142. resolver.add(tree2.ToTXT("n"))
  143. checkRandomNode(t, c, nodes)
  144. }
  145. // This test verifies that RandomNode re-checks the root of the tree to catch
  146. // updates to links.
  147. func TestClientRandomNodeLinkUpdates(t *testing.T) {
  148. var (
  149. clock = new(mclock.Simulated)
  150. nodes = testNodes(nodesSeed1, 30)
  151. resolver = newMapResolver()
  152. cfg = Config{
  153. Resolver: resolver,
  154. Logger: testlog.Logger(t, log.LvlTrace),
  155. RecheckInterval: 20 * time.Minute,
  156. }
  157. c, _ = NewClient(cfg)
  158. )
  159. c.clock = clock
  160. tree3, url3 := makeTestTree("t3", nodes[20:30], nil)
  161. tree2, url2 := makeTestTree("t2", nodes[10:20], nil)
  162. tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2})
  163. resolver.add(tree1.ToTXT("t1"))
  164. resolver.add(tree2.ToTXT("t2"))
  165. resolver.add(tree3.ToTXT("t3"))
  166. // Sync tree1 using RandomNode.
  167. c.AddTree(url1)
  168. checkRandomNode(t, c, nodes[:20])
  169. // Add link to tree3, remove link to tree2.
  170. tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
  171. resolver.add(tree1.ToTXT("t1"))
  172. clock.Run(cfg.RecheckInterval + 1*time.Second)
  173. t.Log("tree1 updated")
  174. var wantNodes []*enode.Node
  175. wantNodes = append(wantNodes, tree1.Nodes()...)
  176. wantNodes = append(wantNodes, tree3.Nodes()...)
  177. checkRandomNode(t, c, wantNodes)
  178. // Check that linked trees are GCed when they're no longer referenced.
  179. if len(c.trees) != 2 {
  180. t.Errorf("client knows %d trees, want 2", len(c.trees))
  181. }
  182. }
  183. func checkRandomNode(t *testing.T, c *Client, wantNodes []*enode.Node) {
  184. t.Helper()
  185. var (
  186. want = make(map[enode.ID]*enode.Node)
  187. maxCalls = len(wantNodes) * 2
  188. calls = 0
  189. ctx = context.Background()
  190. )
  191. for _, n := range wantNodes {
  192. want[n.ID()] = n
  193. }
  194. for ; len(want) > 0 && calls < maxCalls; calls++ {
  195. n := c.RandomNode(ctx)
  196. if n == nil {
  197. t.Fatalf("RandomNode returned nil (call %d)", calls)
  198. }
  199. delete(want, n.ID())
  200. }
  201. t.Logf("checkRandomNode called RandomNode %d times to find %d nodes", calls, len(wantNodes))
  202. for _, n := range want {
  203. t.Errorf("RandomNode didn't discover node %v", n.ID())
  204. }
  205. }
  206. func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) {
  207. tree, err := MakeTree(1, nodes, links)
  208. if err != nil {
  209. panic(err)
  210. }
  211. url, err := tree.Sign(testKey(signingKeySeed), domain)
  212. if err != nil {
  213. panic(err)
  214. }
  215. return tree, url
  216. }
  217. // testKeys creates deterministic private keys for testing.
  218. func testKeys(seed int64, n int) []*ecdsa.PrivateKey {
  219. rand := rand.New(rand.NewSource(seed))
  220. keys := make([]*ecdsa.PrivateKey, n)
  221. for i := 0; i < n; i++ {
  222. key, err := ecdsa.GenerateKey(crypto.S256(), rand)
  223. if err != nil {
  224. panic("can't generate key: " + err.Error())
  225. }
  226. keys[i] = key
  227. }
  228. return keys
  229. }
  230. func testKey(seed int64) *ecdsa.PrivateKey {
  231. return testKeys(seed, 1)[0]
  232. }
  233. func testNodes(seed int64, n int) []*enode.Node {
  234. keys := testKeys(seed, n)
  235. nodes := make([]*enode.Node, n)
  236. for i, key := range keys {
  237. record := new(enr.Record)
  238. record.SetSeq(uint64(i))
  239. enode.SignV4(record, key)
  240. n, err := enode.New(enode.ValidSchemes, record)
  241. if err != nil {
  242. panic(err)
  243. }
  244. nodes[i] = n
  245. }
  246. return nodes
  247. }
  248. func testNode(seed int64) *enode.Node {
  249. return testNodes(seed, 1)[0]
  250. }
  251. type mapResolver map[string]string
  252. func newMapResolver(maps ...map[string]string) mapResolver {
  253. mr := make(mapResolver)
  254. for _, m := range maps {
  255. mr.add(m)
  256. }
  257. return mr
  258. }
  259. func (mr mapResolver) clear() {
  260. for k := range mr {
  261. delete(mr, k)
  262. }
  263. }
  264. func (mr mapResolver) add(m map[string]string) {
  265. for k, v := range m {
  266. mr[k] = v
  267. }
  268. }
  269. func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
  270. if record, ok := mr[name]; ok {
  271. return []string{record}, nil
  272. }
  273. return nil, nil
  274. }