github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/serverpool_test.go (about)

     1  // Copyright 2020 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  	"math/rand"
    21  	"sync/atomic"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/cryptogateway/go-paymex/common/mclock"
    26  	"github.com/cryptogateway/go-paymex/ethdb"
    27  	"github.com/cryptogateway/go-paymex/ethdb/memorydb"
    28  	lpc "github.com/cryptogateway/go-paymex/les/lespay/client"
    29  	"github.com/cryptogateway/go-paymex/p2p"
    30  	"github.com/cryptogateway/go-paymex/p2p/enode"
    31  	"github.com/cryptogateway/go-paymex/p2p/enr"
    32  )
    33  
    34  const (
    35  	spTestNodes  = 1000
    36  	spTestTarget = 5
    37  	spTestLength = 10000
    38  	spMinTotal   = 40000
    39  	spMaxTotal   = 50000
    40  )
    41  
    42  func testNodeID(i int) enode.ID {
    43  	return enode.ID{42, byte(i % 256), byte(i / 256)}
    44  }
    45  
    46  func testNodeIndex(id enode.ID) int {
    47  	if id[0] != 42 {
    48  		return -1
    49  	}
    50  	return int(id[1]) + int(id[2])*256
    51  }
    52  
    53  type serverPoolTest struct {
    54  	db                   ethdb.KeyValueStore
    55  	clock                *mclock.Simulated
    56  	quit                 chan struct{}
    57  	preNeg, preNegFail   bool
    58  	vt                   *lpc.ValueTracker
    59  	sp                   *serverPool
    60  	input                enode.Iterator
    61  	testNodes            []spTestNode
    62  	trusted              []string
    63  	waitCount, waitEnded int32
    64  
    65  	cycle, conn, servedConn  int
    66  	serviceCycles, dialCount int
    67  	disconnect               map[int][]int
    68  }
    69  
    70  type spTestNode struct {
    71  	connectCycles, waitCycles int
    72  	nextConnCycle, totalConn  int
    73  	connected, service        bool
    74  	peer                      *serverPeer
    75  }
    76  
    77  func newServerPoolTest(preNeg, preNegFail bool) *serverPoolTest {
    78  	nodes := make([]*enode.Node, spTestNodes)
    79  	for i := range nodes {
    80  		nodes[i] = enode.SignNull(&enr.Record{}, testNodeID(i))
    81  	}
    82  	return &serverPoolTest{
    83  		clock:      &mclock.Simulated{},
    84  		db:         memorydb.New(),
    85  		input:      enode.CycleNodes(nodes),
    86  		testNodes:  make([]spTestNode, spTestNodes),
    87  		preNeg:     preNeg,
    88  		preNegFail: preNegFail,
    89  	}
    90  }
    91  
    92  func (s *serverPoolTest) beginWait() {
    93  	// ensure that dialIterator and the maximal number of pre-neg queries are not all stuck in a waiting state
    94  	for atomic.AddInt32(&s.waitCount, 1) > preNegLimit {
    95  		atomic.AddInt32(&s.waitCount, -1)
    96  		s.clock.Run(time.Second)
    97  	}
    98  }
    99  
   100  func (s *serverPoolTest) endWait() {
   101  	atomic.AddInt32(&s.waitCount, -1)
   102  	atomic.AddInt32(&s.waitEnded, 1)
   103  }
   104  
   105  func (s *serverPoolTest) addTrusted(i int) {
   106  	s.trusted = append(s.trusted, enode.SignNull(&enr.Record{}, testNodeID(i)).String())
   107  }
   108  
   109  func (s *serverPoolTest) start() {
   110  	var testQuery queryFunc
   111  	if s.preNeg {
   112  		testQuery = func(node *enode.Node) int {
   113  			idx := testNodeIndex(node.ID())
   114  			n := &s.testNodes[idx]
   115  			canConnect := !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle
   116  			if s.preNegFail {
   117  				// simulate a scenario where UDP queries never work
   118  				s.beginWait()
   119  				s.clock.Sleep(time.Second * 5)
   120  				s.endWait()
   121  				return -1
   122  			}
   123  			switch idx % 3 {
   124  			case 0:
   125  				// pre-neg returns true only if connection is possible
   126  				if canConnect {
   127  					return 1
   128  				}
   129  				return 0
   130  			case 1:
   131  				// pre-neg returns true but connection might still fail
   132  				return 1
   133  			case 2:
   134  				// pre-neg returns true if connection is possible, otherwise timeout (node unresponsive)
   135  				if canConnect {
   136  					return 1
   137  				}
   138  				s.beginWait()
   139  				s.clock.Sleep(time.Second * 5)
   140  				s.endWait()
   141  				return -1
   142  			}
   143  			return -1
   144  		}
   145  	}
   146  
   147  	s.vt = lpc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000))
   148  	s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, 0, testQuery, s.clock, s.trusted)
   149  	s.sp.addSource(s.input)
   150  	s.sp.validSchemes = enode.ValidSchemesForTesting
   151  	s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) }
   152  	s.disconnect = make(map[int][]int)
   153  	s.sp.start()
   154  	s.quit = make(chan struct{})
   155  	go func() {
   156  		last := int32(-1)
   157  		for {
   158  			select {
   159  			case <-time.After(time.Millisecond * 100):
   160  				c := atomic.LoadInt32(&s.waitEnded)
   161  				if c == last {
   162  					// advance clock if test is stuck (might happen in rare cases)
   163  					s.clock.Run(time.Second)
   164  				}
   165  				last = c
   166  			case <-s.quit:
   167  				return
   168  			}
   169  		}
   170  	}()
   171  }
   172  
   173  func (s *serverPoolTest) stop() {
   174  	close(s.quit)
   175  	s.sp.stop()
   176  	s.vt.Stop()
   177  	for i := range s.testNodes {
   178  		n := &s.testNodes[i]
   179  		if n.connected {
   180  			n.totalConn += s.cycle
   181  		}
   182  		n.connected = false
   183  		n.peer = nil
   184  		n.nextConnCycle = 0
   185  	}
   186  	s.conn, s.servedConn = 0, 0
   187  }
   188  
   189  func (s *serverPoolTest) run() {
   190  	for count := spTestLength; count > 0; count-- {
   191  		if dcList := s.disconnect[s.cycle]; dcList != nil {
   192  			for _, idx := range dcList {
   193  				n := &s.testNodes[idx]
   194  				s.sp.unregisterPeer(n.peer)
   195  				n.totalConn += s.cycle
   196  				n.connected = false
   197  				n.peer = nil
   198  				s.conn--
   199  				if n.service {
   200  					s.servedConn--
   201  				}
   202  				n.nextConnCycle = s.cycle + n.waitCycles
   203  			}
   204  			delete(s.disconnect, s.cycle)
   205  		}
   206  		if s.conn < spTestTarget {
   207  			s.dialCount++
   208  			s.beginWait()
   209  			s.sp.dialIterator.Next()
   210  			s.endWait()
   211  			dial := s.sp.dialIterator.Node()
   212  			id := dial.ID()
   213  			idx := testNodeIndex(id)
   214  			n := &s.testNodes[idx]
   215  			if !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle {
   216  				s.conn++
   217  				if n.service {
   218  					s.servedConn++
   219  				}
   220  				n.totalConn -= s.cycle
   221  				n.connected = true
   222  				dc := s.cycle + n.connectCycles
   223  				s.disconnect[dc] = append(s.disconnect[dc], idx)
   224  				n.peer = &serverPeer{peerCommons: peerCommons{Peer: p2p.NewPeer(id, "", nil)}}
   225  				s.sp.registerPeer(n.peer)
   226  				if n.service {
   227  					s.vt.Served(s.vt.GetNode(id), []lpc.ServedRequest{{ReqType: 0, Amount: 100}}, 0)
   228  				}
   229  			}
   230  		}
   231  		s.serviceCycles += s.servedConn
   232  		s.clock.Run(time.Second)
   233  		s.cycle++
   234  	}
   235  }
   236  
   237  func (s *serverPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) {
   238  	for ; count > 0; count-- {
   239  		idx := rand.Intn(spTestNodes)
   240  		for s.testNodes[idx].connectCycles != 0 || s.testNodes[idx].connected {
   241  			idx = rand.Intn(spTestNodes)
   242  		}
   243  		res = append(res, idx)
   244  		s.testNodes[idx] = spTestNode{
   245  			connectCycles: conn,
   246  			waitCycles:    wait,
   247  			service:       service,
   248  		}
   249  		if trusted {
   250  			s.addTrusted(idx)
   251  		}
   252  	}
   253  	return
   254  }
   255  
   256  func (s *serverPoolTest) resetNodes() {
   257  	for i, n := range s.testNodes {
   258  		if n.connected {
   259  			n.totalConn += s.cycle
   260  			s.sp.unregisterPeer(n.peer)
   261  		}
   262  		s.testNodes[i] = spTestNode{totalConn: n.totalConn}
   263  	}
   264  	s.conn, s.servedConn = 0, 0
   265  	s.disconnect = make(map[int][]int)
   266  	s.trusted = nil
   267  }
   268  
   269  func (s *serverPoolTest) checkNodes(t *testing.T, nodes []int) {
   270  	var sum int
   271  	for _, idx := range nodes {
   272  		n := &s.testNodes[idx]
   273  		if n.connected {
   274  			n.totalConn += s.cycle
   275  		}
   276  		sum += n.totalConn
   277  		n.totalConn = 0
   278  		if n.connected {
   279  			n.totalConn -= s.cycle
   280  		}
   281  	}
   282  	if sum < spMinTotal || sum > spMaxTotal {
   283  		t.Errorf("Total connection amount %d outside expected range %d to %d", sum, spMinTotal, spMaxTotal)
   284  	}
   285  }
   286  
   287  func TestServerPool(t *testing.T)               { testServerPool(t, false, false) }
   288  func TestServerPoolWithPreNeg(t *testing.T)     { testServerPool(t, true, false) }
   289  func TestServerPoolWithPreNegFail(t *testing.T) { testServerPool(t, true, true) }
   290  func testServerPool(t *testing.T, preNeg, fail bool) {
   291  	s := newServerPoolTest(preNeg, fail)
   292  	nodes := s.setNodes(100, 200, 200, true, false)
   293  	s.setNodes(100, 20, 20, false, false)
   294  	s.start()
   295  	s.run()
   296  	s.stop()
   297  	s.checkNodes(t, nodes)
   298  }
   299  
   300  func TestServerPoolChangedNodes(t *testing.T)           { testServerPoolChangedNodes(t, false) }
   301  func TestServerPoolChangedNodesWithPreNeg(t *testing.T) { testServerPoolChangedNodes(t, true) }
   302  func testServerPoolChangedNodes(t *testing.T, preNeg bool) {
   303  	s := newServerPoolTest(preNeg, false)
   304  	nodes := s.setNodes(100, 200, 200, true, false)
   305  	s.setNodes(100, 20, 20, false, false)
   306  	s.start()
   307  	s.run()
   308  	s.checkNodes(t, nodes)
   309  	for i := 0; i < 3; i++ {
   310  		s.resetNodes()
   311  		nodes := s.setNodes(100, 200, 200, true, false)
   312  		s.setNodes(100, 20, 20, false, false)
   313  		s.run()
   314  		s.checkNodes(t, nodes)
   315  	}
   316  	s.stop()
   317  }
   318  
   319  func TestServerPoolRestartNoDiscovery(t *testing.T) { testServerPoolRestartNoDiscovery(t, false) }
   320  func TestServerPoolRestartNoDiscoveryWithPreNeg(t *testing.T) {
   321  	testServerPoolRestartNoDiscovery(t, true)
   322  }
   323  func testServerPoolRestartNoDiscovery(t *testing.T, preNeg bool) {
   324  	s := newServerPoolTest(preNeg, false)
   325  	nodes := s.setNodes(100, 200, 200, true, false)
   326  	s.setNodes(100, 20, 20, false, false)
   327  	s.start()
   328  	s.run()
   329  	s.stop()
   330  	s.checkNodes(t, nodes)
   331  	s.input = nil
   332  	s.start()
   333  	s.run()
   334  	s.stop()
   335  	s.checkNodes(t, nodes)
   336  }
   337  
   338  func TestServerPoolTrustedNoDiscovery(t *testing.T) { testServerPoolTrustedNoDiscovery(t, false) }
   339  func TestServerPoolTrustedNoDiscoveryWithPreNeg(t *testing.T) {
   340  	testServerPoolTrustedNoDiscovery(t, true)
   341  }
   342  func testServerPoolTrustedNoDiscovery(t *testing.T, preNeg bool) {
   343  	s := newServerPoolTest(preNeg, false)
   344  	trusted := s.setNodes(200, 200, 200, true, true)
   345  	s.input = nil
   346  	s.start()
   347  	s.run()
   348  	s.stop()
   349  	s.checkNodes(t, trusted)
   350  }