github.com/ethereum/go-ethereum@v1.16.1/p2p/discover/v4_lookup_test.go (about)

     1  // Copyright 2019 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 discover
    18  
    19  import (
    20  	"crypto/ecdsa"
    21  	"fmt"
    22  	"net/netip"
    23  	"slices"
    24  	"sync"
    25  	"testing"
    26  
    27  	"github.com/ethereum/go-ethereum/crypto"
    28  	"github.com/ethereum/go-ethereum/p2p/discover/v4wire"
    29  	"github.com/ethereum/go-ethereum/p2p/enode"
    30  	"github.com/ethereum/go-ethereum/p2p/enr"
    31  )
    32  
    33  func TestUDPv4_Lookup(t *testing.T) {
    34  	t.Parallel()
    35  	test := newUDPTest(t)
    36  
    37  	// Lookup on empty table returns no nodes.
    38  	targetKey, _ := v4wire.DecodePubkey(crypto.S256(), lookupTestnet.target)
    39  	if results := test.udp.LookupPubkey(targetKey); len(results) > 0 {
    40  		t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
    41  	}
    42  
    43  	// Seed table with initial node.
    44  	fillTable(test.table, []*enode.Node{lookupTestnet.node(256, 0)}, true)
    45  
    46  	// Start the lookup.
    47  	resultC := make(chan []*enode.Node, 1)
    48  	go func() {
    49  		resultC <- test.udp.LookupPubkey(targetKey)
    50  		test.close()
    51  	}()
    52  
    53  	// Answer lookup packets.
    54  	serveTestnet(test, lookupTestnet)
    55  
    56  	// Verify result nodes.
    57  	results := <-resultC
    58  	t.Logf("results:")
    59  	for _, e := range results {
    60  		t.Logf("  ld=%d, %x", enode.LogDist(lookupTestnet.target.ID(), e.ID()), e.ID().Bytes())
    61  	}
    62  	if len(results) != bucketSize {
    63  		t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize)
    64  	}
    65  	checkLookupResults(t, lookupTestnet, results)
    66  }
    67  
    68  func TestUDPv4_LookupIterator(t *testing.T) {
    69  	t.Parallel()
    70  	test := newUDPTest(t)
    71  	var wg sync.WaitGroup
    72  	defer func() {
    73  		test.close()
    74  		wg.Wait()
    75  	}()
    76  
    77  	// Seed table with initial nodes.
    78  	bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
    79  	for i := range lookupTestnet.dists[256] {
    80  		bootnodes[i] = lookupTestnet.node(256, i)
    81  	}
    82  	fillTable(test.table, bootnodes, true)
    83  	wg.Add(1)
    84  	go func() {
    85  		serveTestnet(test, lookupTestnet)
    86  		wg.Done()
    87  	}()
    88  
    89  	// Create the iterator and collect the nodes it yields.
    90  	iter := test.udp.RandomNodes()
    91  	seen := make(map[enode.ID]*enode.Node)
    92  	for limit := lookupTestnet.len(); iter.Next() && len(seen) < limit; {
    93  		seen[iter.Node().ID()] = iter.Node()
    94  	}
    95  	iter.Close()
    96  
    97  	// Check that all nodes in lookupTestnet were seen by the iterator.
    98  	results := make([]*enode.Node, 0, len(seen))
    99  	for _, n := range seen {
   100  		results = append(results, n)
   101  	}
   102  	sortByID(results)
   103  	want := lookupTestnet.nodes()
   104  	if err := checkNodesEqual(results, want); err != nil {
   105  		t.Fatal(err)
   106  	}
   107  }
   108  
   109  // TestUDPv4_LookupIteratorClose checks that lookupIterator ends when its Close
   110  // method is called.
   111  func TestUDPv4_LookupIteratorClose(t *testing.T) {
   112  	t.Parallel()
   113  	test := newUDPTest(t)
   114  	var wg sync.WaitGroup
   115  	defer func() {
   116  		test.close()
   117  		wg.Wait()
   118  	}()
   119  
   120  	// Seed table with initial nodes.
   121  	bootnodes := make([]*enode.Node, len(lookupTestnet.dists[256]))
   122  	for i := range lookupTestnet.dists[256] {
   123  		bootnodes[i] = lookupTestnet.node(256, i)
   124  	}
   125  	fillTable(test.table, bootnodes, true)
   126  
   127  	wg.Add(1)
   128  	go func() {
   129  		serveTestnet(test, lookupTestnet)
   130  		wg.Done()
   131  	}()
   132  
   133  	it := test.udp.RandomNodes()
   134  	if ok := it.Next(); !ok || it.Node() == nil {
   135  		t.Fatalf("iterator didn't return any node")
   136  	}
   137  
   138  	it.Close()
   139  
   140  	ncalls := 0
   141  	for ; ncalls < 100 && it.Next(); ncalls++ {
   142  		if it.Node() == nil {
   143  			t.Error("iterator returned Node() == nil node after Next() == true")
   144  		}
   145  	}
   146  	t.Logf("iterator returned %d nodes after close", ncalls)
   147  	if it.Next() {
   148  		t.Errorf("Next() == true after close and %d more calls", ncalls)
   149  	}
   150  	if n := it.Node(); n != nil {
   151  		t.Errorf("iterator returned non-nil node after close and %d more calls", ncalls)
   152  	}
   153  }
   154  
   155  func serveTestnet(test *udpTest, testnet *preminedTestnet) {
   156  	for done := false; !done; {
   157  		done = test.waitPacketOut(func(p v4wire.Packet, to netip.AddrPort, hash []byte) {
   158  			n, key := testnet.nodeByAddr(to)
   159  			switch p.(type) {
   160  			case *v4wire.Ping:
   161  				test.packetInFrom(nil, key, to, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash})
   162  			case *v4wire.Findnode:
   163  				dist := enode.LogDist(n.ID(), testnet.target.ID())
   164  				nodes := testnet.nodesAtDistance(dist - 1)
   165  				test.packetInFrom(nil, key, to, &v4wire.Neighbors{Expiration: futureExp, Nodes: nodes})
   166  			}
   167  		})
   168  	}
   169  }
   170  
   171  // checkLookupResults verifies that the results of a lookup are the closest nodes to
   172  // the testnet's target.
   173  func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node) {
   174  	t.Helper()
   175  	t.Logf("results:")
   176  	for _, e := range results {
   177  		t.Logf("  ld=%d, %x", enode.LogDist(tn.target.ID(), e.ID()), e.ID().Bytes())
   178  	}
   179  	if hasDuplicates(results) {
   180  		t.Errorf("result set contains duplicate entries")
   181  	}
   182  	if !sortedByDistanceTo(tn.target.ID(), results) {
   183  		t.Errorf("result set not sorted by distance to target")
   184  	}
   185  	wantNodes := tn.closest(len(results))
   186  	if err := checkNodesEqual(results, wantNodes); err != nil {
   187  		t.Error(err)
   188  	}
   189  }
   190  
   191  // This is the test network for the Lookup test.
   192  // The nodes were obtained by running lookupTestnet.mine with a random NodeID as target.
   193  var lookupTestnet = &preminedTestnet{
   194  	target: hexEncPubkey("5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125"),
   195  	dists: [257][]*ecdsa.PrivateKey{
   196  		251: {
   197  			hexEncPrivkey("29738ba0c1a4397d6a65f292eee07f02df8e58d41594ba2be3cf84ce0fc58169"),
   198  			hexEncPrivkey("511b1686e4e58a917f7f848e9bf5539d206a68f5ad6b54b552c2399fe7d174ae"),
   199  			hexEncPrivkey("d09e5eaeec0fd596236faed210e55ef45112409a5aa7f3276d26646080dcfaeb"),
   200  			hexEncPrivkey("c1e20dbbf0d530e50573bd0a260b32ec15eb9190032b4633d44834afc8afe578"),
   201  			hexEncPrivkey("ed5f38f5702d92d306143e5d9154fb21819777da39af325ea359f453d179e80b"),
   202  		},
   203  		252: {
   204  			hexEncPrivkey("1c9b1cafbec00848d2c174b858219914b42a7d5c9359b1ca03fd650e8239ae94"),
   205  			hexEncPrivkey("e0e1e8db4a6f13c1ffdd3e96b72fa7012293ced187c9dcdcb9ba2af37a46fa10"),
   206  			hexEncPrivkey("3d53823e0a0295cb09f3e11d16c1b44d07dd37cec6f739b8df3a590189fe9fb9"),
   207  		},
   208  		253: {
   209  			hexEncPrivkey("2d0511ae9bf590166597eeab86b6f27b1ab761761eaea8965487b162f8703847"),
   210  			hexEncPrivkey("6cfbd7b8503073fc3dbdb746a7c672571648d3bd15197ccf7f7fef3d904f53a2"),
   211  			hexEncPrivkey("a30599b12827b69120633f15b98a7f6bc9fc2e9a0fd6ae2ebb767c0e64d743ab"),
   212  			hexEncPrivkey("14a98db9b46a831d67eff29f3b85b1b485bb12ae9796aea98d91be3dc78d8a91"),
   213  			hexEncPrivkey("2369ff1fc1ff8ca7d20b17e2673adc3365c3674377f21c5d9dafaff21fe12e24"),
   214  			hexEncPrivkey("9ae91101d6b5048607f41ec0f690ef5d09507928aded2410aabd9237aa2727d7"),
   215  			hexEncPrivkey("05e3c59090a3fd1ae697c09c574a36fcf9bedd0afa8fe3946f21117319ca4973"),
   216  			hexEncPrivkey("06f31c5ea632658f718a91a1b1b9ae4b7549d7b3bc61cbc2be5f4a439039f3ad"),
   217  		},
   218  		254: {
   219  			hexEncPrivkey("dec742079ec00ff4ec1284d7905bc3de2366f67a0769431fd16f80fd68c58a7c"),
   220  			hexEncPrivkey("ff02c8861fa12fbd129d2a95ea663492ef9c1e51de19dcfbbfe1c59894a28d2b"),
   221  			hexEncPrivkey("4dded9e4eefcbce4262be4fd9e8a773670ab0b5f448f286ec97dfc8cf681444a"),
   222  			hexEncPrivkey("750d931e2a8baa2c9268cb46b7cd851f4198018bed22f4dceb09dd334a2395f6"),
   223  			hexEncPrivkey("ce1435a956a98ffec484cd11489c4f165cf1606819ab6b521cee440f0c677e9e"),
   224  			hexEncPrivkey("996e7f8d1638be92d7328b4770f47e5420fc4bafecb4324fd33b1f5d9f403a75"),
   225  			hexEncPrivkey("ebdc44e77a6cc0eb622e58cf3bb903c3da4c91ca75b447b0168505d8fc308b9c"),
   226  			hexEncPrivkey("46bd1eddcf6431bea66fc19ebc45df191c1c7d6ed552dcdc7392885009c322f0"),
   227  		},
   228  		255: {
   229  			hexEncPrivkey("da8645f90826e57228d9ea72aff84500060ad111a5d62e4af831ed8e4b5acfb8"),
   230  			hexEncPrivkey("3c944c5d9af51d4c1d43f5d0f3a1a7ef65d5e82744d669b58b5fed242941a566"),
   231  			hexEncPrivkey("5ebcde76f1d579eebf6e43b0ffe9157e65ffaa391175d5b9aa988f47df3e33da"),
   232  			hexEncPrivkey("97f78253a7d1d796e4eaabce721febcc4550dd68fb11cc818378ba807a2cb7de"),
   233  			hexEncPrivkey("a38cd7dc9b4079d1c0406afd0fdb1165c285f2c44f946eca96fc67772c988c7d"),
   234  			hexEncPrivkey("d64cbb3ffdf712c372b7a22a176308ef8f91861398d5dbaf326fd89c6eaeef1c"),
   235  			hexEncPrivkey("d269609743ef29d6446e3355ec647e38d919c82a4eb5837e442efd7f4218944f"),
   236  			hexEncPrivkey("d8f7bcc4a530efde1d143717007179e0d9ace405ddaaf151c4d863753b7fd64c"),
   237  		},
   238  		256: {
   239  			hexEncPrivkey("8c5b422155d33ea8e9d46f71d1ad3e7b24cb40051413ffa1a81cff613d243ba9"),
   240  			hexEncPrivkey("937b1af801def4e8f5a3a8bd225a8bcff1db764e41d3e177f2e9376e8dd87233"),
   241  			hexEncPrivkey("120260dce739b6f71f171da6f65bc361b5fad51db74cf02d3e973347819a6518"),
   242  			hexEncPrivkey("1fa56cf25d4b46c2bf94e82355aa631717b63190785ac6bae545a88aadc304a9"),
   243  			hexEncPrivkey("3c38c503c0376f9b4adcbe935d5f4b890391741c764f61b03cd4d0d42deae002"),
   244  			hexEncPrivkey("3a54af3e9fa162bc8623cdf3e5d9b70bf30ade1d54cc3abea8659aba6cff471f"),
   245  			hexEncPrivkey("6799a02ea1999aefdcbcc4d3ff9544478be7365a328d0d0f37c26bd95ade0cda"),
   246  			hexEncPrivkey("e24a7bc9051058f918646b0f6e3d16884b2a55a15553b89bab910d55ebc36116"),
   247  		},
   248  	},
   249  }
   250  
   251  type preminedTestnet struct {
   252  	target v4wire.Pubkey
   253  	dists  [hashBits + 1][]*ecdsa.PrivateKey
   254  }
   255  
   256  func (tn *preminedTestnet) len() int {
   257  	n := 0
   258  	for _, keys := range tn.dists {
   259  		n += len(keys)
   260  	}
   261  	return n
   262  }
   263  
   264  func (tn *preminedTestnet) nodes() []*enode.Node {
   265  	result := make([]*enode.Node, 0, tn.len())
   266  	for dist, keys := range tn.dists {
   267  		for index := range keys {
   268  			result = append(result, tn.node(dist, index))
   269  		}
   270  	}
   271  	sortByID(result)
   272  	return result
   273  }
   274  
   275  func (tn *preminedTestnet) node(dist, index int) *enode.Node {
   276  	key := tn.dists[dist][index]
   277  	rec := new(enr.Record)
   278  	rec.Set(enr.IP{127, byte(dist >> 8), byte(dist), byte(index)})
   279  	rec.Set(enr.UDP(5000))
   280  	enode.SignV4(rec, key)
   281  	n, _ := enode.New(enode.ValidSchemes, rec)
   282  	return n
   283  }
   284  
   285  func (tn *preminedTestnet) nodeByAddr(addr netip.AddrPort) (*enode.Node, *ecdsa.PrivateKey) {
   286  	ip := addr.Addr().As4()
   287  	dist := int(ip[1])<<8 + int(ip[2])
   288  	index := int(ip[3])
   289  	key := tn.dists[dist][index]
   290  	return tn.node(dist, index), key
   291  }
   292  
   293  func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node {
   294  	result := make([]v4wire.Node, len(tn.dists[dist]))
   295  	for i := range result {
   296  		result[i] = nodeToRPC(tn.node(dist, i))
   297  	}
   298  	return result
   299  }
   300  
   301  func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []uint, elems int) []*enode.Node {
   302  	var result []*enode.Node
   303  	for d := range lookupTestnet.dists {
   304  		for i := range lookupTestnet.dists[d] {
   305  			n := lookupTestnet.node(d, i)
   306  			d := enode.LogDist(base.ID(), n.ID())
   307  			if slices.Contains(distances, uint(d)) {
   308  				result = append(result, n)
   309  				if len(result) >= elems {
   310  					return result
   311  				}
   312  			}
   313  		}
   314  	}
   315  	return result
   316  }
   317  
   318  func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) {
   319  	for d := range tn.dists {
   320  		for i := range tn.dists[d] {
   321  			nodes = append(nodes, tn.node(d, i))
   322  		}
   323  	}
   324  	slices.SortFunc(nodes, func(a, b *enode.Node) int {
   325  		return enode.DistCmp(tn.target.ID(), a.ID(), b.ID())
   326  	})
   327  	return nodes[:n]
   328  }
   329  
   330  var _ = (*preminedTestnet).mine // avoid linter warning about mine being dead code.
   331  
   332  // mine generates a testnet struct literal with nodes at
   333  // various distances to the network's target.
   334  func (tn *preminedTestnet) mine() {
   335  	// Clear existing slices first (useful when re-mining).
   336  	for i := range tn.dists {
   337  		tn.dists[i] = nil
   338  	}
   339  
   340  	targetSha := tn.target.ID()
   341  	found, need := 0, 40
   342  	for found < need {
   343  		k := newkey()
   344  		ld := enode.LogDist(targetSha, v4wire.EncodePubkey(&k.PublicKey).ID())
   345  		if len(tn.dists[ld]) < 8 {
   346  			tn.dists[ld] = append(tn.dists[ld], k)
   347  			found++
   348  			fmt.Printf("found ID with ld %d (%d/%d)\n", ld, found, need)
   349  		}
   350  	}
   351  	fmt.Printf("&preminedTestnet{\n")
   352  	fmt.Printf("	target: hexEncPubkey(\"%x\"),\n", tn.target[:])
   353  	fmt.Printf("	dists: [%d][]*ecdsa.PrivateKey{\n", len(tn.dists))
   354  	for ld, ns := range tn.dists {
   355  		if len(ns) == 0 {
   356  			continue
   357  		}
   358  		fmt.Printf("		%d: {\n", ld)
   359  		for _, key := range ns {
   360  			fmt.Printf("			hexEncPrivkey(\"%x\"),\n", crypto.FromECDSA(key))
   361  		}
   362  		fmt.Printf("		},\n")
   363  	}
   364  	fmt.Printf("	},\n")
   365  	fmt.Printf("}\n")
   366  }