clientpool-fuzzer.go 8.9 KB

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