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