github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/tests/fuzzers/vflux/clientpool-fuzzer.go (about)

     1  // Copyright 2021 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 vflux
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/binary"
    22  	"io"
    23  	"math"
    24  	"math/big"
    25  	"time"
    26  
    27  	"github.com/ethereum/go-ethereum/common/mclock"
    28  	"github.com/ethereum/go-ethereum/ethdb/memorydb"
    29  	"github.com/ethereum/go-ethereum/les/vflux"
    30  	vfs "github.com/ethereum/go-ethereum/les/vflux/server"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/p2p/enode"
    33  	"github.com/ethereum/go-ethereum/p2p/enr"
    34  	"github.com/ethereum/go-ethereum/rlp"
    35  )
    36  
    37  var (
    38  	debugMode = false
    39  	doLog     = func(msg string, ctx ...interface{}) {
    40  		if !debugMode {
    41  			return
    42  		}
    43  		log.Info(msg, ctx...)
    44  	}
    45  )
    46  
    47  type fuzzer struct {
    48  	peers                  [256]*clientPeer
    49  	disconnectList         []*clientPeer
    50  	input                  io.Reader
    51  	exhausted              bool
    52  	activeCount, activeCap uint64
    53  	maxCount, maxCap       uint64
    54  }
    55  
    56  type clientPeer struct {
    57  	fuzzer  *fuzzer
    58  	node    *enode.Node
    59  	freeID  string
    60  	timeout time.Duration
    61  
    62  	balance  vfs.ConnectedBalance
    63  	capacity uint64
    64  }
    65  
    66  func (p *clientPeer) Node() *enode.Node {
    67  	return p.node
    68  }
    69  
    70  func (p *clientPeer) FreeClientId() string {
    71  	return p.freeID
    72  }
    73  
    74  func (p *clientPeer) InactiveAllowance() time.Duration {
    75  	return p.timeout
    76  }
    77  
    78  func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) {
    79  	origin, originTotal := p.capacity, p.fuzzer.activeCap
    80  	p.fuzzer.activeCap -= p.capacity
    81  	if p.capacity != 0 {
    82  		p.fuzzer.activeCount--
    83  	}
    84  	p.capacity = newCap
    85  	p.fuzzer.activeCap += p.capacity
    86  	if p.capacity != 0 {
    87  		p.fuzzer.activeCount++
    88  	}
    89  	doLog("Update capacity", "peer", p.node.ID(), "origin", origin, "cap", newCap, "origintotal", originTotal, "total", p.fuzzer.activeCap, "requested", requested)
    90  }
    91  
    92  func (p *clientPeer) Disconnect() {
    93  	origin, originTotal := p.capacity, p.fuzzer.activeCap
    94  	p.fuzzer.disconnectList = append(p.fuzzer.disconnectList, p)
    95  	p.fuzzer.activeCap -= p.capacity
    96  	if p.capacity != 0 {
    97  		p.fuzzer.activeCount--
    98  	}
    99  	p.capacity = 0
   100  	p.balance = nil
   101  	doLog("Disconnect", "peer", p.node.ID(), "origin", origin, "origintotal", originTotal, "total", p.fuzzer.activeCap)
   102  }
   103  
   104  func newFuzzer(input []byte) *fuzzer {
   105  	f := &fuzzer{
   106  		input: bytes.NewReader(input),
   107  	}
   108  	for i := range f.peers {
   109  		f.peers[i] = &clientPeer{
   110  			fuzzer:  f,
   111  			node:    enode.SignNull(new(enr.Record), enode.ID{byte(i)}),
   112  			freeID:  string([]byte{byte(i)}),
   113  			timeout: f.randomDelay(),
   114  		}
   115  	}
   116  	return f
   117  }
   118  
   119  func (f *fuzzer) read(size int) []byte {
   120  	out := make([]byte, size)
   121  	if _, err := f.input.Read(out); err != nil {
   122  		f.exhausted = true
   123  	}
   124  	return out
   125  }
   126  
   127  func (f *fuzzer) randomByte() byte {
   128  	d := f.read(1)
   129  	return d[0]
   130  }
   131  
   132  func (f *fuzzer) randomBool() bool {
   133  	d := f.read(1)
   134  	return d[0]&1 == 1
   135  }
   136  
   137  func (f *fuzzer) randomInt(max int) int {
   138  	if max == 0 {
   139  		return 0
   140  	}
   141  	if max <= 256 {
   142  		return int(f.randomByte()) % max
   143  	}
   144  	var a uint16
   145  	if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
   146  		f.exhausted = true
   147  	}
   148  	return int(a % uint16(max))
   149  }
   150  
   151  func (f *fuzzer) randomTokenAmount(signed bool) int64 {
   152  	x := uint64(f.randomInt(65000))
   153  	x = x * x * x * x
   154  
   155  	if signed && (x&1) == 1 {
   156  		if x <= math.MaxInt64 {
   157  			return -int64(x)
   158  		}
   159  		return math.MinInt64
   160  	}
   161  	if x <= math.MaxInt64 {
   162  		return int64(x)
   163  	}
   164  	return math.MaxInt64
   165  }
   166  
   167  func (f *fuzzer) randomDelay() time.Duration {
   168  	delay := f.randomByte()
   169  	if delay < 128 {
   170  		return time.Duration(delay) * time.Second
   171  	}
   172  	return 0
   173  }
   174  
   175  func (f *fuzzer) randomFactors() vfs.PriceFactors {
   176  	return vfs.PriceFactors{
   177  		TimeFactor:     float64(f.randomByte()) / 25500,
   178  		CapacityFactor: float64(f.randomByte()) / 255,
   179  		RequestFactor:  float64(f.randomByte()) / 255,
   180  	}
   181  }
   182  
   183  func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance, id enode.ID) {
   184  	switch f.randomInt(3) {
   185  	case 0:
   186  		cost := uint64(f.randomTokenAmount(false))
   187  		balance.RequestServed(cost)
   188  		doLog("Serve request cost", "id", id, "amount", cost)
   189  	case 1:
   190  		posFactor, negFactor := f.randomFactors(), f.randomFactors()
   191  		balance.SetPriceFactors(posFactor, negFactor)
   192  		doLog("Set price factor", "pos", posFactor, "neg", negFactor)
   193  	case 2:
   194  		balance.GetBalance()
   195  		balance.GetRawBalance()
   196  		balance.GetPriceFactors()
   197  	}
   198  }
   199  
   200  func (f *fuzzer) atomicBalanceOp(balance vfs.AtomicBalanceOperator, id enode.ID) {
   201  	switch f.randomInt(3) {
   202  	case 0:
   203  		amount := f.randomTokenAmount(true)
   204  		balance.AddBalance(amount)
   205  		doLog("Add balance", "id", id, "amount", amount)
   206  	case 1:
   207  		pos, neg := uint64(f.randomTokenAmount(false)), uint64(f.randomTokenAmount(false))
   208  		balance.SetBalance(pos, neg)
   209  		doLog("Set balance", "id", id, "pos", pos, "neg", neg)
   210  	case 2:
   211  		balance.GetBalance()
   212  		balance.GetRawBalance()
   213  		balance.GetPriceFactors()
   214  	}
   215  }
   216  
   217  func FuzzClientPool(input []byte) int {
   218  	if len(input) > 10000 {
   219  		return -1
   220  	}
   221  	f := newFuzzer(input)
   222  	if f.exhausted {
   223  		return 0
   224  	}
   225  	clock := &mclock.Simulated{}
   226  	db := memorydb.New()
   227  	pool := vfs.NewClientPool(db, 10, f.randomDelay(), clock, func() bool { return true })
   228  	pool.Start()
   229  	defer pool.Stop()
   230  
   231  	count := 0
   232  	for !f.exhausted && count < 1000 {
   233  		count++
   234  		switch f.randomInt(11) {
   235  		case 0:
   236  			i := int(f.randomByte())
   237  			f.peers[i].balance = pool.Register(f.peers[i])
   238  			doLog("Register peer", "id", f.peers[i].node.ID())
   239  		case 1:
   240  			i := int(f.randomByte())
   241  			f.peers[i].Disconnect()
   242  			doLog("Disconnect peer", "id", f.peers[i].node.ID())
   243  		case 2:
   244  			f.maxCount = uint64(f.randomByte())
   245  			f.maxCap = uint64(f.randomByte())
   246  			f.maxCap *= f.maxCap
   247  
   248  			count, cap := pool.Limits()
   249  			pool.SetLimits(f.maxCount, f.maxCap)
   250  			doLog("Set limits", "maxcount", f.maxCount, "maxcap", f.maxCap, "origincount", count, "oricap", cap)
   251  		case 3:
   252  			bias := f.randomDelay()
   253  			pool.SetConnectedBias(f.randomDelay())
   254  			doLog("Set connection bias", "bias", bias)
   255  		case 4:
   256  			pos, neg := f.randomFactors(), f.randomFactors()
   257  			pool.SetDefaultFactors(pos, neg)
   258  			doLog("Set default factors", "pos", pos, "neg", neg)
   259  		case 5:
   260  			pos, neg := uint64(f.randomInt(50000)), uint64(f.randomInt(50000))
   261  			pool.SetExpirationTCs(pos, neg)
   262  			doLog("Set expiration constants", "pos", pos, "neg", neg)
   263  		case 6:
   264  			var (
   265  				index     = f.randomByte()
   266  				reqCap    = uint64(f.randomByte())
   267  				bias      = f.randomDelay()
   268  				requested = f.randomBool()
   269  			)
   270  			pool.SetCapacity(f.peers[index].node, reqCap, bias, requested)
   271  			doLog("Set capacity", "id", f.peers[index].node.ID(), "reqcap", reqCap, "bias", bias, "requested", requested)
   272  		case 7:
   273  			index := f.randomByte()
   274  			if balance := f.peers[index].balance; balance != nil {
   275  				f.connectedBalanceOp(balance, f.peers[index].node.ID())
   276  			}
   277  		case 8:
   278  			index := f.randomByte()
   279  			pool.BalanceOperation(f.peers[index].node.ID(), f.peers[index].freeID, func(balance vfs.AtomicBalanceOperator) {
   280  				count := f.randomInt(4)
   281  				for i := 0; i < count; i++ {
   282  					f.atomicBalanceOp(balance, f.peers[index].node.ID())
   283  				}
   284  			})
   285  		case 9:
   286  			pool.TotalTokenAmount()
   287  			pool.GetExpirationTCs()
   288  			pool.Active()
   289  			pool.Limits()
   290  			pool.GetPosBalanceIDs(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].node.ID(), f.randomInt(100))
   291  		case 10:
   292  			req := vflux.CapacityQueryReq{
   293  				Bias:      uint64(f.randomByte()),
   294  				AddTokens: make([]vflux.IntOrInf, f.randomInt(vflux.CapacityQueryMaxLen+1)),
   295  			}
   296  			for i := range req.AddTokens {
   297  				v := vflux.IntOrInf{Type: uint8(f.randomInt(4))}
   298  				if v.Type < 2 {
   299  					v.Value = *big.NewInt(f.randomTokenAmount(false))
   300  				}
   301  				req.AddTokens[i] = v
   302  			}
   303  			reqEnc, err := rlp.EncodeToBytes(&req)
   304  			if err != nil {
   305  				panic(err)
   306  			}
   307  			p := int(f.randomByte())
   308  			if p < len(reqEnc) {
   309  				reqEnc[p] = f.randomByte()
   310  			}
   311  			pool.Handle(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, vflux.CapacityQueryName, reqEnc)
   312  		}
   313  
   314  		for _, peer := range f.disconnectList {
   315  			pool.Unregister(peer)
   316  			doLog("Unregister peer", "id", peer.node.ID())
   317  		}
   318  		f.disconnectList = nil
   319  		if d := f.randomDelay(); d > 0 {
   320  			clock.Run(d)
   321  		}
   322  		doLog("Clientpool stats in fuzzer", "count", f.activeCap, "maxcount", f.maxCount, "cap", f.activeCap, "maxcap", f.maxCap)
   323  		activeCount, activeCap := pool.Active()
   324  		doLog("Clientpool stats in pool", "count", activeCount, "cap", activeCap)
   325  		if activeCount != f.activeCount || activeCap != f.activeCap {
   326  			panic(nil)
   327  		}
   328  		if f.activeCount > f.maxCount || f.activeCap > f.maxCap {
   329  			panic(nil)
   330  		}
   331  	}
   332  	return 0
   333  }