github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/discover/table_test.go (about)

     1  package discover
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"errors"
     6  	"fmt"
     7  	"math/rand"
     8  	"net"
     9  	"reflect"
    10  	"testing"
    11  	"testing/quick"
    12  	"time"
    13  
    14  	"github.com/jonasnick/go-ethereum/crypto"
    15  )
    16  
    17  func TestTable_bumpOrAddBucketAssign(t *testing.T) {
    18  	tab := newTable(nil, NodeID{}, &net.UDPAddr{})
    19  	for i := 1; i < len(tab.buckets); i++ {
    20  		tab.bumpOrAdd(randomID(tab.self.ID, i), &net.UDPAddr{})
    21  	}
    22  	for i, b := range tab.buckets {
    23  		if i > 0 && len(b.entries) != 1 {
    24  			t.Errorf("bucket %d has %d entries, want 1", i, len(b.entries))
    25  		}
    26  	}
    27  }
    28  
    29  func TestTable_bumpOrAddPingReplace(t *testing.T) {
    30  	pingC := make(pingC)
    31  	tab := newTable(pingC, NodeID{}, &net.UDPAddr{})
    32  	last := fillBucket(tab, 200)
    33  
    34  	// this bumpOrAdd should not replace the last node
    35  	// because the node replies to ping.
    36  	new := tab.bumpOrAdd(randomID(tab.self.ID, 200), &net.UDPAddr{})
    37  
    38  	pinged := <-pingC
    39  	if pinged != last.ID {
    40  		t.Fatalf("pinged wrong node: %v\nwant %v", pinged, last.ID)
    41  	}
    42  
    43  	tab.mutex.Lock()
    44  	defer tab.mutex.Unlock()
    45  	if l := len(tab.buckets[200].entries); l != bucketSize {
    46  		t.Errorf("wrong bucket size after bumpOrAdd: got %d, want %d", bucketSize, l)
    47  	}
    48  	if !contains(tab.buckets[200].entries, last.ID) {
    49  		t.Error("last entry was removed")
    50  	}
    51  	if contains(tab.buckets[200].entries, new.ID) {
    52  		t.Error("new entry was added")
    53  	}
    54  }
    55  
    56  func TestTable_bumpOrAddPingTimeout(t *testing.T) {
    57  	tab := newTable(pingC(nil), NodeID{}, &net.UDPAddr{})
    58  	last := fillBucket(tab, 200)
    59  
    60  	// this bumpOrAdd should replace the last node
    61  	// because the node does not reply to ping.
    62  	new := tab.bumpOrAdd(randomID(tab.self.ID, 200), &net.UDPAddr{})
    63  
    64  	// wait for async bucket update. damn. this needs to go away.
    65  	time.Sleep(2 * time.Millisecond)
    66  
    67  	tab.mutex.Lock()
    68  	defer tab.mutex.Unlock()
    69  	if l := len(tab.buckets[200].entries); l != bucketSize {
    70  		t.Errorf("wrong bucket size after bumpOrAdd: got %d, want %d", bucketSize, l)
    71  	}
    72  	if contains(tab.buckets[200].entries, last.ID) {
    73  		t.Error("last entry was not removed")
    74  	}
    75  	if !contains(tab.buckets[200].entries, new.ID) {
    76  		t.Error("new entry was not added")
    77  	}
    78  }
    79  
    80  func fillBucket(tab *Table, ld int) (last *Node) {
    81  	b := tab.buckets[ld]
    82  	for len(b.entries) < bucketSize {
    83  		b.entries = append(b.entries, &Node{ID: randomID(tab.self.ID, ld)})
    84  	}
    85  	return b.entries[bucketSize-1]
    86  }
    87  
    88  type pingC chan NodeID
    89  
    90  func (t pingC) findnode(n *Node, target NodeID) ([]*Node, error) {
    91  	panic("findnode called on pingRecorder")
    92  }
    93  func (t pingC) close() {
    94  	panic("close called on pingRecorder")
    95  }
    96  func (t pingC) ping(n *Node) error {
    97  	if t == nil {
    98  		return errTimeout
    99  	}
   100  	t <- n.ID
   101  	return nil
   102  }
   103  
   104  func TestTable_bump(t *testing.T) {
   105  	tab := newTable(nil, NodeID{}, &net.UDPAddr{})
   106  
   107  	// add an old entry and two recent ones
   108  	oldactive := time.Now().Add(-2 * time.Minute)
   109  	old := &Node{ID: randomID(tab.self.ID, 200), active: oldactive}
   110  	others := []*Node{
   111  		&Node{ID: randomID(tab.self.ID, 200), active: time.Now()},
   112  		&Node{ID: randomID(tab.self.ID, 200), active: time.Now()},
   113  	}
   114  	tab.add(append(others, old))
   115  	if tab.buckets[200].entries[0] == old {
   116  		t.Fatal("old entry is at front of bucket")
   117  	}
   118  
   119  	// bumping the old entry should move it to the front
   120  	tab.bump(old.ID)
   121  	if old.active == oldactive {
   122  		t.Error("activity timestamp not updated")
   123  	}
   124  	if tab.buckets[200].entries[0] != old {
   125  		t.Errorf("bumped entry did not move to the front of bucket")
   126  	}
   127  }
   128  
   129  func TestTable_closest(t *testing.T) {
   130  	t.Parallel()
   131  
   132  	test := func(test *closeTest) bool {
   133  		// for any node table, Target and N
   134  		tab := newTable(nil, test.Self, &net.UDPAddr{})
   135  		tab.add(test.All)
   136  
   137  		// check that doClosest(Target, N) returns nodes
   138  		result := tab.closest(test.Target, test.N).entries
   139  		if hasDuplicates(result) {
   140  			t.Errorf("result contains duplicates")
   141  			return false
   142  		}
   143  		if !sortedByDistanceTo(test.Target, result) {
   144  			t.Errorf("result is not sorted by distance to target")
   145  			return false
   146  		}
   147  
   148  		// check that the number of results is min(N, tablen)
   149  		wantN := test.N
   150  		if tlen := tab.len(); tlen < test.N {
   151  			wantN = tlen
   152  		}
   153  		if len(result) != wantN {
   154  			t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN)
   155  			return false
   156  		} else if len(result) == 0 {
   157  			return true // no need to check distance
   158  		}
   159  
   160  		// check that the result nodes have minimum distance to target.
   161  		for _, b := range tab.buckets {
   162  			for _, n := range b.entries {
   163  				if contains(result, n.ID) {
   164  					continue // don't run the check below for nodes in result
   165  				}
   166  				farthestResult := result[len(result)-1].ID
   167  				if distcmp(test.Target, n.ID, farthestResult) < 0 {
   168  					t.Errorf("table contains node that is closer to target but it's not in result")
   169  					t.Logf("  Target:          %v", test.Target)
   170  					t.Logf("  Farthest Result: %v", farthestResult)
   171  					t.Logf("  ID:              %v", n.ID)
   172  					return false
   173  				}
   174  			}
   175  		}
   176  		return true
   177  	}
   178  	if err := quick.Check(test, quickcfg); err != nil {
   179  		t.Error(err)
   180  	}
   181  }
   182  
   183  type closeTest struct {
   184  	Self   NodeID
   185  	Target NodeID
   186  	All    []*Node
   187  	N      int
   188  }
   189  
   190  func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
   191  	t := &closeTest{
   192  		Self:   gen(NodeID{}, rand).(NodeID),
   193  		Target: gen(NodeID{}, rand).(NodeID),
   194  		N:      rand.Intn(bucketSize),
   195  	}
   196  	for _, id := range gen([]NodeID{}, rand).([]NodeID) {
   197  		t.All = append(t.All, &Node{ID: id})
   198  	}
   199  	return reflect.ValueOf(t)
   200  }
   201  
   202  func TestTable_Lookup(t *testing.T) {
   203  	self := gen(NodeID{}, quickrand).(NodeID)
   204  	target := randomID(self, 200)
   205  	transport := findnodeOracle{t, target}
   206  	tab := newTable(transport, self, &net.UDPAddr{})
   207  
   208  	// lookup on empty table returns no nodes
   209  	if results := tab.Lookup(target); len(results) > 0 {
   210  		t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
   211  	}
   212  	// seed table with initial node (otherwise lookup will terminate immediately)
   213  	tab.bumpOrAdd(randomID(target, 200), &net.UDPAddr{Port: 200})
   214  
   215  	results := tab.Lookup(target)
   216  	t.Logf("results:")
   217  	for _, e := range results {
   218  		t.Logf("  ld=%d, %v", logdist(target, e.ID), e.ID)
   219  	}
   220  	if len(results) != bucketSize {
   221  		t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize)
   222  	}
   223  	if hasDuplicates(results) {
   224  		t.Errorf("result set contains duplicate entries")
   225  	}
   226  	if !sortedByDistanceTo(target, results) {
   227  		t.Errorf("result set not sorted by distance to target")
   228  	}
   229  	if !contains(results, target) {
   230  		t.Errorf("result set does not contain target")
   231  	}
   232  }
   233  
   234  // findnode on this transport always returns at least one node
   235  // that is one bucket closer to the target.
   236  type findnodeOracle struct {
   237  	t      *testing.T
   238  	target NodeID
   239  }
   240  
   241  func (t findnodeOracle) findnode(n *Node, target NodeID) ([]*Node, error) {
   242  	t.t.Logf("findnode query at dist %d", n.DiscPort)
   243  	// current log distance is encoded in port number
   244  	var result []*Node
   245  	switch n.DiscPort {
   246  	case 0:
   247  		panic("query to node at distance 0")
   248  	default:
   249  		// TODO: add more randomness to distances
   250  		next := n.DiscPort - 1
   251  		for i := 0; i < bucketSize; i++ {
   252  			result = append(result, &Node{ID: randomID(t.target, next), DiscPort: next})
   253  		}
   254  	}
   255  	return result, nil
   256  }
   257  
   258  func (t findnodeOracle) close() {}
   259  
   260  func (t findnodeOracle) ping(n *Node) error {
   261  	return errors.New("ping is not supported by this transport")
   262  }
   263  
   264  func hasDuplicates(slice []*Node) bool {
   265  	seen := make(map[NodeID]bool)
   266  	for _, e := range slice {
   267  		if seen[e.ID] {
   268  			return true
   269  		}
   270  		seen[e.ID] = true
   271  	}
   272  	return false
   273  }
   274  
   275  func sortedByDistanceTo(distbase NodeID, slice []*Node) bool {
   276  	var last NodeID
   277  	for i, e := range slice {
   278  		if i > 0 && distcmp(distbase, e.ID, last) < 0 {
   279  			return false
   280  		}
   281  		last = e.ID
   282  	}
   283  	return true
   284  }
   285  
   286  func contains(ns []*Node, id NodeID) bool {
   287  	for _, n := range ns {
   288  		if n.ID == id {
   289  			return true
   290  		}
   291  	}
   292  	return false
   293  }
   294  
   295  // gen wraps quick.Value so it's easier to use.
   296  // it generates a random value of the given value's type.
   297  func gen(typ interface{}, rand *rand.Rand) interface{} {
   298  	v, ok := quick.Value(reflect.TypeOf(typ), rand)
   299  	if !ok {
   300  		panic(fmt.Sprintf("couldn't generate random value of type %T", typ))
   301  	}
   302  	return v.Interface()
   303  }
   304  
   305  func newkey() *ecdsa.PrivateKey {
   306  	key, err := crypto.GenerateKey()
   307  	if err != nil {
   308  		panic("couldn't generate key: " + err.Error())
   309  	}
   310  	return key
   311  }