github.com/core-coin/go-core/v2@v2.1.9/p2p/dnsdisc/client_test.go (about)

     1  // Copyright 2019 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package dnsdisc
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"math/rand"
    23  	"reflect"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/davecgh/go-spew/spew"
    28  
    29  	"github.com/core-coin/go-core/v2/common/mclock"
    30  	"github.com/core-coin/go-core/v2/crypto"
    31  	"github.com/core-coin/go-core/v2/internal/testlog"
    32  	"github.com/core-coin/go-core/v2/log"
    33  	"github.com/core-coin/go-core/v2/p2p/enode"
    34  	"github.com/core-coin/go-core/v2/p2p/enr"
    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  		"AZ2AA7473KDOKV7PC3GMHBXZSE.n": "enr:-Pm4q1Jkab2APz8czlz1s8NxAZYGjIutJ0Or9tucWsbQnzA5uGrgqeOt2jmV3zC423-V6pGEto25A6fwgH-BxlojPGJwKfBv-kAt3p2WpWCuXsrv58mGFDV18Si0RPzVk04Jact6D2STKiyQDhasrWw94dUcAHxVxTpa-ZmsVbDRxaijR6cRzDr6EuJ_zNZ5PIJtnip-37g-dI_K44yrJZK97PnOaqlrtHrjykxVAICCaWSCdjSJc2VjcDI1NmsxuDl8VcU6WvmZrFWw0cWoo0enEcw6-hLif8zWeTyCbZ4qft-4PnSPyuOMqyWSvez5zmqpa7R648pMVQA",
    46  		"FFWEK2OQ5VIQR6UVZCTXPYTWI4.n": "enr:-Pm4q-56O3Ed1neHk_0leSRDqNm-uCqxBbekTF7vZJoVC3UAKbs_ozkQaDMBa7yIQdrhGfbHlah7wAOPAIjBJ6JHRJNQBevMWIbLCY3XoGROUrmVhoyUdB3LL8STkVSurs5VLJbWuQF2IUac7UOILSsD3Xg2AKMHAoZkYa-RtQVo4_9_l47u_sO-Ggberg4RJkamAZS-cMx5boa9vO7_8Cxk6YZLRzXEXGqhJa0tgAKCaWSCdjSJc2VjcDI1NmsxuDmjBwKGZGGvkbUFaOP_f5eO7v7DvhoG3q4OESZGpgGUvnDMeW6Gvbzu__AsZOmGS0c1xFxqoSWtLYA",
    47  		"RESRN326FX43MSLLCGJTO64QNE.n": "enr:-Pm4q4K4eHfS_nw1XZzCEzcx2wE3xwgOcEcrbD8xduY9lahR9Y99FKuo38r73EcyFashWcCvw6KFOd_ggPKhgG0g9D-iBt5oH27IRFWhfes-mhh41SGAxn2hOolcRH6N-QZma9orGk9b6wFnyikIEtf3DsMsANbRV07JV2YasbfV8oJTJyN3DSaRp3Mocpg1LJl6aV9sapqrZR46_JRrNZPfG8oWrU8YJAbfcxHBAAGCaWSCdjSJc2VjcDI1NmsxuDnW0VdOyVdmGrG31fKCUycjdw0mkadzKHKYNSyZemlfbGqaq2UeOvyUazWT3xvKFq1PGCQG33MRwQA",
    48  		"NBR5UGUKPKHBEV6MNKYLWA76UA.n": "enrtree-branch:AZ2AA7473KDOKV7PC3GMHBXZSE,RESRN326FX43MSLLCGJTO64QNE,FFWEK2OQ5VIQR6UVZCTXPYTWI4",
    49  		"HBRIBBF355HFMMLJX4ATX3PRRY.n": "enrtree://JJ5QVSTC2YDAIZSUEKUDYBBZHADEQTEWDG4TNQTIWUJGMOT52SWKKRUYMIE4KVJMTJYKRA4G4SW7QZTXNKEPAL3HIQAA@morenodes.example.org",
    50  		"n":                            "enrtree-root:v1 e=NBR5UGUKPKHBEV6MNKYLWA76UA l=HBRIBBF355HFMMLJX4ATX3PRRY seq=1 sig=0-nBiy1P3GB-CsXUL4vR_WKoY7UK0bYIjFCvhrxTd64A_Cg1Y3UOPQpWo-FLdLdEHWbri-xL_ocAS7yG_Mn8-QCmQZAG_7apeT9NYp4dAbscIdX0J1YJdjxeWSVN1t4UU5a5ixdkNHfOthEtGN8TjSIAhR3c_l0JEw3XUFpFrCjAQu-MCmR7bhcFQ_-KFwrG6r_1oQ2Q_-glxs9NYZz2ueAgQ8Lm0te4tZsA",
    51  	}
    52  	var (
    53  		wantNodes = testNodes(0x29452, 3)
    54  		wantLinks = []string{"enrtree://JJ5QVSTC2YDAIZSUEKUDYBBZHADEQTEWDG4TNQTIWUJGMOT52SWKKRUYMIE4KVJMTJYKRA4G4SW7QZTXNKEPAL3HIQAA@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://QUO5Z7S5BEJQ3V2QLJC2YKGAILXYYCTEPNXBOBKD76FBOCWG5K77LIINSD76QJOGZ5GWDHHWXHQCAQ6C43JNPOFVTMAA@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  }
    73  
    74  // In this test, syncing the tree fails because it contains an invalid ENR entry.
    75  func TestClientSyncTreeBadNode(t *testing.T) {
    76  	// var b strings.Builder
    77  	// b.WriteString(enrPrefix)
    78  	// b.WriteString("-----")
    79  	// badHash := subdomain(&b)
    80  	// tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"})
    81  	// tree.entries[badHash] = &b
    82  	// tree.root.eroot = badHash
    83  	// url, _ := tree.Sign(testKey(signingKeySeed), "n")
    84  	// fmt.Println(url)
    85  	// fmt.Printf("%#v\n", tree.ToTXT("n"))
    86  
    87  	r := mapResolver{
    88  		"LOOO2ZWKOLDJW2LQVMYL6UT77U.n": "enrtree-branch:",
    89  		"JZ7RET254NLCICBLV5AZ3WVUHA.n": "enr:-----",
    90  		"LCR3TNLW2SMSEFKARSGSOWUOEI.n": "enrtree://VOCWVGXWWC3FDXJPIO26CIMTMUXMC4A4JWTPDQGSUNTKYS45VPEUGPXQTZA4UEUVKK6SYAUQQ3M3ANQE32DSUOZUGICA@morenodes.example.org",
    91  		"n":                            "enrtree-root:v1 e=JZ7RET254NLCICBLV5AZ3WVUHA l=LCR3TNLW2SMSEFKARSGSOWUOEI seq=3 sig=mmG0XKqV42QjGtfKY0LmkstO8As69v0XqFhY-Nv64AMu1Uj0SRXLn-B6bX6HDGhqxgVYNxLwEvcA_Z_EAP6MnRly1tj_QL7kKyF8lNlQtNSj6Rd6U7fvlO-ItaKfXT-Kgvx1ao3L2NWFwFwJtOXAphwAhR3c_l0JEw3XUFpFrCjAQu-MCmR7bhcFQ_-KFwrG6r_1oQ2Q_-glxs9NYZz2ueAgQ8Lm0te4tZsA",
    92  	}
    93  	c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
    94  	_, err := c.SyncTree("enrtree://QUO5Z7S5BEJQ3V2QLJC2YKGAILXYYCTEPNXBOBKD76FBOCWG5K77LIINSD76QJOGZ5GWDHHWXHQCAQ6C43JNPOFVTMAA@n")
    95  	wantErr := nameError{name: "JZ7RET254NLCICBLV5AZ3WVUHA.n", err: entryError{typ: "enr", err: errInvalidENR}}
    96  	if err != wantErr {
    97  		t.Fatalf("expected sync error %q, got %q", wantErr, err)
    98  	}
    99  }
   100  
   101  // This test checks that randomIterator finds all entries.
   102  func TestIterator(t *testing.T) {
   103  	nodes := testNodes(nodesSeed1, 30)
   104  	tree, url := makeTestTree("n", nodes, nil)
   105  	r := mapResolver(tree.ToTXT("n"))
   106  	c := NewClient(Config{
   107  		Resolver:  r,
   108  		Logger:    testlog.Logger(t, log.LvlTrace),
   109  		RateLimit: 500,
   110  	})
   111  	it, err := c.NewIterator(url)
   112  	if err != nil {
   113  		t.Fatal(err)
   114  	}
   115  
   116  	checkIterator(t, it, nodes)
   117  }
   118  
   119  // This test checks if closing randomIterator races.
   120  func TestIteratorClose(t *testing.T) {
   121  	nodes := testNodes(nodesSeed1, 500)
   122  	tree1, url1 := makeTestTree("t1", nodes, nil)
   123  	c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))})
   124  	it, err := c.NewIterator(url1)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  
   129  	done := make(chan struct{})
   130  	go func() {
   131  		for it.Next() {
   132  			_ = it.Node()
   133  		}
   134  		close(done)
   135  	}()
   136  
   137  	time.Sleep(50 * time.Millisecond)
   138  	it.Close()
   139  	<-done
   140  }
   141  
   142  // This test checks that randomIterator traverses linked trees as well as explicitly added trees.
   143  func TestIteratorLinks(t *testing.T) {
   144  	nodes := testNodes(nodesSeed1, 40)
   145  	tree1, url1 := makeTestTree("t1", nodes[:10], nil)
   146  	tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1})
   147  	c := NewClient(Config{
   148  		Resolver:  newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")),
   149  		Logger:    testlog.Logger(t, log.LvlTrace),
   150  		RateLimit: 500,
   151  	})
   152  	it, err := c.NewIterator(url2)
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  
   157  	checkIterator(t, it, nodes)
   158  }
   159  
   160  // This test verifies that randomIterator re-checks the root of the tree to catch
   161  // updates to nodes.
   162  func TestIteratorNodeUpdates(t *testing.T) {
   163  	var (
   164  		clock    = new(mclock.Simulated)
   165  		nodes    = testNodes(nodesSeed1, 30)
   166  		resolver = newMapResolver()
   167  		c        = NewClient(Config{
   168  			Resolver:        resolver,
   169  			Logger:          testlog.Logger(t, log.LvlTrace),
   170  			RecheckInterval: 20 * time.Minute,
   171  			RateLimit:       500,
   172  		})
   173  	)
   174  	c.clock = clock
   175  	tree1, url := makeTestTree("n", nodes[:25], nil)
   176  	it, err := c.NewIterator(url)
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  
   181  	// Sync the original tree.
   182  	resolver.add(tree1.ToTXT("n"))
   183  	checkIterator(t, it, nodes[:25])
   184  
   185  	// Ensure RandomNode returns the new nodes after the tree is updated.
   186  	updateSomeNodes(nodesSeed1, nodes)
   187  	tree2, _ := makeTestTree("n", nodes, nil)
   188  	resolver.clear()
   189  	resolver.add(tree2.ToTXT("n"))
   190  	t.Log("tree updated")
   191  
   192  	clock.Run(c.cfg.RecheckInterval + 1*time.Second)
   193  	checkIterator(t, it, nodes)
   194  }
   195  
   196  // This test checks that the tree root is rechecked when a couple of leaf
   197  // requests have failed. The test is just like TestIteratorNodeUpdates, but
   198  // without advancing the clock by recheckInterval after the tree update.
   199  func TestIteratorRootRecheckOnFail(t *testing.T) {
   200  	var (
   201  		clock    = new(mclock.Simulated)
   202  		nodes    = testNodes(nodesSeed1, 30)
   203  		resolver = newMapResolver()
   204  		c        = NewClient(Config{
   205  			Resolver:        resolver,
   206  			Logger:          testlog.Logger(t, log.LvlTrace),
   207  			RecheckInterval: 20 * time.Minute,
   208  			RateLimit:       500,
   209  			// Disabling the cache is required for this test because the client doesn't
   210  			// notice leaf failures if all records are cached.
   211  			CacheLimit: 1,
   212  		})
   213  	)
   214  	c.clock = clock
   215  	tree1, url := makeTestTree("n", nodes[:25], nil)
   216  	it, err := c.NewIterator(url)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	// Sync the original tree.
   222  	resolver.add(tree1.ToTXT("n"))
   223  	checkIterator(t, it, nodes[:25])
   224  
   225  	// Ensure RandomNode returns the new nodes after the tree is updated.
   226  	updateSomeNodes(nodesSeed1, nodes)
   227  	tree2, _ := makeTestTree("n", nodes, nil)
   228  	resolver.clear()
   229  	resolver.add(tree2.ToTXT("n"))
   230  	t.Log("tree updated")
   231  
   232  	checkIterator(t, it, nodes)
   233  }
   234  
   235  // updateSomeNodes applies ENR updates to some of the given nodes.
   236  func updateSomeNodes(keySeed int64, nodes []*enode.Node) {
   237  	keys := testKeys(nodesSeed1, len(nodes))
   238  	for i, n := range nodes[:len(nodes)/2] {
   239  		r := n.Record()
   240  		r.Set(enr.IP{127, 0, 0, 1})
   241  		r.SetSeq(55)
   242  		enode.SignV4(r, keys[i])
   243  		n2, _ := enode.New(enode.ValidSchemes, r)
   244  		nodes[i] = n2
   245  	}
   246  }
   247  
   248  // This test verifies that randomIterator re-checks the root of the tree to catch
   249  // updates to links.
   250  func TestIteratorLinkUpdates(t *testing.T) {
   251  	var (
   252  		clock    = new(mclock.Simulated)
   253  		nodes    = testNodes(nodesSeed1, 30)
   254  		resolver = newMapResolver()
   255  		c        = NewClient(Config{
   256  			Resolver:        resolver,
   257  			Logger:          testlog.Logger(t, log.LvlTrace),
   258  			RecheckInterval: 20 * time.Minute,
   259  			RateLimit:       500,
   260  		})
   261  	)
   262  	c.clock = clock
   263  	tree3, url3 := makeTestTree("t3", nodes[20:30], nil)
   264  	tree2, url2 := makeTestTree("t2", nodes[10:20], nil)
   265  	tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2})
   266  	resolver.add(tree1.ToTXT("t1"))
   267  	resolver.add(tree2.ToTXT("t2"))
   268  	resolver.add(tree3.ToTXT("t3"))
   269  
   270  	it, err := c.NewIterator(url1)
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	// Sync tree1 using RandomNode.
   276  	checkIterator(t, it, nodes[:20])
   277  
   278  	// Add link to tree3, remove link to tree2.
   279  	tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
   280  	resolver.add(tree1.ToTXT("t1"))
   281  	t.Log("tree1 updated")
   282  
   283  	clock.Run(c.cfg.RecheckInterval + 1*time.Second)
   284  
   285  	var wantNodes []*enode.Node
   286  	wantNodes = append(wantNodes, tree1.Nodes()...)
   287  	wantNodes = append(wantNodes, tree3.Nodes()...)
   288  	checkIterator(t, it, wantNodes)
   289  
   290  	// Check that linked trees are GCed when they're no longer referenced.
   291  	knownTrees := it.(*randomIterator).trees
   292  	if len(knownTrees) != 2 {
   293  		t.Errorf("client knows %d trees, want 2", len(knownTrees))
   294  	}
   295  }
   296  
   297  func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) {
   298  	t.Helper()
   299  
   300  	var (
   301  		want     = make(map[enode.ID]*enode.Node)
   302  		maxCalls = len(wantNodes) * 3
   303  		calls    = 0
   304  	)
   305  	for _, n := range wantNodes {
   306  		want[n.ID()] = n
   307  	}
   308  	for ; len(want) > 0 && calls < maxCalls; calls++ {
   309  		if !it.Next() {
   310  			t.Fatalf("Next returned false (call %d)", calls)
   311  		}
   312  		n := it.Node()
   313  		delete(want, n.ID())
   314  	}
   315  	t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes))
   316  	for _, n := range want {
   317  		t.Errorf("iterator didn't discover node %v", n.ID())
   318  	}
   319  }
   320  
   321  func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) {
   322  	tree, err := MakeTree(1, nodes, links)
   323  	if err != nil {
   324  		panic(err)
   325  	}
   326  	url, err := tree.Sign(testKey(signingKeySeed), domain)
   327  	if err != nil {
   328  		panic(err)
   329  	}
   330  	return tree, url
   331  }
   332  
   333  // testKeys creates deterministic private keys for testing.
   334  func testKeys(seed int64, n int) []*crypto.PrivateKey {
   335  	rand := rand.New(rand.NewSource(seed))
   336  	keys := make([]*crypto.PrivateKey, n)
   337  	for i := 0; i < n; i++ {
   338  		key, err := crypto.GenerateKey(rand)
   339  		if err != nil {
   340  			panic("can't generate key: " + err.Error())
   341  		}
   342  		keys[i] = key
   343  	}
   344  	return keys
   345  }
   346  
   347  func testKey(seed int64) *crypto.PrivateKey {
   348  	return testKeys(seed, 1)[0]
   349  }
   350  
   351  func testNodes(seed int64, n int) []*enode.Node {
   352  	keys := testKeys(seed, n)
   353  	nodes := make([]*enode.Node, n)
   354  	for i, key := range keys {
   355  		record := new(enr.Record)
   356  		record.SetSeq(uint64(i))
   357  		enode.SignV4(record, key)
   358  		n, err := enode.New(enode.ValidSchemes, record)
   359  		if err != nil {
   360  			panic(err)
   361  		}
   362  		nodes[i] = n
   363  	}
   364  	return nodes
   365  }
   366  
   367  func testNode(seed int64) *enode.Node {
   368  	return testNodes(seed, 1)[0]
   369  }
   370  
   371  type mapResolver map[string]string
   372  
   373  func newMapResolver(maps ...map[string]string) mapResolver {
   374  	mr := make(mapResolver)
   375  	for _, m := range maps {
   376  		mr.add(m)
   377  	}
   378  	return mr
   379  }
   380  
   381  func (mr mapResolver) clear() {
   382  	for k := range mr {
   383  		delete(mr, k)
   384  	}
   385  }
   386  
   387  func (mr mapResolver) add(m map[string]string) {
   388  	for k, v := range m {
   389  		mr[k] = v
   390  	}
   391  }
   392  
   393  func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
   394  	if record, ok := mr[name]; ok {
   395  		return []string{record}, nil
   396  	}
   397  	return nil, errors.New("not found")
   398  }