client_test.go 10 KB

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