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