github.com/core-coin/go-core/v2@v2.1.9/p2p/discover/table_test.go (about)

     1  // Copyright 2015 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package discover
    18  
    19  import (
    20  	crand "crypto/rand"
    21  	"fmt"
    22  	"math/rand"
    23  	"net"
    24  	"reflect"
    25  	"testing"
    26  	"testing/quick"
    27  	"time"
    28  
    29  	"github.com/core-coin/go-core/v2/crypto"
    30  	"github.com/core-coin/go-core/v2/p2p/enode"
    31  	"github.com/core-coin/go-core/v2/p2p/enr"
    32  	"github.com/core-coin/go-core/v2/p2p/netutil"
    33  )
    34  
    35  func TestTable_pingReplace(t *testing.T) {
    36  	run := func(newNodeResponding, lastInBucketResponding bool) {
    37  		name := fmt.Sprintf("newNodeResponding=%t/lastInBucketResponding=%t", newNodeResponding, lastInBucketResponding)
    38  		t.Run(name, func(t *testing.T) {
    39  			t.Parallel()
    40  			testPingReplace(t, newNodeResponding, lastInBucketResponding)
    41  		})
    42  	}
    43  
    44  	run(true, true)
    45  	run(false, true)
    46  	run(true, false)
    47  	run(false, false)
    48  }
    49  
    50  func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding bool) {
    51  	transport := newPingRecorder()
    52  	tab, db := newTestTable(transport)
    53  	defer db.Close()
    54  	defer tab.close()
    55  
    56  	<-tab.initDone
    57  
    58  	// Fill up the sender's bucket.
    59  	pingKey, _ := crypto.UnmarshalPrivateKeyHex("ab856a9af6b0b651dd2f43b5e12193652ec1701c4da6f1c0d2a366ac4b9dabc9433ef09e41ca129552bd2c029086d9b03604de872a3b343204")
    60  	pingSender := wrapNode(enode.NewV4(pingKey.PublicKey(), net.IP{127, 0, 0, 1}, 99, 99))
    61  	last := fillBucket(tab, pingSender)
    62  
    63  	// Add the sender as if it just pinged us. Revalidate should replace the last node in
    64  	// its bucket if it is unresponsive. Revalidate again to ensure that
    65  	transport.dead[last.ID()] = !lastInBucketIsResponding
    66  	transport.dead[pingSender.ID()] = !newNodeIsResponding
    67  	tab.addSeenNode(pingSender)
    68  	tab.doRevalidate(make(chan struct{}, 1))
    69  	tab.doRevalidate(make(chan struct{}, 1))
    70  
    71  	if !transport.pinged[last.ID()] {
    72  		// Oldest node in bucket is pinged to see whether it is still alive.
    73  		t.Error("table did not ping last node in bucket")
    74  	}
    75  
    76  	tab.mutex.Lock()
    77  	defer tab.mutex.Unlock()
    78  	wantSize := bucketSize
    79  	if !lastInBucketIsResponding && !newNodeIsResponding {
    80  		wantSize--
    81  	}
    82  	if l := len(tab.bucket(pingSender.ID()).entries); l != wantSize {
    83  		t.Errorf("wrong bucket size after bond: got %d, want %d", l, wantSize)
    84  	}
    85  	if found := contains(tab.bucket(pingSender.ID()).entries, last.ID()); found != lastInBucketIsResponding {
    86  		t.Errorf("last entry found: %t, want: %t", found, lastInBucketIsResponding)
    87  	}
    88  	wantNewEntry := newNodeIsResponding && !lastInBucketIsResponding
    89  	if found := contains(tab.bucket(pingSender.ID()).entries, pingSender.ID()); found != wantNewEntry {
    90  		t.Errorf("new entry found: %t, want: %t", found, wantNewEntry)
    91  	}
    92  }
    93  
    94  func TestBucket_bumpNoDuplicates(t *testing.T) {
    95  	t.Parallel()
    96  	cfg := &quick.Config{
    97  		MaxCount: 1000,
    98  		Rand:     rand.New(rand.NewSource(time.Now().Unix())),
    99  		Values: func(args []reflect.Value, rand *rand.Rand) {
   100  			// generate a random list of nodes. this will be the content of the bucket.
   101  			n := rand.Intn(bucketSize-1) + 1
   102  			nodes := make([]*node, n)
   103  			for i := range nodes {
   104  				nodes[i] = nodeAtDistance(enode.ID{}, 200, intIP(200))
   105  			}
   106  			args[0] = reflect.ValueOf(nodes)
   107  			// generate random bump positions.
   108  			bumps := make([]int, rand.Intn(100))
   109  			for i := range bumps {
   110  				bumps[i] = rand.Intn(len(nodes))
   111  			}
   112  			args[1] = reflect.ValueOf(bumps)
   113  		},
   114  	}
   115  
   116  	prop := func(nodes []*node, bumps []int) (ok bool) {
   117  		tab, db := newTestTable(newPingRecorder())
   118  		defer db.Close()
   119  		defer tab.close()
   120  
   121  		b := &bucket{entries: make([]*node, len(nodes))}
   122  		copy(b.entries, nodes)
   123  		for i, pos := range bumps {
   124  			tab.bumpInBucket(b, b.entries[pos])
   125  			if hasDuplicates(b.entries) {
   126  				t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps))
   127  				for _, n := range b.entries {
   128  					t.Logf("  %p", n)
   129  				}
   130  				return false
   131  			}
   132  		}
   133  		checkIPLimitInvariant(t, tab)
   134  		return true
   135  	}
   136  	if err := quick.Check(prop, cfg); err != nil {
   137  		t.Error(err)
   138  	}
   139  }
   140  
   141  // This checks that the table-wide IP limit is applied correctly.
   142  func TestTable_IPLimit(t *testing.T) {
   143  	transport := newPingRecorder()
   144  	tab, db := newTestTable(transport)
   145  	defer db.Close()
   146  	defer tab.close()
   147  
   148  	for i := 0; i < tableIPLimit+1; i++ {
   149  		n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
   150  		tab.addSeenNode(n)
   151  	}
   152  	if tab.len() > tableIPLimit {
   153  		t.Errorf("too many nodes in table")
   154  	}
   155  	checkIPLimitInvariant(t, tab)
   156  }
   157  
   158  // This checks that the per-bucket IP limit is applied correctly.
   159  func TestTable_BucketIPLimit(t *testing.T) {
   160  	transport := newPingRecorder()
   161  	tab, db := newTestTable(transport)
   162  	defer db.Close()
   163  	defer tab.close()
   164  
   165  	d := 3
   166  	for i := 0; i < bucketIPLimit+1; i++ {
   167  		n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
   168  		tab.addSeenNode(n)
   169  	}
   170  	if tab.len() > bucketIPLimit {
   171  		t.Errorf("too many nodes in table")
   172  	}
   173  	checkIPLimitInvariant(t, tab)
   174  }
   175  
   176  // checkIPLimitInvariant checks that ip limit sets contain an entry for every
   177  // node in the table and no extra entries.
   178  func checkIPLimitInvariant(t *testing.T, tab *Table) {
   179  	t.Helper()
   180  
   181  	tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit}
   182  	for _, b := range tab.buckets {
   183  		for _, n := range b.entries {
   184  			tabset.Add(n.IP())
   185  		}
   186  	}
   187  	if tabset.String() != tab.ips.String() {
   188  		t.Errorf("table IP set is incorrect:\nhave: %v\nwant: %v", tab.ips, tabset)
   189  	}
   190  }
   191  
   192  func TestTable_findnodeByID(t *testing.T) {
   193  	t.Parallel()
   194  
   195  	test := func(test *closeTest) bool {
   196  		// for any node table, Target and N
   197  		transport := newPingRecorder()
   198  		tab, db := newTestTable(transport)
   199  		defer db.Close()
   200  		defer tab.close()
   201  		fillTable(tab, test.All)
   202  
   203  		// check that closest(Target, N) returns nodes
   204  		result := tab.findnodeByID(test.Target, test.N, false).entries
   205  		if hasDuplicates(result) {
   206  			t.Errorf("result contains duplicates")
   207  			return false
   208  		}
   209  		if !sortedByDistanceTo(test.Target, result) {
   210  			t.Errorf("result is not sorted by distance to target")
   211  			return false
   212  		}
   213  
   214  		// check that the number of results is min(N, tablen)
   215  		wantN := test.N
   216  		if tlen := tab.len(); tlen < test.N {
   217  			wantN = tlen
   218  		}
   219  		if len(result) != wantN {
   220  			t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN)
   221  			return false
   222  		} else if len(result) == 0 {
   223  			return true // no need to check distance
   224  		}
   225  
   226  		// check that the result nodes have minimum distance to target.
   227  		for _, b := range tab.buckets {
   228  			for _, n := range b.entries {
   229  				if contains(result, n.ID()) {
   230  					continue // don't run the check below for nodes in result
   231  				}
   232  				farthestResult := result[len(result)-1].ID()
   233  				if enode.DistCmp(test.Target, n.ID(), farthestResult) < 0 {
   234  					t.Errorf("table contains node that is closer to target but it's not in result")
   235  					t.Logf("  Target:          %v", test.Target)
   236  					t.Logf("  Farthest Result: %v", farthestResult)
   237  					t.Logf("  ID:              %v", n.ID())
   238  					return false
   239  				}
   240  			}
   241  		}
   242  		return true
   243  	}
   244  	if err := quick.Check(test, quickcfg()); err != nil {
   245  		t.Error(err)
   246  	}
   247  }
   248  
   249  func TestTable_ReadRandomNodesGetAll(t *testing.T) {
   250  	cfg := &quick.Config{
   251  		MaxCount: 200,
   252  		Rand:     rand.New(rand.NewSource(time.Now().Unix())),
   253  		Values: func(args []reflect.Value, rand *rand.Rand) {
   254  			args[0] = reflect.ValueOf(make([]*enode.Node, rand.Intn(1000)))
   255  		},
   256  	}
   257  	test := func(buf []*enode.Node) bool {
   258  		transport := newPingRecorder()
   259  		tab, db := newTestTable(transport)
   260  		defer db.Close()
   261  		defer tab.close()
   262  		<-tab.initDone
   263  
   264  		for i := 0; i < len(buf); i++ {
   265  			ld := cfg.Rand.Intn(len(tab.buckets))
   266  			fillTable(tab, []*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))})
   267  		}
   268  		gotN := tab.ReadRandomNodes(buf)
   269  		if gotN != tab.len() {
   270  			t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len())
   271  			return false
   272  		}
   273  		if hasDuplicates(wrapNodes(buf[:gotN])) {
   274  			t.Errorf("result contains duplicates")
   275  			return false
   276  		}
   277  		return true
   278  	}
   279  	if err := quick.Check(test, cfg); err != nil {
   280  		t.Error(err)
   281  	}
   282  }
   283  
   284  type closeTest struct {
   285  	Self   enode.ID
   286  	Target enode.ID
   287  	All    []*node
   288  	N      int
   289  }
   290  
   291  func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
   292  	t := &closeTest{
   293  		Self:   gen(enode.ID{}, rand).(enode.ID),
   294  		Target: gen(enode.ID{}, rand).(enode.ID),
   295  		N:      rand.Intn(bucketSize),
   296  	}
   297  	for _, id := range gen([]enode.ID{}, rand).([]enode.ID) {
   298  		r := new(enr.Record)
   299  		r.Set(enr.IP(genIP(rand)))
   300  		n := wrapNode(enode.SignNull(r, id))
   301  		n.livenessChecks = 1
   302  		t.All = append(t.All, n)
   303  	}
   304  	return reflect.ValueOf(t)
   305  }
   306  
   307  func TestTable_addVerifiedNode(t *testing.T) {
   308  	tab, db := newTestTable(newPingRecorder())
   309  	<-tab.initDone
   310  	defer db.Close()
   311  	defer tab.close()
   312  
   313  	// Insert two nodes.
   314  	n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
   315  	n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
   316  	tab.addSeenNode(n1)
   317  	tab.addSeenNode(n2)
   318  
   319  	// Verify bucket content:
   320  	bcontent := []*node{n1, n2}
   321  	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
   322  		t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
   323  	}
   324  
   325  	// Add a changed version of n2.
   326  	newrec := n2.Record()
   327  	newrec.Set(enr.IP{99, 99, 99, 99})
   328  	newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
   329  	tab.addVerifiedNode(newn2)
   330  
   331  	// Check that bucket is updated correctly.
   332  	newBcontent := []*node{newn2, n1}
   333  	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, newBcontent) {
   334  		t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
   335  	}
   336  	checkIPLimitInvariant(t, tab)
   337  }
   338  
   339  func TestTable_addSeenNode(t *testing.T) {
   340  	tab, db := newTestTable(newPingRecorder())
   341  	<-tab.initDone
   342  	defer db.Close()
   343  	defer tab.close()
   344  
   345  	// Insert two nodes.
   346  	n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
   347  	n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
   348  	tab.addSeenNode(n1)
   349  	tab.addSeenNode(n2)
   350  
   351  	// Verify bucket content:
   352  	bcontent := []*node{n1, n2}
   353  	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
   354  		t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
   355  	}
   356  
   357  	// Add a changed version of n2.
   358  	newrec := n2.Record()
   359  	newrec.Set(enr.IP{99, 99, 99, 99})
   360  	newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
   361  	tab.addSeenNode(newn2)
   362  
   363  	// Check that bucket content is unchanged.
   364  	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
   365  		t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
   366  	}
   367  	checkIPLimitInvariant(t, tab)
   368  }
   369  
   370  // This test checks that ENR updates happen during revalidation. If a node in the table
   371  // announces a new sequence number, the new record should be pulled.
   372  func TestTable_revalidateSyncRecord(t *testing.T) {
   373  	transport := newPingRecorder()
   374  	tab, db := newTestTable(transport)
   375  	<-tab.initDone
   376  	defer db.Close()
   377  	defer tab.close()
   378  
   379  	// Insert a node.
   380  	var r enr.Record
   381  	r.Set(enr.IP(net.IP{127, 0, 0, 1}))
   382  	id := enode.ID{1}
   383  	n1 := wrapNode(enode.SignNull(&r, id))
   384  	tab.addSeenNode(n1)
   385  
   386  	// Update the node record.
   387  	r.Set(enr.WithEntry("foo", "bar"))
   388  	n2 := enode.SignNull(&r, id)
   389  	transport.updateRecord(n2)
   390  
   391  	tab.doRevalidate(make(chan struct{}, 1))
   392  	intable := tab.getNode(id)
   393  	if !reflect.DeepEqual(intable, n2) {
   394  		t.Fatalf("table contains old record with seq %d, want seq %d", intable.Seq(), n2.Seq())
   395  	}
   396  }
   397  
   398  // gen wraps quick.Value so it's easier to use.
   399  // it generates a random value of the given value's type.
   400  func gen(typ interface{}, rand *rand.Rand) interface{} {
   401  	v, ok := quick.Value(reflect.TypeOf(typ), rand)
   402  	if !ok {
   403  		panic(fmt.Sprintf("couldn't generate random value of type %T", typ))
   404  	}
   405  	return v.Interface()
   406  }
   407  
   408  func genIP(rand *rand.Rand) net.IP {
   409  	ip := make(net.IP, 4)
   410  	rand.Read(ip)
   411  	return ip
   412  }
   413  
   414  func quickcfg() *quick.Config {
   415  	return &quick.Config{
   416  		MaxCount: 5000,
   417  		Rand:     rand.New(rand.NewSource(time.Now().Unix())),
   418  	}
   419  }
   420  
   421  func newkey() *crypto.PrivateKey {
   422  	key, err := crypto.GenerateKey(crand.Reader)
   423  	if err != nil {
   424  		panic("couldn't generate key: " + err.Error())
   425  	}
   426  	return key
   427  }