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 }