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