github.com/cxptek/go-ethereum@v1.9.7/les/clientpool_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 les
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"math"
    23  	"math/rand"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum/common/mclock"
    29  	"github.com/ethereum/go-ethereum/core/rawdb"
    30  	"github.com/ethereum/go-ethereum/p2p/enode"
    31  )
    32  
    33  func TestClientPoolL10C100Free(t *testing.T) {
    34  	testClientPool(t, 10, 100, 0, true)
    35  }
    36  
    37  func TestClientPoolL40C200Free(t *testing.T) {
    38  	testClientPool(t, 40, 200, 0, true)
    39  }
    40  
    41  func TestClientPoolL100C300Free(t *testing.T) {
    42  	testClientPool(t, 100, 300, 0, true)
    43  }
    44  
    45  func TestClientPoolL10C100P4(t *testing.T) {
    46  	testClientPool(t, 10, 100, 4, false)
    47  }
    48  
    49  func TestClientPoolL40C200P30(t *testing.T) {
    50  	testClientPool(t, 40, 200, 30, false)
    51  }
    52  
    53  func TestClientPoolL100C300P20(t *testing.T) {
    54  	testClientPool(t, 100, 300, 20, false)
    55  }
    56  
    57  const testClientPoolTicks = 100000
    58  
    59  type poolTestPeer int
    60  
    61  func (i poolTestPeer) ID() enode.ID {
    62  	return enode.ID{byte(i % 256), byte(i >> 8)}
    63  }
    64  
    65  func (i poolTestPeer) freeClientId() string {
    66  	return fmt.Sprintf("addr #%d", i)
    67  }
    68  
    69  func (i poolTestPeer) updateCapacity(uint64) {}
    70  
    71  type poolTestPeerWithCap struct {
    72  	poolTestPeer
    73  
    74  	cap uint64
    75  }
    76  
    77  func (i *poolTestPeerWithCap) updateCapacity(cap uint64) { i.cap = cap }
    78  
    79  func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomDisconnect bool) {
    80  	rand.Seed(time.Now().UnixNano())
    81  	var (
    82  		clock     mclock.Simulated
    83  		db        = rawdb.NewMemoryDatabase()
    84  		connected = make([]bool, clientCount)
    85  		connTicks = make([]int, clientCount)
    86  		disconnCh = make(chan int, clientCount)
    87  		disconnFn = func(id enode.ID) {
    88  			disconnCh <- int(id[0]) + int(id[1])<<8
    89  		}
    90  		pool = newClientPool(db, 1, &clock, disconnFn)
    91  	)
    92  	pool.disableBias = true
    93  	pool.setLimits(connLimit, uint64(connLimit))
    94  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
    95  
    96  	// pool should accept new peers up to its connected limit
    97  	for i := 0; i < connLimit; i++ {
    98  		if pool.connect(poolTestPeer(i), 0) {
    99  			connected[i] = true
   100  		} else {
   101  			t.Fatalf("Test peer #%d rejected", i)
   102  		}
   103  	}
   104  	// randomly connect and disconnect peers, expect to have a similar total connection time at the end
   105  	for tickCounter := 0; tickCounter < testClientPoolTicks; tickCounter++ {
   106  		clock.Run(1 * time.Second)
   107  
   108  		if tickCounter == testClientPoolTicks/4 {
   109  			// give a positive balance to some of the peers
   110  			amount := uint64(testClientPoolTicks / 2 * 1000000000) // enough for half of the simulation period
   111  			for i := 0; i < paidCount; i++ {
   112  				pool.addBalance(poolTestPeer(i).ID(), amount, false)
   113  			}
   114  		}
   115  
   116  		i := rand.Intn(clientCount)
   117  		if connected[i] {
   118  			if randomDisconnect {
   119  				pool.disconnect(poolTestPeer(i))
   120  				connected[i] = false
   121  				connTicks[i] += tickCounter
   122  			}
   123  		} else {
   124  			if pool.connect(poolTestPeer(i), 0) {
   125  				connected[i] = true
   126  				connTicks[i] -= tickCounter
   127  			}
   128  		}
   129  	pollDisconnects:
   130  		for {
   131  			select {
   132  			case i := <-disconnCh:
   133  				pool.disconnect(poolTestPeer(i))
   134  				if connected[i] {
   135  					connTicks[i] += tickCounter
   136  					connected[i] = false
   137  				}
   138  			default:
   139  				break pollDisconnects
   140  			}
   141  		}
   142  	}
   143  
   144  	expTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2*(connLimit-paidCount)/(clientCount-paidCount)
   145  	expMin := expTicks - expTicks/5
   146  	expMax := expTicks + expTicks/5
   147  	paidTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2
   148  	paidMin := paidTicks - paidTicks/5
   149  	paidMax := paidTicks + paidTicks/5
   150  
   151  	// check if the total connected time of peers are all in the expected range
   152  	for i, c := range connected {
   153  		if c {
   154  			connTicks[i] += testClientPoolTicks
   155  		}
   156  		min, max := expMin, expMax
   157  		if i < paidCount {
   158  			// expect a higher amount for clients with a positive balance
   159  			min, max = paidMin, paidMax
   160  		}
   161  		if connTicks[i] < min || connTicks[i] > max {
   162  			t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max)
   163  		}
   164  	}
   165  	pool.stop()
   166  }
   167  
   168  func TestConnectPaidClient(t *testing.T) {
   169  	var (
   170  		clock mclock.Simulated
   171  		db    = rawdb.NewMemoryDatabase()
   172  	)
   173  	pool := newClientPool(db, 1, &clock, nil)
   174  	defer pool.stop()
   175  	pool.setLimits(10, uint64(10))
   176  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   177  
   178  	// Add balance for an external client and mark it as paid client
   179  	pool.addBalance(poolTestPeer(0).ID(), 1000, false)
   180  
   181  	if !pool.connect(poolTestPeer(0), 10) {
   182  		t.Fatalf("Failed to connect paid client")
   183  	}
   184  }
   185  
   186  func TestConnectPaidClientToSmallPool(t *testing.T) {
   187  	var (
   188  		clock mclock.Simulated
   189  		db    = rawdb.NewMemoryDatabase()
   190  	)
   191  	pool := newClientPool(db, 1, &clock, nil)
   192  	defer pool.stop()
   193  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   194  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   195  
   196  	// Add balance for an external client and mark it as paid client
   197  	pool.addBalance(poolTestPeer(0).ID(), 1000, false)
   198  
   199  	// Connect a fat paid client to pool, should reject it.
   200  	if pool.connect(poolTestPeer(0), 100) {
   201  		t.Fatalf("Connected fat paid client, should reject it")
   202  	}
   203  }
   204  
   205  func TestConnectPaidClientToFullPool(t *testing.T) {
   206  	var (
   207  		clock mclock.Simulated
   208  		db    = rawdb.NewMemoryDatabase()
   209  	)
   210  	removeFn := func(enode.ID) {} // Noop
   211  	pool := newClientPool(db, 1, &clock, removeFn)
   212  	defer pool.stop()
   213  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   214  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   215  
   216  	for i := 0; i < 10; i++ {
   217  		pool.addBalance(poolTestPeer(i).ID(), 1000000000, false)
   218  		pool.connect(poolTestPeer(i), 1)
   219  	}
   220  	pool.addBalance(poolTestPeer(11).ID(), 1000, false) // Add low balance to new paid client
   221  	if pool.connect(poolTestPeer(11), 1) {
   222  		t.Fatalf("Low balance paid client should be rejected")
   223  	}
   224  	clock.Run(time.Second)
   225  	pool.addBalance(poolTestPeer(12).ID(), 1000000000*60*3, false) // Add high balance to new paid client
   226  	if !pool.connect(poolTestPeer(12), 1) {
   227  		t.Fatalf("High balance paid client should be accpected")
   228  	}
   229  }
   230  
   231  func TestPaidClientKickedOut(t *testing.T) {
   232  	var (
   233  		clock    mclock.Simulated
   234  		db       = rawdb.NewMemoryDatabase()
   235  		kickedCh = make(chan int, 1)
   236  	)
   237  	removeFn := func(id enode.ID) { kickedCh <- int(id[0]) }
   238  	pool := newClientPool(db, 1, &clock, removeFn)
   239  	defer pool.stop()
   240  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   241  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   242  
   243  	for i := 0; i < 10; i++ {
   244  		pool.addBalance(poolTestPeer(i).ID(), 1000000000, false) // 1 second allowance
   245  		pool.connect(poolTestPeer(i), 1)
   246  		clock.Run(time.Millisecond)
   247  	}
   248  	clock.Run(time.Second)
   249  	clock.Run(connectedBias)
   250  	if !pool.connect(poolTestPeer(11), 0) {
   251  		t.Fatalf("Free client should be accectped")
   252  	}
   253  	select {
   254  	case id := <-kickedCh:
   255  		if id != 0 {
   256  			t.Fatalf("Kicked client mismatch, want %v, got %v", 0, id)
   257  		}
   258  	case <-time.NewTimer(time.Second).C:
   259  		t.Fatalf("timeout")
   260  	}
   261  }
   262  
   263  func TestConnectFreeClient(t *testing.T) {
   264  	var (
   265  		clock mclock.Simulated
   266  		db    = rawdb.NewMemoryDatabase()
   267  	)
   268  	pool := newClientPool(db, 1, &clock, nil)
   269  	defer pool.stop()
   270  	pool.setLimits(10, uint64(10))
   271  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   272  	if !pool.connect(poolTestPeer(0), 10) {
   273  		t.Fatalf("Failed to connect free client")
   274  	}
   275  }
   276  
   277  func TestConnectFreeClientToFullPool(t *testing.T) {
   278  	var (
   279  		clock mclock.Simulated
   280  		db    = rawdb.NewMemoryDatabase()
   281  	)
   282  	removeFn := func(enode.ID) {} // Noop
   283  	pool := newClientPool(db, 1, &clock, removeFn)
   284  	defer pool.stop()
   285  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   286  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   287  
   288  	for i := 0; i < 10; i++ {
   289  		pool.connect(poolTestPeer(i), 1)
   290  	}
   291  	if pool.connect(poolTestPeer(11), 1) {
   292  		t.Fatalf("New free client should be rejected")
   293  	}
   294  	clock.Run(time.Minute)
   295  	if pool.connect(poolTestPeer(12), 1) {
   296  		t.Fatalf("New free client should be rejected")
   297  	}
   298  	clock.Run(time.Millisecond)
   299  	clock.Run(4 * time.Minute)
   300  	if !pool.connect(poolTestPeer(13), 1) {
   301  		t.Fatalf("Old client connects more than 5min should be kicked")
   302  	}
   303  }
   304  
   305  func TestFreeClientKickedOut(t *testing.T) {
   306  	var (
   307  		clock  mclock.Simulated
   308  		db     = rawdb.NewMemoryDatabase()
   309  		kicked = make(chan int, 10)
   310  	)
   311  	removeFn := func(id enode.ID) { kicked <- int(id[0]) }
   312  	pool := newClientPool(db, 1, &clock, removeFn)
   313  	defer pool.stop()
   314  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   315  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   316  
   317  	for i := 0; i < 10; i++ {
   318  		pool.connect(poolTestPeer(i), 1)
   319  		clock.Run(time.Millisecond)
   320  	}
   321  	if pool.connect(poolTestPeer(10), 1) {
   322  		t.Fatalf("New free client should be rejected")
   323  	}
   324  	clock.Run(5 * time.Minute)
   325  	for i := 0; i < 10; i++ {
   326  		pool.connect(poolTestPeer(i+10), 1)
   327  	}
   328  	for i := 0; i < 10; i++ {
   329  		select {
   330  		case id := <-kicked:
   331  			if id >= 10 {
   332  				t.Fatalf("Old client should be kicked, now got: %d", id)
   333  			}
   334  		case <-time.NewTimer(time.Second).C:
   335  			t.Fatalf("timeout")
   336  		}
   337  	}
   338  }
   339  
   340  func TestPositiveBalanceCalculation(t *testing.T) {
   341  	var (
   342  		clock  mclock.Simulated
   343  		db     = rawdb.NewMemoryDatabase()
   344  		kicked = make(chan int, 10)
   345  	)
   346  	removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
   347  	pool := newClientPool(db, 1, &clock, removeFn)
   348  	defer pool.stop()
   349  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   350  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   351  
   352  	pool.addBalance(poolTestPeer(0).ID(), uint64(time.Minute*3), false)
   353  	pool.connect(poolTestPeer(0), 10)
   354  	clock.Run(time.Minute)
   355  
   356  	pool.disconnect(poolTestPeer(0))
   357  	pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID())
   358  	if pb.value != uint64(time.Minute*2) {
   359  		t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb.value)
   360  	}
   361  }
   362  
   363  func TestDowngradePriorityClient(t *testing.T) {
   364  	var (
   365  		clock  mclock.Simulated
   366  		db     = rawdb.NewMemoryDatabase()
   367  		kicked = make(chan int, 10)
   368  	)
   369  	removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
   370  	pool := newClientPool(db, 1, &clock, removeFn)
   371  	defer pool.stop()
   372  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   373  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   374  
   375  	p := &poolTestPeerWithCap{
   376  		poolTestPeer: poolTestPeer(0),
   377  	}
   378  	pool.addBalance(p.ID(), uint64(time.Minute), false)
   379  	pool.connect(p, 10)
   380  	if p.cap != 10 {
   381  		t.Fatalf("The capcacity of priority peer hasn't been updated, got: %d", p.cap)
   382  	}
   383  
   384  	clock.Run(time.Minute)             // All positive balance should be used up.
   385  	time.Sleep(300 * time.Millisecond) // Ensure the callback is called
   386  	if p.cap != 1 {
   387  		t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap)
   388  	}
   389  	pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID())
   390  	if pb.value != 0 {
   391  		t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb.value)
   392  	}
   393  
   394  	pool.addBalance(poolTestPeer(0).ID(), uint64(time.Minute), false)
   395  	pb = pool.ndb.getOrNewPB(poolTestPeer(0).ID())
   396  	if pb.value != uint64(time.Minute) {
   397  		t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb.value)
   398  	}
   399  }
   400  
   401  func TestNegativeBalanceCalculation(t *testing.T) {
   402  	var (
   403  		clock  mclock.Simulated
   404  		db     = rawdb.NewMemoryDatabase()
   405  		kicked = make(chan int, 10)
   406  	)
   407  	removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
   408  	pool := newClientPool(db, 1, &clock, removeFn)
   409  	defer pool.stop()
   410  	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
   411  	pool.setPriceFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
   412  
   413  	for i := 0; i < 10; i++ {
   414  		pool.connect(poolTestPeer(i), 1)
   415  	}
   416  	clock.Run(time.Second)
   417  
   418  	for i := 0; i < 10; i++ {
   419  		pool.disconnect(poolTestPeer(i))
   420  		nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId())
   421  		if nb.logValue != 0 {
   422  			t.Fatalf("Short connection shouldn't be recorded")
   423  		}
   424  	}
   425  
   426  	for i := 0; i < 10; i++ {
   427  		pool.connect(poolTestPeer(i), 1)
   428  	}
   429  	clock.Run(time.Minute)
   430  	for i := 0; i < 10; i++ {
   431  		pool.disconnect(poolTestPeer(i))
   432  		nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId())
   433  		nb.logValue -= pool.logOffset(clock.Now())
   434  		nb.logValue /= fixedPointMultiplier
   435  		if nb.logValue != int64(math.Log(float64(time.Minute/time.Second))) {
   436  			t.Fatalf("Negative balance mismatch, want %v, got %v", int64(math.Log(float64(time.Minute/time.Second))), nb.logValue)
   437  		}
   438  	}
   439  }
   440  
   441  func TestNodeDB(t *testing.T) {
   442  	ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{})
   443  	defer ndb.close()
   444  
   445  	if !bytes.Equal(ndb.verbuf[:], []byte{0x00, 0x00}) {
   446  		t.Fatalf("version buffer mismatch, want %v, got %v", []byte{0x00, 0x00}, ndb.verbuf)
   447  	}
   448  	var cases = []struct {
   449  		id       enode.ID
   450  		ip       string
   451  		balance  interface{}
   452  		positive bool
   453  	}{
   454  		{enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 100, lastTotal: 200}, true},
   455  		{enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 200, lastTotal: 300}, true},
   456  		{enode.ID{}, "127.0.0.1", negBalance{logValue: 10}, false},
   457  		{enode.ID{}, "127.0.0.1", negBalance{logValue: 20}, false},
   458  	}
   459  	for _, c := range cases {
   460  		if c.positive {
   461  			ndb.setPB(c.id, c.balance.(posBalance))
   462  			if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, c.balance.(posBalance)) {
   463  				t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance.(posBalance), pb)
   464  			}
   465  		} else {
   466  			ndb.setNB(c.ip, c.balance.(negBalance))
   467  			if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, c.balance.(negBalance)) {
   468  				t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance.(negBalance), nb)
   469  			}
   470  		}
   471  	}
   472  	for _, c := range cases {
   473  		if c.positive {
   474  			ndb.delPB(c.id)
   475  			if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, posBalance{}) {
   476  				t.Fatalf("Positive balance mismatch, want %v, got %v", posBalance{}, pb)
   477  			}
   478  		} else {
   479  			ndb.delNB(c.ip)
   480  			if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, negBalance{}) {
   481  				t.Fatalf("Negative balance mismatch, want %v, got %v", negBalance{}, nb)
   482  			}
   483  		}
   484  	}
   485  	ndb.setCumulativeTime(100)
   486  	if ndb.getCumulativeTime() != 100 {
   487  		t.Fatalf("Cumulative time mismatch, want %v, got %v", 100, ndb.getCumulativeTime())
   488  	}
   489  }
   490  
   491  func TestNodeDBExpiration(t *testing.T) {
   492  	var (
   493  		iterated int
   494  		done     = make(chan struct{}, 1)
   495  	)
   496  	callback := func(now mclock.AbsTime, b negBalance) bool {
   497  		iterated += 1
   498  		return true
   499  	}
   500  	clock := &mclock.Simulated{}
   501  	ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock)
   502  	defer ndb.close()
   503  	ndb.nbEvictCallBack = callback
   504  	ndb.cleanupHook = func() { done <- struct{}{} }
   505  
   506  	var cases = []struct {
   507  		ip      string
   508  		balance negBalance
   509  	}{
   510  		{"127.0.0.1", negBalance{logValue: 1}},
   511  		{"127.0.0.2", negBalance{logValue: 1}},
   512  		{"127.0.0.3", negBalance{logValue: 1}},
   513  		{"127.0.0.4", negBalance{logValue: 1}},
   514  	}
   515  	for _, c := range cases {
   516  		ndb.setNB(c.ip, c.balance)
   517  	}
   518  	time.Sleep(100 * time.Millisecond) // Ensure the db expirer is registered.
   519  	clock.Run(time.Hour + time.Minute)
   520  	select {
   521  	case <-done:
   522  	case <-time.NewTimer(time.Second).C:
   523  		t.Fatalf("timeout")
   524  	}
   525  	if iterated != 4 {
   526  		t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated)
   527  	}
   528  
   529  	for _, c := range cases {
   530  		ndb.setNB(c.ip, c.balance)
   531  	}
   532  	clock.Run(time.Hour + time.Minute)
   533  	select {
   534  	case <-done:
   535  	case <-time.NewTimer(time.Second).C:
   536  		t.Fatalf("timeout")
   537  	}
   538  	if iterated != 8 {
   539  		t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated)
   540  	}
   541  }