github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/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  	"errors"
    23  	"reflect"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/davecgh/go-spew/spew"
    28  	"github.com/unicornultrafoundation/go-u2u/common/hexutil"
    29  	"github.com/unicornultrafoundation/go-u2u/common/mclock"
    30  	"github.com/unicornultrafoundation/go-u2u/crypto"
    31  	"github.com/unicornultrafoundation/go-u2u/internal/testlog"
    32  	"github.com/unicornultrafoundation/go-u2u/log"
    33  	"github.com/unicornultrafoundation/go-u2u/p2p/enode"
    34  	"github.com/unicornultrafoundation/go-u2u/p2p/enr"
    35  )
    36  
    37  var signingKeyForTesting, _ = crypto.ToECDSA(hexutil.MustDecode("0xdc599867fc513f8f5e2c2c9c489cde5e71362d1d9ec6e693e0de063236ed1240"))
    38  
    39  func TestClientSyncTree(t *testing.T) {
    40  	nodes := []string{
    41  		"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA",
    42  		"enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI",
    43  		"enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o",
    44  	}
    45  
    46  	r := mapResolver{
    47  		"n":                            "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA",
    48  		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
    49  		"JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24",
    50  		"2XS2367YHAXJFGLZHVAWLQD4ZY.n": nodes[0],
    51  		"H4FHT4B454P6UXFD7JCYQ5PWDY.n": nodes[1],
    52  		"MHTDO6TMUBRIA2XWG5LUDACK24.n": nodes[2],
    53  	}
    54  	var (
    55  		wantNodes = sortByID(parseNodes(nodes))
    56  		wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}
    57  		wantSeq   = uint(1)
    58  	)
    59  
    60  	c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
    61  	stree, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
    62  	if err != nil {
    63  		t.Fatal("sync error:", err)
    64  	}
    65  	if !reflect.DeepEqual(sortByID(stree.Nodes()), wantNodes) {
    66  		t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes))
    67  	}
    68  	if !reflect.DeepEqual(stree.Links(), wantLinks) {
    69  		t.Errorf("wrong links in synced tree: %v", stree.Links())
    70  	}
    71  	if stree.Seq() != wantSeq {
    72  		t.Errorf("synced tree has wrong seq: %d", stree.Seq())
    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  	// var b strings.Builder
    79  	// b.WriteString(enrPrefix)
    80  	// b.WriteString("-----")
    81  	// badHash := subdomain(&b)
    82  	// tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"})
    83  	// tree.entries[badHash] = &b
    84  	// tree.root.eroot = badHash
    85  	// url, _ := tree.Sign(signingKeyForTesting, "n")
    86  	// fmt.Println(url)
    87  	// fmt.Printf("%#v\n", tree.ToTXT("n"))
    88  
    89  	r := mapResolver{
    90  		"n":                            "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA",
    91  		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
    92  		"INDMVBZEEQ4ESVYAKGIYU74EAA.n": "enr:-----",
    93  	}
    94  	c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
    95  	_, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
    96  	wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}}
    97  	if err != wantErr {
    98  		t.Fatalf("expected sync error %q, got %q", wantErr, err)
    99  	}
   100  }
   101  
   102  // This test checks that randomIterator finds all entries.
   103  func TestIterator(t *testing.T) {
   104  	var (
   105  		keys      = testKeys(30)
   106  		nodes     = testNodes(keys)
   107  		tree, url = makeTestTree("n", nodes, nil)
   108  		r         = mapResolver(tree.ToTXT("n"))
   109  	)
   110  
   111  	c := NewClient(Config{
   112  		Resolver:  r,
   113  		Logger:    testlog.Logger(t, log.LvlTrace),
   114  		RateLimit: 500,
   115  	})
   116  	it, err := c.NewIterator(url)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  
   121  	checkIterator(t, it, nodes)
   122  }
   123  
   124  func TestIteratorCloseWithoutNext(t *testing.T) {
   125  	tree1, url1 := makeTestTree("t1", nil, nil)
   126  	c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))})
   127  	it, err := c.NewIterator(url1)
   128  	if err != nil {
   129  		t.Fatal(err)
   130  	}
   131  
   132  	it.Close()
   133  	ok := it.Next()
   134  	if ok {
   135  		t.Fatal("Next returned true after Close")
   136  	}
   137  }
   138  
   139  // This test checks if closing randomIterator races.
   140  func TestIteratorClose(t *testing.T) {
   141  	var (
   142  		keys        = testKeys(500)
   143  		nodes       = testNodes(keys)
   144  		tree1, url1 = makeTestTree("t1", nodes, nil)
   145  	)
   146  
   147  	c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))})
   148  	it, err := c.NewIterator(url1)
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  
   153  	done := make(chan struct{})
   154  	go func() {
   155  		for it.Next() {
   156  			_ = it.Node()
   157  		}
   158  		close(done)
   159  	}()
   160  
   161  	time.Sleep(50 * time.Millisecond)
   162  	it.Close()
   163  	<-done
   164  }
   165  
   166  // This test checks that randomIterator traverses linked trees as well as explicitly added trees.
   167  func TestIteratorLinks(t *testing.T) {
   168  	var (
   169  		keys        = testKeys(40)
   170  		nodes       = testNodes(keys)
   171  		tree1, url1 = makeTestTree("t1", nodes[:10], nil)
   172  		tree2, url2 = makeTestTree("t2", nodes[10:], []string{url1})
   173  	)
   174  	c := NewClient(Config{
   175  		Resolver:  newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")),
   176  		Logger:    testlog.Logger(t, log.LvlTrace),
   177  		RateLimit: 500,
   178  	})
   179  	it, err := c.NewIterator(url2)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  
   184  	checkIterator(t, it, nodes)
   185  }
   186  
   187  // This test verifies that randomIterator re-checks the root of the tree to catch
   188  // updates to nodes.
   189  func TestIteratorNodeUpdates(t *testing.T) {
   190  	var (
   191  		clock    = new(mclock.Simulated)
   192  		keys     = testKeys(30)
   193  		nodes    = testNodes(keys)
   194  		resolver = newMapResolver()
   195  		c        = NewClient(Config{
   196  			Resolver:        resolver,
   197  			Logger:          testlog.Logger(t, log.LvlTrace),
   198  			RecheckInterval: 20 * time.Minute,
   199  			RateLimit:       500,
   200  		})
   201  	)
   202  	c.clock = clock
   203  	tree1, url := makeTestTree("n", nodes[:25], nil)
   204  	it, err := c.NewIterator(url)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  
   209  	// Sync the original tree.
   210  	resolver.add(tree1.ToTXT("n"))
   211  	checkIterator(t, it, nodes[:25])
   212  
   213  	// Ensure RandomNode returns the new nodes after the tree is updated.
   214  	updateSomeNodes(keys, nodes)
   215  	tree2, _ := makeTestTree("n", nodes, nil)
   216  	resolver.clear()
   217  	resolver.add(tree2.ToTXT("n"))
   218  	t.Log("tree updated")
   219  
   220  	clock.Run(c.cfg.RecheckInterval + 1*time.Second)
   221  	checkIterator(t, it, nodes)
   222  }
   223  
   224  // This test checks that the tree root is rechecked when a couple of leaf
   225  // requests have failed. The test is just like TestIteratorNodeUpdates, but
   226  // without advancing the clock by recheckInterval after the tree update.
   227  func TestIteratorRootRecheckOnFail(t *testing.T) {
   228  	var (
   229  		clock    = new(mclock.Simulated)
   230  		keys     = testKeys(30)
   231  		nodes    = testNodes(keys)
   232  		resolver = newMapResolver()
   233  		c        = NewClient(Config{
   234  			Resolver:        resolver,
   235  			Logger:          testlog.Logger(t, log.LvlTrace),
   236  			RecheckInterval: 20 * time.Minute,
   237  			RateLimit:       500,
   238  			// Disabling the cache is required for this test because the client doesn't
   239  			// notice leaf failures if all records are cached.
   240  			CacheLimit: 1,
   241  		})
   242  	)
   243  	c.clock = clock
   244  	tree1, url := makeTestTree("n", nodes[:25], nil)
   245  	it, err := c.NewIterator(url)
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  
   250  	// Sync the original tree.
   251  	resolver.add(tree1.ToTXT("n"))
   252  	checkIterator(t, it, nodes[:25])
   253  
   254  	// Ensure RandomNode returns the new nodes after the tree is updated.
   255  	updateSomeNodes(keys, nodes)
   256  	tree2, _ := makeTestTree("n", nodes, nil)
   257  	resolver.clear()
   258  	resolver.add(tree2.ToTXT("n"))
   259  	t.Log("tree updated")
   260  
   261  	checkIterator(t, it, nodes)
   262  }
   263  
   264  // This test checks that the iterator works correctly when the tree is initially empty.
   265  func TestIteratorEmptyTree(t *testing.T) {
   266  	var (
   267  		clock    = new(mclock.Simulated)
   268  		keys     = testKeys(1)
   269  		nodes    = testNodes(keys)
   270  		resolver = newMapResolver()
   271  		c        = NewClient(Config{
   272  			Resolver:        resolver,
   273  			Logger:          testlog.Logger(t, log.LvlTrace),
   274  			RecheckInterval: 20 * time.Minute,
   275  			RateLimit:       500,
   276  		})
   277  	)
   278  	c.clock = clock
   279  	tree1, url := makeTestTree("n", nil, nil)
   280  	tree2, _ := makeTestTree("n", nodes, nil)
   281  	resolver.add(tree1.ToTXT("n"))
   282  
   283  	// Start the iterator.
   284  	node := make(chan *enode.Node, 1)
   285  	it, err := c.NewIterator(url)
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	go func() {
   290  		it.Next()
   291  		node <- it.Node()
   292  	}()
   293  
   294  	// Wait for the client to get stuck in waitForRootUpdates.
   295  	clock.WaitForTimers(1)
   296  
   297  	// Now update the root.
   298  	resolver.add(tree2.ToTXT("n"))
   299  
   300  	// Wait for it to pick up the root change.
   301  	clock.Run(c.cfg.RecheckInterval)
   302  	select {
   303  	case n := <-node:
   304  		if n.ID() != nodes[0].ID() {
   305  			t.Fatalf("wrong node returned")
   306  		}
   307  	case <-time.After(5 * time.Second):
   308  		t.Fatal("it.Next() did not unblock within 5s of real time")
   309  	}
   310  }
   311  
   312  // updateSomeNodes applies ENR updates to some of the given nodes.
   313  func updateSomeNodes(keys []*ecdsa.PrivateKey, nodes []*enode.Node) {
   314  	for i, n := range nodes[:len(nodes)/2] {
   315  		r := n.Record()
   316  		r.Set(enr.IP{127, 0, 0, 1})
   317  		r.SetSeq(55)
   318  		enode.SignV4(r, keys[i])
   319  		n2, _ := enode.New(enode.ValidSchemes, r)
   320  		nodes[i] = n2
   321  	}
   322  }
   323  
   324  // This test verifies that randomIterator re-checks the root of the tree to catch
   325  // updates to links.
   326  func TestIteratorLinkUpdates(t *testing.T) {
   327  	var (
   328  		clock    = new(mclock.Simulated)
   329  		keys     = testKeys(30)
   330  		nodes    = testNodes(keys)
   331  		resolver = newMapResolver()
   332  		c        = NewClient(Config{
   333  			Resolver:        resolver,
   334  			Logger:          testlog.Logger(t, log.LvlTrace),
   335  			RecheckInterval: 20 * time.Minute,
   336  			RateLimit:       500,
   337  		})
   338  	)
   339  	c.clock = clock
   340  	tree3, url3 := makeTestTree("t3", nodes[20:30], nil)
   341  	tree2, url2 := makeTestTree("t2", nodes[10:20], nil)
   342  	tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2})
   343  	resolver.add(tree1.ToTXT("t1"))
   344  	resolver.add(tree2.ToTXT("t2"))
   345  	resolver.add(tree3.ToTXT("t3"))
   346  
   347  	it, err := c.NewIterator(url1)
   348  	if err != nil {
   349  		t.Fatal(err)
   350  	}
   351  
   352  	// Sync tree1 using RandomNode.
   353  	checkIterator(t, it, nodes[:20])
   354  
   355  	// Add link to tree3, remove link to tree2.
   356  	tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
   357  	resolver.add(tree1.ToTXT("t1"))
   358  	t.Log("tree1 updated")
   359  
   360  	clock.Run(c.cfg.RecheckInterval + 1*time.Second)
   361  
   362  	var wantNodes []*enode.Node
   363  	wantNodes = append(wantNodes, tree1.Nodes()...)
   364  	wantNodes = append(wantNodes, tree3.Nodes()...)
   365  	checkIterator(t, it, wantNodes)
   366  
   367  	// Check that linked trees are GCed when they're no longer referenced.
   368  	knownTrees := it.(*randomIterator).trees
   369  	if len(knownTrees) != 2 {
   370  		t.Errorf("client knows %d trees, want 2", len(knownTrees))
   371  	}
   372  }
   373  
   374  func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) {
   375  	t.Helper()
   376  
   377  	var (
   378  		want     = make(map[enode.ID]*enode.Node)
   379  		maxCalls = len(wantNodes) * 3
   380  		calls    = 0
   381  	)
   382  	for _, n := range wantNodes {
   383  		want[n.ID()] = n
   384  	}
   385  	for ; len(want) > 0 && calls < maxCalls; calls++ {
   386  		if !it.Next() {
   387  			t.Fatalf("Next returned false (call %d)", calls)
   388  		}
   389  		n := it.Node()
   390  		delete(want, n.ID())
   391  	}
   392  	t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes))
   393  	for _, n := range want {
   394  		t.Errorf("iterator didn't discover node %v", n.ID())
   395  	}
   396  }
   397  
   398  func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) {
   399  	tree, err := MakeTree(1, nodes, links)
   400  	if err != nil {
   401  		panic(err)
   402  	}
   403  	url, err := tree.Sign(signingKeyForTesting, domain)
   404  	if err != nil {
   405  		panic(err)
   406  	}
   407  	return tree, url
   408  }
   409  
   410  // testKeys creates deterministic private keys for testing.
   411  func testKeys(n int) []*ecdsa.PrivateKey {
   412  	keys := make([]*ecdsa.PrivateKey, n)
   413  	for i := 0; i < n; i++ {
   414  		key, err := crypto.GenerateKey()
   415  		if err != nil {
   416  			panic("can't generate key: " + err.Error())
   417  		}
   418  		keys[i] = key
   419  	}
   420  	return keys
   421  }
   422  
   423  func testNodes(keys []*ecdsa.PrivateKey) []*enode.Node {
   424  	nodes := make([]*enode.Node, len(keys))
   425  	for i, key := range keys {
   426  		record := new(enr.Record)
   427  		record.SetSeq(uint64(i))
   428  		enode.SignV4(record, key)
   429  		n, err := enode.New(enode.ValidSchemes, record)
   430  		if err != nil {
   431  			panic(err)
   432  		}
   433  		nodes[i] = n
   434  	}
   435  	return nodes
   436  }
   437  
   438  type mapResolver map[string]string
   439  
   440  func newMapResolver(maps ...map[string]string) mapResolver {
   441  	mr := make(mapResolver, len(maps))
   442  	for _, m := range maps {
   443  		mr.add(m)
   444  	}
   445  	return mr
   446  }
   447  
   448  func (mr mapResolver) clear() {
   449  	for k := range mr {
   450  		delete(mr, k)
   451  	}
   452  }
   453  
   454  func (mr mapResolver) add(m map[string]string) {
   455  	for k, v := range m {
   456  		mr[k] = v
   457  	}
   458  }
   459  
   460  func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
   461  	if record, ok := mr[name]; ok {
   462  		return []string{record}, nil
   463  	}
   464  	return nil, errors.New("not found")
   465  }
   466  
   467  func parseNodes(rec []string) []*enode.Node {
   468  	var ns []*enode.Node
   469  	for _, r := range rec {
   470  		var n enode.Node
   471  		if err := n.UnmarshalText([]byte(r)); err != nil {
   472  			panic(err)
   473  		}
   474  		ns = append(ns, &n)
   475  	}
   476  	return ns
   477  }