github.com/murrekatt/go-ethereum@v1.5.8-0.20170123175102-fc52f2c007fb/p2p/dial_test.go (about)

     1  // Copyright 2015 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 p2p
    18  
    19  import (
    20  	"encoding/binary"
    21  	"net"
    22  	"reflect"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/davecgh/go-spew/spew"
    27  	"github.com/ethereum/go-ethereum/p2p/discover"
    28  	"github.com/ethereum/go-ethereum/p2p/netutil"
    29  )
    30  
    31  func init() {
    32  	spew.Config.Indent = "\t"
    33  }
    34  
    35  type dialtest struct {
    36  	init   *dialstate // state before and after the test.
    37  	rounds []round
    38  }
    39  
    40  type round struct {
    41  	peers []*Peer // current peer set
    42  	done  []task  // tasks that got done this round
    43  	new   []task  // the result must match this one
    44  }
    45  
    46  func runDialTest(t *testing.T, test dialtest) {
    47  	var (
    48  		vtime   time.Time
    49  		running int
    50  	)
    51  	pm := func(ps []*Peer) map[discover.NodeID]*Peer {
    52  		m := make(map[discover.NodeID]*Peer)
    53  		for _, p := range ps {
    54  			m[p.rw.id] = p
    55  		}
    56  		return m
    57  	}
    58  	for i, round := range test.rounds {
    59  		for _, task := range round.done {
    60  			running--
    61  			if running < 0 {
    62  				panic("running task counter underflow")
    63  			}
    64  			test.init.taskDone(task, vtime)
    65  		}
    66  
    67  		new := test.init.newTasks(running, pm(round.peers), vtime)
    68  		if !sametasks(new, round.new) {
    69  			t.Errorf("round %d: new tasks mismatch:\ngot %v\nwant %v\nstate: %v\nrunning: %v\n",
    70  				i, spew.Sdump(new), spew.Sdump(round.new), spew.Sdump(test.init), spew.Sdump(running))
    71  		}
    72  
    73  		// Time advances by 16 seconds on every round.
    74  		vtime = vtime.Add(16 * time.Second)
    75  		running += len(new)
    76  	}
    77  }
    78  
    79  type fakeTable []*discover.Node
    80  
    81  func (t fakeTable) Self() *discover.Node                     { return new(discover.Node) }
    82  func (t fakeTable) Close()                                   {}
    83  func (t fakeTable) Lookup(discover.NodeID) []*discover.Node  { return nil }
    84  func (t fakeTable) Resolve(discover.NodeID) *discover.Node   { return nil }
    85  func (t fakeTable) ReadRandomNodes(buf []*discover.Node) int { return copy(buf, t) }
    86  
    87  // This test checks that dynamic dials are launched from discovery results.
    88  func TestDialStateDynDial(t *testing.T) {
    89  	runDialTest(t, dialtest{
    90  		init: newDialState(nil, fakeTable{}, 5, nil),
    91  		rounds: []round{
    92  			// A discovery query is launched.
    93  			{
    94  				peers: []*Peer{
    95  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
    96  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
    97  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
    98  				},
    99  				new: []task{&discoverTask{}},
   100  			},
   101  			// Dynamic dials are launched when it completes.
   102  			{
   103  				peers: []*Peer{
   104  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
   105  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   106  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   107  				},
   108  				done: []task{
   109  					&discoverTask{results: []*discover.Node{
   110  						{ID: uintID(2)}, // this one is already connected and not dialed.
   111  						{ID: uintID(3)},
   112  						{ID: uintID(4)},
   113  						{ID: uintID(5)},
   114  						{ID: uintID(6)}, // these are not tried because max dyn dials is 5
   115  						{ID: uintID(7)}, // ...
   116  					}},
   117  				},
   118  				new: []task{
   119  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
   120  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
   121  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
   122  				},
   123  			},
   124  			// Some of the dials complete but no new ones are launched yet because
   125  			// the sum of active dial count and dynamic peer count is == maxDynDials.
   126  			{
   127  				peers: []*Peer{
   128  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
   129  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   130  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   131  					{rw: &conn{flags: dynDialedConn, id: uintID(3)}},
   132  					{rw: &conn{flags: dynDialedConn, id: uintID(4)}},
   133  				},
   134  				done: []task{
   135  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
   136  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
   137  				},
   138  			},
   139  			// No new dial tasks are launched in the this round because
   140  			// maxDynDials has been reached.
   141  			{
   142  				peers: []*Peer{
   143  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
   144  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   145  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   146  					{rw: &conn{flags: dynDialedConn, id: uintID(3)}},
   147  					{rw: &conn{flags: dynDialedConn, id: uintID(4)}},
   148  					{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
   149  				},
   150  				done: []task{
   151  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
   152  				},
   153  				new: []task{
   154  					&waitExpireTask{Duration: 14 * time.Second},
   155  				},
   156  			},
   157  			// In this round, the peer with id 2 drops off. The query
   158  			// results from last discovery lookup are reused.
   159  			{
   160  				peers: []*Peer{
   161  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
   162  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   163  					{rw: &conn{flags: dynDialedConn, id: uintID(3)}},
   164  					{rw: &conn{flags: dynDialedConn, id: uintID(4)}},
   165  					{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
   166  				},
   167  				new: []task{
   168  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}},
   169  				},
   170  			},
   171  			// More peers (3,4) drop off and dial for ID 6 completes.
   172  			// The last query result from the discovery lookup is reused
   173  			// and a new one is spawned because more candidates are needed.
   174  			{
   175  				peers: []*Peer{
   176  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
   177  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   178  					{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
   179  				},
   180  				done: []task{
   181  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(6)}},
   182  				},
   183  				new: []task{
   184  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}},
   185  					&discoverTask{},
   186  				},
   187  			},
   188  			// Peer 7 is connected, but there still aren't enough dynamic peers
   189  			// (4 out of 5). However, a discovery is already running, so ensure
   190  			// no new is started.
   191  			{
   192  				peers: []*Peer{
   193  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
   194  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   195  					{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
   196  					{rw: &conn{flags: dynDialedConn, id: uintID(7)}},
   197  				},
   198  				done: []task{
   199  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(7)}},
   200  				},
   201  			},
   202  			// Finish the running node discovery with an empty set. A new lookup
   203  			// should be immediately requested.
   204  			{
   205  				peers: []*Peer{
   206  					{rw: &conn{flags: staticDialedConn, id: uintID(0)}},
   207  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   208  					{rw: &conn{flags: dynDialedConn, id: uintID(5)}},
   209  					{rw: &conn{flags: dynDialedConn, id: uintID(7)}},
   210  				},
   211  				done: []task{
   212  					&discoverTask{},
   213  				},
   214  				new: []task{
   215  					&discoverTask{},
   216  				},
   217  			},
   218  		},
   219  	})
   220  }
   221  
   222  func TestDialStateDynDialFromTable(t *testing.T) {
   223  	// This table always returns the same random nodes
   224  	// in the order given below.
   225  	table := fakeTable{
   226  		{ID: uintID(1)},
   227  		{ID: uintID(2)},
   228  		{ID: uintID(3)},
   229  		{ID: uintID(4)},
   230  		{ID: uintID(5)},
   231  		{ID: uintID(6)},
   232  		{ID: uintID(7)},
   233  		{ID: uintID(8)},
   234  	}
   235  
   236  	runDialTest(t, dialtest{
   237  		init: newDialState(nil, table, 10, nil),
   238  		rounds: []round{
   239  			// 5 out of 8 of the nodes returned by ReadRandomNodes are dialed.
   240  			{
   241  				new: []task{
   242  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
   243  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
   244  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
   245  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
   246  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
   247  					&discoverTask{},
   248  				},
   249  			},
   250  			// Dialing nodes 1,2 succeeds. Dials from the lookup are launched.
   251  			{
   252  				peers: []*Peer{
   253  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   254  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   255  				},
   256  				done: []task{
   257  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(1)}},
   258  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(2)}},
   259  					&discoverTask{results: []*discover.Node{
   260  						{ID: uintID(10)},
   261  						{ID: uintID(11)},
   262  						{ID: uintID(12)},
   263  					}},
   264  				},
   265  				new: []task{
   266  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}},
   267  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}},
   268  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}},
   269  					&discoverTask{},
   270  				},
   271  			},
   272  			// Dialing nodes 3,4,5 fails. The dials from the lookup succeed.
   273  			{
   274  				peers: []*Peer{
   275  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   276  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   277  					{rw: &conn{flags: dynDialedConn, id: uintID(10)}},
   278  					{rw: &conn{flags: dynDialedConn, id: uintID(11)}},
   279  					{rw: &conn{flags: dynDialedConn, id: uintID(12)}},
   280  				},
   281  				done: []task{
   282  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(3)}},
   283  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(4)}},
   284  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(5)}},
   285  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(10)}},
   286  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(11)}},
   287  					&dialTask{flags: dynDialedConn, dest: &discover.Node{ID: uintID(12)}},
   288  				},
   289  			},
   290  			// Waiting for expiry. No waitExpireTask is launched because the
   291  			// discovery query is still running.
   292  			{
   293  				peers: []*Peer{
   294  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   295  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   296  					{rw: &conn{flags: dynDialedConn, id: uintID(10)}},
   297  					{rw: &conn{flags: dynDialedConn, id: uintID(11)}},
   298  					{rw: &conn{flags: dynDialedConn, id: uintID(12)}},
   299  				},
   300  			},
   301  			// Nodes 3,4 are not tried again because only the first two
   302  			// returned random nodes (nodes 1,2) are tried and they're
   303  			// already connected.
   304  			{
   305  				peers: []*Peer{
   306  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   307  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   308  					{rw: &conn{flags: dynDialedConn, id: uintID(10)}},
   309  					{rw: &conn{flags: dynDialedConn, id: uintID(11)}},
   310  					{rw: &conn{flags: dynDialedConn, id: uintID(12)}},
   311  				},
   312  			},
   313  		},
   314  	})
   315  }
   316  
   317  // This test checks that candidates that do not match the netrestrict list are not dialed.
   318  func TestDialStateNetRestrict(t *testing.T) {
   319  	// This table always returns the same random nodes
   320  	// in the order given below.
   321  	table := fakeTable{
   322  		{ID: uintID(1), IP: net.ParseIP("127.0.0.1")},
   323  		{ID: uintID(2), IP: net.ParseIP("127.0.0.2")},
   324  		{ID: uintID(3), IP: net.ParseIP("127.0.0.3")},
   325  		{ID: uintID(4), IP: net.ParseIP("127.0.0.4")},
   326  		{ID: uintID(5), IP: net.ParseIP("127.0.2.5")},
   327  		{ID: uintID(6), IP: net.ParseIP("127.0.2.6")},
   328  		{ID: uintID(7), IP: net.ParseIP("127.0.2.7")},
   329  		{ID: uintID(8), IP: net.ParseIP("127.0.2.8")},
   330  	}
   331  	restrict := new(netutil.Netlist)
   332  	restrict.Add("127.0.2.0/24")
   333  
   334  	runDialTest(t, dialtest{
   335  		init: newDialState(nil, table, 10, restrict),
   336  		rounds: []round{
   337  			{
   338  				new: []task{
   339  					&dialTask{flags: dynDialedConn, dest: table[4]},
   340  					&discoverTask{},
   341  				},
   342  			},
   343  		},
   344  	})
   345  }
   346  
   347  // This test checks that static dials are launched.
   348  func TestDialStateStaticDial(t *testing.T) {
   349  	wantStatic := []*discover.Node{
   350  		{ID: uintID(1)},
   351  		{ID: uintID(2)},
   352  		{ID: uintID(3)},
   353  		{ID: uintID(4)},
   354  		{ID: uintID(5)},
   355  	}
   356  
   357  	runDialTest(t, dialtest{
   358  		init: newDialState(wantStatic, fakeTable{}, 0, nil),
   359  		rounds: []round{
   360  			// Static dials are launched for the nodes that
   361  			// aren't yet connected.
   362  			{
   363  				peers: []*Peer{
   364  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   365  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   366  				},
   367  				new: []task{
   368  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
   369  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
   370  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}},
   371  				},
   372  			},
   373  			// No new tasks are launched in this round because all static
   374  			// nodes are either connected or still being dialed.
   375  			{
   376  				peers: []*Peer{
   377  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   378  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   379  					{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
   380  				},
   381  				done: []task{
   382  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
   383  				},
   384  			},
   385  			// No new dial tasks are launched because all static
   386  			// nodes are now connected.
   387  			{
   388  				peers: []*Peer{
   389  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   390  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   391  					{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
   392  					{rw: &conn{flags: staticDialedConn, id: uintID(4)}},
   393  					{rw: &conn{flags: staticDialedConn, id: uintID(5)}},
   394  				},
   395  				done: []task{
   396  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
   397  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(5)}},
   398  				},
   399  				new: []task{
   400  					&waitExpireTask{Duration: 14 * time.Second},
   401  				},
   402  			},
   403  			// Wait a round for dial history to expire, no new tasks should spawn.
   404  			{
   405  				peers: []*Peer{
   406  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   407  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   408  					{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
   409  					{rw: &conn{flags: staticDialedConn, id: uintID(4)}},
   410  					{rw: &conn{flags: staticDialedConn, id: uintID(5)}},
   411  				},
   412  			},
   413  			// If a static node is dropped, it should be immediately redialed,
   414  			// irrespective whether it was originally static or dynamic.
   415  			{
   416  				peers: []*Peer{
   417  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   418  					{rw: &conn{flags: staticDialedConn, id: uintID(3)}},
   419  					{rw: &conn{flags: staticDialedConn, id: uintID(5)}},
   420  				},
   421  				new: []task{
   422  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
   423  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(4)}},
   424  				},
   425  			},
   426  		},
   427  	})
   428  }
   429  
   430  // This test checks that past dials are not retried for some time.
   431  func TestDialStateCache(t *testing.T) {
   432  	wantStatic := []*discover.Node{
   433  		{ID: uintID(1)},
   434  		{ID: uintID(2)},
   435  		{ID: uintID(3)},
   436  	}
   437  
   438  	runDialTest(t, dialtest{
   439  		init: newDialState(wantStatic, fakeTable{}, 0, nil),
   440  		rounds: []round{
   441  			// Static dials are launched for the nodes that
   442  			// aren't yet connected.
   443  			{
   444  				peers: nil,
   445  				new: []task{
   446  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
   447  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
   448  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
   449  				},
   450  			},
   451  			// No new tasks are launched in this round because all static
   452  			// nodes are either connected or still being dialed.
   453  			{
   454  				peers: []*Peer{
   455  					{rw: &conn{flags: staticDialedConn, id: uintID(1)}},
   456  					{rw: &conn{flags: staticDialedConn, id: uintID(2)}},
   457  				},
   458  				done: []task{
   459  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(1)}},
   460  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(2)}},
   461  				},
   462  			},
   463  			// A salvage task is launched to wait for node 3's history
   464  			// entry to expire.
   465  			{
   466  				peers: []*Peer{
   467  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   468  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   469  				},
   470  				done: []task{
   471  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
   472  				},
   473  				new: []task{
   474  					&waitExpireTask{Duration: 14 * time.Second},
   475  				},
   476  			},
   477  			// Still waiting for node 3's entry to expire in the cache.
   478  			{
   479  				peers: []*Peer{
   480  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   481  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   482  				},
   483  			},
   484  			// The cache entry for node 3 has expired and is retried.
   485  			{
   486  				peers: []*Peer{
   487  					{rw: &conn{flags: dynDialedConn, id: uintID(1)}},
   488  					{rw: &conn{flags: dynDialedConn, id: uintID(2)}},
   489  				},
   490  				new: []task{
   491  					&dialTask{flags: staticDialedConn, dest: &discover.Node{ID: uintID(3)}},
   492  				},
   493  			},
   494  		},
   495  	})
   496  }
   497  
   498  func TestDialResolve(t *testing.T) {
   499  	resolved := discover.NewNode(uintID(1), net.IP{127, 0, 55, 234}, 3333, 4444)
   500  	table := &resolveMock{answer: resolved}
   501  	state := newDialState(nil, table, 0, nil)
   502  
   503  	// Check that the task is generated with an incomplete ID.
   504  	dest := discover.NewNode(uintID(1), nil, 0, 0)
   505  	state.addStatic(dest)
   506  	tasks := state.newTasks(0, nil, time.Time{})
   507  	if !reflect.DeepEqual(tasks, []task{&dialTask{flags: staticDialedConn, dest: dest}}) {
   508  		t.Fatalf("expected dial task, got %#v", tasks)
   509  	}
   510  
   511  	// Now run the task, it should resolve the ID once.
   512  	config := Config{Dialer: &net.Dialer{Deadline: time.Now().Add(-5 * time.Minute)}}
   513  	srv := &Server{ntab: table, Config: config}
   514  	tasks[0].Do(srv)
   515  	if !reflect.DeepEqual(table.resolveCalls, []discover.NodeID{dest.ID}) {
   516  		t.Fatalf("wrong resolve calls, got %v", table.resolveCalls)
   517  	}
   518  
   519  	// Report it as done to the dialer, which should update the static node record.
   520  	state.taskDone(tasks[0], time.Now())
   521  	if state.static[uintID(1)].dest != resolved {
   522  		t.Fatalf("state.dest not updated")
   523  	}
   524  }
   525  
   526  // compares task lists but doesn't care about the order.
   527  func sametasks(a, b []task) bool {
   528  	if len(a) != len(b) {
   529  		return false
   530  	}
   531  next:
   532  	for _, ta := range a {
   533  		for _, tb := range b {
   534  			if reflect.DeepEqual(ta, tb) {
   535  				continue next
   536  			}
   537  		}
   538  		return false
   539  	}
   540  	return true
   541  }
   542  
   543  func uintID(i uint32) discover.NodeID {
   544  	var id discover.NodeID
   545  	binary.BigEndian.PutUint32(id[:], i)
   546  	return id
   547  }
   548  
   549  // implements discoverTable for TestDialResolve
   550  type resolveMock struct {
   551  	resolveCalls []discover.NodeID
   552  	answer       *discover.Node
   553  }
   554  
   555  func (t *resolveMock) Resolve(id discover.NodeID) *discover.Node {
   556  	t.resolveCalls = append(t.resolveCalls, id)
   557  	return t.answer
   558  }
   559  
   560  func (t *resolveMock) Self() *discover.Node                     { return new(discover.Node) }
   561  func (t *resolveMock) Close()                                   {}
   562  func (t *resolveMock) Bootstrap([]*discover.Node)               {}
   563  func (t *resolveMock) Lookup(discover.NodeID) []*discover.Node  { return nil }
   564  func (t *resolveMock) ReadRandomNodes(buf []*discover.Node) int { return 0 }