github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/dnsdisc/client_test.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package dnsdisc
    19  
    20  import (
    21  	"context"
    22  	"crypto/ecdsa"
    23  	"math/rand"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/AigarNetwork/aigar/common/mclock"
    29  	"github.com/AigarNetwork/aigar/crypto"
    30  	"github.com/AigarNetwork/aigar/internal/testlog"
    31  	"github.com/AigarNetwork/aigar/log"
    32  	"github.com/AigarNetwork/aigar/p2p/enode"
    33  	"github.com/AigarNetwork/aigar/p2p/enr"
    34  	"github.com/davecgh/go-spew/spew"
    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  		"n":                            "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA",
    46  		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
    47  		"JWXYDBPXYWG6FX3GMDIBFA6CJ4.n": "enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24",
    48  		"2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA",
    49  		"H4FHT4B454P6UXFD7JCYQ5PWDY.n": "enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI",
    50  		"MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o",
    51  	}
    52  	var (
    53  		wantNodes = testNodes(0x29452, 3)
    54  		wantLinks = []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@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://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@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  	if len(c.trees) > 0 {
    73  		t.Errorf("tree from SyncTree added to client")
    74  	}
    75  }
    76  
    77  // In this test, syncing the tree fails because it contains an invalid ENR entry.
    78  func TestClientSyncTreeBadNode(t *testing.T) {
    79  	// var b strings.Builder
    80  	// b.WriteString(enrPrefix)
    81  	// b.WriteString("-----")
    82  	// badHash := subdomain(&b)
    83  	// tree, _ := MakeTree(3, nil, []string{"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"})
    84  	// tree.entries[badHash] = &b
    85  	// tree.root.eroot = badHash
    86  	// url, _ := tree.Sign(testKey(signingKeySeed), "n")
    87  	// fmt.Println(url)
    88  	// fmt.Printf("%#v\n", tree.ToTXT("n"))
    89  
    90  	r := mapResolver{
    91  		"n":                            "enrtree-root:v1 e=INDMVBZEEQ4ESVYAKGIYU74EAA l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=3 sig=Vl3AmunLur0JZ3sIyJPSH6A3Vvdp4F40jWQeCmkIhmcgwE4VC5U9wpK8C_uL_CMY29fd6FAhspRvq2z_VysTLAA",
    92  		"C7HRFPF3BLGF3YR4DY5KX3SMBE.n": "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org",
    93  		"INDMVBZEEQ4ESVYAKGIYU74EAA.n": "enr:-----",
    94  	}
    95  	c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
    96  	_, err := c.SyncTree("enrtree://AKPYQIUQIL7PSIACI32J7FGZW56E5FKHEFCCOFHILBIMW3M6LWXS2@n")
    97  	wantErr := nameError{name: "INDMVBZEEQ4ESVYAKGIYU74EAA.n", err: entryError{typ: "enr", err: errInvalidENR}}
    98  	if err != wantErr {
    99  		t.Fatalf("expected sync error %q, got %q", wantErr, err)
   100  	}
   101  }
   102  
   103  // This test checks that RandomNode hits all entries.
   104  func TestClientRandomNode(t *testing.T) {
   105  	nodes := testNodes(nodesSeed1, 30)
   106  	tree, url := makeTestTree("n", nodes, nil)
   107  	r := mapResolver(tree.ToTXT("n"))
   108  	c, _ := NewClient(Config{Resolver: r, Logger: testlog.Logger(t, log.LvlTrace)})
   109  	if err := c.AddTree(url); err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	checkRandomNode(t, c, nodes)
   114  }
   115  
   116  // This test checks that RandomNode traverses linked trees as well as explicitly added trees.
   117  func TestClientRandomNodeLinks(t *testing.T) {
   118  	nodes := testNodes(nodesSeed1, 40)
   119  	tree1, url1 := makeTestTree("t1", nodes[:10], nil)
   120  	tree2, url2 := makeTestTree("t2", nodes[10:], []string{url1})
   121  	cfg := Config{
   122  		Resolver: newMapResolver(tree1.ToTXT("t1"), tree2.ToTXT("t2")),
   123  		Logger:   testlog.Logger(t, log.LvlTrace),
   124  	}
   125  	c, _ := NewClient(cfg)
   126  	if err := c.AddTree(url2); err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	checkRandomNode(t, c, nodes)
   131  }
   132  
   133  // This test verifies that RandomNode re-checks the root of the tree to catch
   134  // updates to nodes.
   135  func TestClientRandomNodeUpdates(t *testing.T) {
   136  	var (
   137  		clock    = new(mclock.Simulated)
   138  		nodes    = testNodes(nodesSeed1, 30)
   139  		resolver = newMapResolver()
   140  		cfg      = Config{
   141  			Resolver:        resolver,
   142  			Logger:          testlog.Logger(t, log.LvlTrace),
   143  			RecheckInterval: 20 * time.Minute,
   144  		}
   145  		c, _ = NewClient(cfg)
   146  	)
   147  	c.clock = clock
   148  	tree1, url := makeTestTree("n", nodes[:25], nil)
   149  
   150  	// Sync the original tree.
   151  	resolver.add(tree1.ToTXT("n"))
   152  	c.AddTree(url)
   153  	checkRandomNode(t, c, nodes[:25])
   154  
   155  	// Update some nodes and ensure RandomNode returns the new nodes as well.
   156  	keys := testKeys(nodesSeed1, len(nodes))
   157  	for i, n := range nodes[:len(nodes)/2] {
   158  		r := n.Record()
   159  		r.Set(enr.IP{127, 0, 0, 1})
   160  		r.SetSeq(55)
   161  		enode.SignV4(r, keys[i])
   162  		n2, _ := enode.New(enode.ValidSchemes, r)
   163  		nodes[i] = n2
   164  	}
   165  	tree2, _ := makeTestTree("n", nodes, nil)
   166  	clock.Run(cfg.RecheckInterval + 1*time.Second)
   167  	resolver.clear()
   168  	resolver.add(tree2.ToTXT("n"))
   169  	checkRandomNode(t, c, nodes)
   170  }
   171  
   172  // This test verifies that RandomNode re-checks the root of the tree to catch
   173  // updates to links.
   174  func TestClientRandomNodeLinkUpdates(t *testing.T) {
   175  	var (
   176  		clock    = new(mclock.Simulated)
   177  		nodes    = testNodes(nodesSeed1, 30)
   178  		resolver = newMapResolver()
   179  		cfg      = Config{
   180  			Resolver:        resolver,
   181  			Logger:          testlog.Logger(t, log.LvlTrace),
   182  			RecheckInterval: 20 * time.Minute,
   183  		}
   184  		c, _ = NewClient(cfg)
   185  	)
   186  	c.clock = clock
   187  	tree3, url3 := makeTestTree("t3", nodes[20:30], nil)
   188  	tree2, url2 := makeTestTree("t2", nodes[10:20], nil)
   189  	tree1, url1 := makeTestTree("t1", nodes[0:10], []string{url2})
   190  	resolver.add(tree1.ToTXT("t1"))
   191  	resolver.add(tree2.ToTXT("t2"))
   192  	resolver.add(tree3.ToTXT("t3"))
   193  
   194  	// Sync tree1 using RandomNode.
   195  	c.AddTree(url1)
   196  	checkRandomNode(t, c, nodes[:20])
   197  
   198  	// Add link to tree3, remove link to tree2.
   199  	tree1, _ = makeTestTree("t1", nodes[:10], []string{url3})
   200  	resolver.add(tree1.ToTXT("t1"))
   201  	clock.Run(cfg.RecheckInterval + 1*time.Second)
   202  	t.Log("tree1 updated")
   203  
   204  	var wantNodes []*enode.Node
   205  	wantNodes = append(wantNodes, tree1.Nodes()...)
   206  	wantNodes = append(wantNodes, tree3.Nodes()...)
   207  	checkRandomNode(t, c, wantNodes)
   208  
   209  	// Check that linked trees are GCed when they're no longer referenced.
   210  	if len(c.trees) != 2 {
   211  		t.Errorf("client knows %d trees, want 2", len(c.trees))
   212  	}
   213  }
   214  
   215  func checkRandomNode(t *testing.T, c *Client, wantNodes []*enode.Node) {
   216  	t.Helper()
   217  
   218  	var (
   219  		want     = make(map[enode.ID]*enode.Node)
   220  		maxCalls = len(wantNodes) * 2
   221  		calls    = 0
   222  		ctx      = context.Background()
   223  	)
   224  	for _, n := range wantNodes {
   225  		want[n.ID()] = n
   226  	}
   227  	for ; len(want) > 0 && calls < maxCalls; calls++ {
   228  		n := c.RandomNode(ctx)
   229  		if n == nil {
   230  			t.Fatalf("RandomNode returned nil (call %d)", calls)
   231  		}
   232  		delete(want, n.ID())
   233  	}
   234  	t.Logf("checkRandomNode called RandomNode %d times to find %d nodes", calls, len(wantNodes))
   235  	for _, n := range want {
   236  		t.Errorf("RandomNode didn't discover node %v", n.ID())
   237  	}
   238  }
   239  
   240  func makeTestTree(domain string, nodes []*enode.Node, links []string) (*Tree, string) {
   241  	tree, err := MakeTree(1, nodes, links)
   242  	if err != nil {
   243  		panic(err)
   244  	}
   245  	url, err := tree.Sign(testKey(signingKeySeed), domain)
   246  	if err != nil {
   247  		panic(err)
   248  	}
   249  	return tree, url
   250  }
   251  
   252  // testKeys creates deterministic private keys for testing.
   253  func testKeys(seed int64, n int) []*ecdsa.PrivateKey {
   254  	rand := rand.New(rand.NewSource(seed))
   255  	keys := make([]*ecdsa.PrivateKey, n)
   256  	for i := 0; i < n; i++ {
   257  		key, err := ecdsa.GenerateKey(crypto.S256(), rand)
   258  		if err != nil {
   259  			panic("can't generate key: " + err.Error())
   260  		}
   261  		keys[i] = key
   262  	}
   263  	return keys
   264  }
   265  
   266  func testKey(seed int64) *ecdsa.PrivateKey {
   267  	return testKeys(seed, 1)[0]
   268  }
   269  
   270  func testNodes(seed int64, n int) []*enode.Node {
   271  	keys := testKeys(seed, n)
   272  	nodes := make([]*enode.Node, n)
   273  	for i, key := range keys {
   274  		record := new(enr.Record)
   275  		record.SetSeq(uint64(i))
   276  		enode.SignV4(record, key)
   277  		n, err := enode.New(enode.ValidSchemes, record)
   278  		if err != nil {
   279  			panic(err)
   280  		}
   281  		nodes[i] = n
   282  	}
   283  	return nodes
   284  }
   285  
   286  func testNode(seed int64) *enode.Node {
   287  	return testNodes(seed, 1)[0]
   288  }
   289  
   290  type mapResolver map[string]string
   291  
   292  func newMapResolver(maps ...map[string]string) mapResolver {
   293  	mr := make(mapResolver)
   294  	for _, m := range maps {
   295  		mr.add(m)
   296  	}
   297  	return mr
   298  }
   299  
   300  func (mr mapResolver) clear() {
   301  	for k := range mr {
   302  		delete(mr, k)
   303  	}
   304  }
   305  
   306  func (mr mapResolver) add(m map[string]string) {
   307  	for k, v := range m {
   308  		mr[k] = v
   309  	}
   310  }
   311  
   312  func (mr mapResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
   313  	if record, ok := mr[name]; ok {
   314  		return []string{record}, nil
   315  	}
   316  	return nil, nil
   317  }