github.com/ethereum-optimism/optimism/l2geth@v0.0.0-20230612200230-50b04ade19e3/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  	"math/rand"
    23  	"reflect"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/davecgh/go-spew/spew"
    28  	"github.com/ethereum-optimism/optimism/l2geth/common/mclock"
    29  	"github.com/ethereum-optimism/optimism/l2geth/crypto"
    30  	"github.com/ethereum-optimism/optimism/l2geth/internal/testlog"
    31  	"github.com/ethereum-optimism/optimism/l2geth/log"
    32  	"github.com/ethereum-optimism/optimism/l2geth/p2p/enode"
    33  	"github.com/ethereum-optimism/optimism/l2geth/p2p/enr"
    34  )
    35  
    36  const (
    37  	signingKeySeed = 0x111111
    38  	nodesSeed1     = 0x2945237
    39  	nodesSeed2     = 0x4567299
    40  )
    41  
    42  func TestClientSyncTree(t *testing.T) {
    43  	r := mapResolver{
    44  		"n":                            "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA",
    45  		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
    46  		"JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24",
    47  		"2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA",
    48  		"H4FHT4B454P6UXFD7JCYQ5PWDY.n": "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI",
    49  		"MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o",
    50  	}
    51  	var (
    52  		wantNodes = testNodes(0x29452, 3)
    53  		wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"}
    54  		wantSeq   = uint(1)
    55  	)
    56  
    57  	c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
    58  	stree, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
    59  	if err != nil {
    60  		t.Fatal("sync error:", err)
    61  	}
    62  	if !reflect.DeepEqual(sortByID(stree.Nodes()), sortByID(wantNodes)) {
    63  		t.Errorf("wrong nodes in synced tree:\nhave %v\nwant %v", spew.Sdump(stree.Nodes()), spew.Sdump(wantNodes))
    64  	}
    65  	if !reflect.DeepEqual(stree.Links(), wantLinks) {
    66  		t.Errorf("wrong links in synced tree: %v", stree.Links())
    67  	}
    68  	if stree.Seq() != wantSeq {
    69  		t.Errorf("synced tree has wrong seq: %d", stree.Seq())
    70  	}
    71  }
    72  
    73  // In this test, syncing the tree fails because it contains an invalid ENR entry.
    74  func TestClientSyncTreeBadNode(t *testing.T) {
    75  	// var b strings.Builder
    76  	// b.WriteString(enrPrefix)
    77  	// b.WriteString("-----")
    78  	// badHash := subdomain(&b)
    79  	// tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"})
    80  	// tree.entries[badHash] = &b
    81  	// tree.root.eroot = badHash
    82  	// url, _ := tree.Sign(testKey(signingKeySeed), "n")
    83  	// fmt.Println(url)
    84  	// fmt.Printf("%#v\n", tree.ToTXT("n"))
    85  
    86  	r := mapResolver{
    87  		"n":                            "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA",
    88  		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
    89  		"INDMVBZEEQ4ESVYAKGIYU74EAA.n": "enr:-----",
    90  	}
    91  	c := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
    92  	_, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
    93  	wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}}
    94  	if err != wantErr {
    95  		t.Fatalf("expected sync error %q, got %q", wantErr, err)
    96  	}
    97  }
    98  
    99  // This test checks that randomIterator finds all entries.
   100  func TestIterator(t *testing.T) {
   101  	nodes := testNodes(nodesSeed1, 30)
   102  	tree, url := makeTestTree("n", nodes, nil)
   103  	r := mapResolver(tree.ToTXT("n"))
   104  	c := NewClient(Config{
   105  		Resolver:  r,
   106  		Logger:    testlog.Logger(t, log.LvlTrace),
   107  		RateLimit: 500,
   108  	})
   109  	it, err := c.NewIterator(url)
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  
   114  	checkIterator(t, it, nodes)
   115  }
   116  
   117  // This test checks if closing randomIterator races.
   118  func TestIteratorClose(t *testing.T) {
   119  	nodes := testNodes(nodesSeed1, 500)
   120  	tree1, url1 := makeTestTree("t1", nodes, 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  	done := make(chan struct{})
   128  	go func() {
   129  		for it.Next() {
   130  			_ = it.Node()
   131  		}
   132  		close(done)
   133  	}()
   134  
   135  	time.Sleep(50 * time.Millisecond)
   136  	it.Close()
   137  	<-done
   138  }
   139  
   140  // This test checks that randomIterator traverses linked trees as well as explicitly added trees.
   141  func TestIteratorLinks(t *testing.T) {
   142  	nodes := testNodes(nodesSeed1, 40)
   143  	tree1, url1 := makeTestTree("t1", nodes[:10], nil)
   144  	tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1})
   145  	c := NewClient(Config{
   146  		Resolver:  newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")),
   147  		Logger:    testlog.Logger(t, log.LvlTrace),
   148  		RateLimit: 500,
   149  	})
   150  	it, err := c.NewIterator(url2)
   151  	if err != nil {
   152  		t.Fatal(err)
   153  	}
   154  
   155  	checkIterator(t, it, nodes)
   156  }
   157  
   158  // This test verifies that randomIterator re-checks the root of the tree to catch
   159  // updates to nodes.
   160  func TestIteratorNodeUpdates(t *testing.T) {
   161  	var (
   162  		clock    = new(mclock.Simulated)
   163  		nodes    = testNodes(nodesSeed1, 30)
   164  		resolver = newMapResolver()
   165  		c        = NewClient(Config{
   166  			Resolver:        resolver,
   167  			Logger:          testlog.Logger(t, log.LvlTrace),
   168  			RecheckInterval: 20 * time.Minute,
   169  			RateLimit:       500,
   170  		})
   171  	)
   172  	c.clock = clock
   173  	tree1, url := makeTestTree("n", nodes[:25], nil)
   174  	it, err := c.NewIterator(url)
   175  	if err != nil {
   176  		t.Fatal(err)
   177  	}
   178  
   179  	// sync the original tree.
   180  	resolver.add(tree1.ToTXT("n"))
   181  	checkIterator(t, it, nodes[:25])
   182  
   183  	// Update some nodes and ensure RandomNode returns the new nodes as well.
   184  	keys := testKeys(nodesSeed1, len(nodes))
   185  	for i, n := range nodes[:len(nodes)/2] {
   186  		r := n.Record()
   187  		r.Set(enr.IP{127, 0, 0, 1})
   188  		r.SetSeq(55)
   189  		enode.SignV4(r, keys[i])
   190  		n2, _ := enode.New(enode.ValidSchemes, r)
   191  		nodes[i] = n2
   192  	}
   193  	tree2, _ := makeTestTree("n", nodes, nil)
   194  	clock.Run(c.cfg.RecheckInterval + 1*time.Second)
   195  	resolver.clear()
   196  	resolver.add(tree2.ToTXT("n"))
   197  	checkIterator(t, it, nodes)
   198  }
   199  
   200  // This test verifies that randomIterator re-checks the root of the tree to catch
   201  // updates to links.
   202  func TestIteratorLinkUpdates(t *testing.T) {
   203  	var (
   204  		clock    = new(mclock.Simulated)
   205  		nodes    = testNodes(nodesSeed1, 30)
   206  		resolver = newMapResolver()
   207  		c        = NewClient(Config{
   208  			Resolver:        resolver,
   209  			Logger:          testlog.Logger(t, log.LvlTrace),
   210  			RecheckInterval: 20 * time.Minute,
   211  			RateLimit:       500,
   212  		})
   213  	)
   214  	c.clock = clock
   215  	tree3, url3 := makeTestTree("t3", nodes[20:30], nil)
   216  	tree2, url2 := makeTestTree("t2", nodes[10:20], nil)
   217  	tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2})
   218  	resolver.add(tree1.ToTXT("t1"))
   219  	resolver.add(tree2.ToTXT("t2"))
   220  	resolver.add(tree3.ToTXT("t3"))
   221  
   222  	it, err := c.NewIterator(url1)
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  
   227  	// Sync tree1 using RandomNode.
   228  	checkIterator(t, it, nodes[:20])
   229  
   230  	// Add link to tree3, remove link to tree2.
   231  	tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
   232  	resolver.add(tree1.ToTXT("t1"))
   233  	clock.Run(c.cfg.RecheckInterval + 1*time.Second)
   234  	t.Log("tree1 updated")
   235  
   236  	var wantNodes []*enode.Node
   237  	wantNodes = append(wantNodes, tree1.Nodes()...)
   238  	wantNodes = append(wantNodes, tree3.Nodes()...)
   239  	checkIterator(t, it, wantNodes)
   240  
   241  	// Check that linked trees are GCed when they're no longer referenced.
   242  	knownTrees := it.(*randomIterator).trees
   243  	if len(knownTrees) != 2 {
   244  		t.Errorf("client knows %d trees, want 2", len(knownTrees))
   245  	}
   246  }
   247  
   248  func checkIterator(t *testing.T, it enode.Iterator, wantNodes []*enode.Node) {
   249  	t.Helper()
   250  
   251  	var (
   252  		want     = make(map[enode.ID]*enode.Node)
   253  		maxCalls = len(wantNodes) * 3
   254  		calls    = 0
   255  	)
   256  	for _, n := range wantNodes {
   257  		want[n.ID()] = n
   258  	}
   259  	for ; len(want) > 0 && calls < maxCalls; calls++ {
   260  		if !it.Next() {
   261  			t.Fatalf("Next returned false (call %d)", calls)
   262  		}
   263  		n := it.Node()
   264  		delete(want, n.ID())
   265  	}
   266  	t.Logf("checkIterator called Next %d times to find %d nodes", calls, len(wantNodes))
   267  	for _, n := range want {
   268  		t.Errorf("iterator didn't discover node %v", n.ID())
   269  	}
   270  }
   271  
   272  func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) {
   273  	tree, err := MakeTree(1, nodes, links)
   274  	if err != nil {
   275  		panic(err)
   276  	}
   277  	url, err := tree.Sign(testKey(signingKeySeed), domain)
   278  	if err != nil {
   279  		panic(err)
   280  	}
   281  	return tree, url
   282  }
   283  
   284  // testKeys creates deterministic private keys for testing.
   285  func testKeys(seed int64, n int) []*ecdsa.PrivateKey {
   286  	rand := rand.New(rand.NewSource(seed))
   287  	keys := make([]*ecdsa.PrivateKey, n)
   288  	for i := 0; i < n; i++ {
   289  		key, err := ecdsa.GenerateKey(crypto.S256(), rand)
   290  		if err != nil {
   291  			panic("can't generate key: " + err.Error())
   292  		}
   293  		keys[i] = key
   294  	}
   295  	return keys
   296  }
   297  
   298  func testKey(seed int64) *ecdsa.PrivateKey {
   299  	return testKeys(seed, 1)[0]
   300  }
   301  
   302  func testNodes(seed int64, n int) []*enode.Node {
   303  	keys := testKeys(seed, n)
   304  	nodes := make([]*enode.Node, n)
   305  	for i, key := range keys {
   306  		record := new(enr.Record)
   307  		record.SetSeq(uint64(i))
   308  		enode.SignV4(record, key)
   309  		n, err := enode.New(enode.ValidSchemes, record)
   310  		if err != nil {
   311  			panic(err)
   312  		}
   313  		nodes[i] = n
   314  	}
   315  	return nodes
   316  }
   317  
   318  func testNode(seed int64) *enode.Node {
   319  	return testNodes(seed, 1)[0]
   320  }
   321  
   322  type mapResolver map[string]string
   323  
   324  func newMapResolver(maps ...map[string]string) mapResolver {
   325  	mr := make(mapResolver)
   326  	for _, m := range maps {
   327  		mr.add(m)
   328  	}
   329  	return mr
   330  }
   331  
   332  func (mr mapResolver) clear() {
   333  	for k := range mr {
   334  		delete(mr, k)
   335  	}
   336  }
   337  
   338  func (mr mapResolver) add(m map[string]string) {
   339  	for k, v := range m {
   340  		mr[k] = v
   341  	}
   342  }
   343  
   344  func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
   345  	if record, ok := mr[name]; ok {
   346  		return []string{record}, nil
   347  	}
   348  	return nil, nil
   349  }