github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/les/costtracker.go (about) 1 // Copyright 2016 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 detailct. 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 "encoding/binary" 21 "math" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common/mclock" 27 "github.com/ethereum/go-ethereum/eth" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/les/flowcontrol" 30 "github.com/ethereum/go-ethereum/log" 31 ) 32 33 const makeCostStats = false // make request cost statistics during operation 34 35 var ( 36 // average request cost estimates based on serving time 37 reqAvgTimeCost = requestCostTable{ 38 GetBlockHeadersMsg: {150000, 30000}, 39 GetBlockBodiesMsg: {0, 700000}, 40 GetReceiptsMsg: {0, 1000000}, 41 GetCodeMsg: {0, 450000}, 42 GetProofsV2Msg: {0, 600000}, 43 GetHelperTrieProofsMsg: {0, 1000000}, 44 SendTxV2Msg: {0, 450000}, 45 GetTxStatusMsg: {0, 250000}, 46 } 47 // maximum incoming message size estimates 48 reqMaxInSize = requestCostTable{ 49 GetBlockHeadersMsg: {40, 0}, 50 GetBlockBodiesMsg: {0, 40}, 51 GetReceiptsMsg: {0, 40}, 52 GetCodeMsg: {0, 80}, 53 GetProofsV2Msg: {0, 80}, 54 GetHelperTrieProofsMsg: {0, 20}, 55 SendTxV2Msg: {0, 66000}, 56 GetTxStatusMsg: {0, 50}, 57 } 58 // maximum outgoing message size estimates 59 reqMaxOutSize = requestCostTable{ 60 GetBlockHeadersMsg: {0, 556}, 61 GetBlockBodiesMsg: {0, 100000}, 62 GetReceiptsMsg: {0, 200000}, 63 GetCodeMsg: {0, 50000}, 64 GetProofsV2Msg: {0, 4000}, 65 GetHelperTrieProofsMsg: {0, 4000}, 66 SendTxV2Msg: {0, 100}, 67 GetTxStatusMsg: {0, 100}, 68 } 69 minBufLimit = uint64(50000000 * maxCostFactor) // minimum buffer limit allowed for a client 70 minCapacity = (minBufLimit-1)/bufLimitRatio + 1 // minimum capacity allowed for a client 71 ) 72 73 const ( 74 maxCostFactor = 2 // ratio of maximum and average cost estimates 75 gfInitWeight = time.Second * 10 76 gfMaxWeight = time.Hour 77 gfUsageThreshold = 0.5 78 gfUsageTC = time.Second 79 gfDbKey = "_globalCostFactor" 80 ) 81 82 // costTracker is responsible for calculating costs and cost estimates on the 83 // server side. It continuously updates the global cost factor which is defined 84 // as the number of cost units per nanosecond of serving time in a single thread. 85 // It is based on statistics collected during serving requests in high-load periods 86 // and practically acts as a one-dimension request price scaling factor over the 87 // pre-defined cost estimate table. Instead of scaling the cost values, the real 88 // value of cost units is changed by applying the factor to the serving times. This 89 // is more convenient because the changes in the cost factor can be applied immediately 90 // without always notifying the clients about the changed cost tables. 91 type costTracker struct { 92 db ethdb.Database 93 stopCh chan chan struct{} 94 95 inSizeFactor, outSizeFactor float64 96 gf, utilTarget float64 97 98 gfUpdateCh chan gfUpdate 99 gfLock sync.RWMutex 100 totalRechargeCh chan uint64 101 102 stats map[uint64][]uint64 103 } 104 105 // newCostTracker creates a cost tracker and loads the cost factor statistics from the database 106 func newCostTracker(db ethdb.Database, config *eth.Config) *costTracker { 107 utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100 108 ct := &costTracker{ 109 db: db, 110 stopCh: make(chan chan struct{}), 111 utilTarget: utilTarget, 112 } 113 if config.LightBandwidthIn > 0 { 114 ct.inSizeFactor = utilTarget / float64(config.LightBandwidthIn) 115 } 116 if config.LightBandwidthOut > 0 { 117 ct.outSizeFactor = utilTarget / float64(config.LightBandwidthOut) 118 } 119 if makeCostStats { 120 ct.stats = make(map[uint64][]uint64) 121 for code := range reqAvgTimeCost { 122 ct.stats[code] = make([]uint64, 10) 123 } 124 } 125 ct.gfLoop() 126 return ct 127 } 128 129 // stop stops the cost tracker and saves the cost factor statistics to the database 130 func (ct *costTracker) stop() { 131 stopCh := make(chan struct{}) 132 ct.stopCh <- stopCh 133 <-stopCh 134 if makeCostStats { 135 ct.printStats() 136 } 137 } 138 139 // makeCostList returns upper cost estimates based on the hardcoded cost estimate 140 // tables and the optionally specified incoming/outgoing bandwidth limits 141 func (ct *costTracker) makeCostList() RequestCostList { 142 maxCost := func(avgTime, inSize, outSize uint64) uint64 { 143 globalFactor := ct.globalFactor() 144 145 cost := avgTime * maxCostFactor 146 inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor * maxCostFactor) 147 if inSizeCost > cost { 148 cost = inSizeCost 149 } 150 outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor * maxCostFactor) 151 if outSizeCost > cost { 152 cost = outSizeCost 153 } 154 return cost 155 } 156 var list RequestCostList 157 for code, data := range reqAvgTimeCost { 158 list = append(list, requestCostListItem{ 159 MsgCode: code, 160 BaseCost: maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost), 161 ReqCost: maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost), 162 }) 163 } 164 return list 165 } 166 167 type gfUpdate struct { 168 avgTime, servingTime float64 169 } 170 171 // gfLoop starts an event loop which updates the global cost factor which is 172 // calculated as a weighted average of the average estimate / serving time ratio. 173 // The applied weight equals the serving time if gfUsage is over a threshold, 174 // zero otherwise. gfUsage is the recent average serving time per time unit in 175 // an exponential moving window. This ensures that statistics are collected only 176 // under high-load circumstances where the measured serving times are relevant. 177 // The total recharge parameter of the flow control system which controls the 178 // total allowed serving time per second but nominated in cost units, should 179 // also be scaled with the cost factor and is also updated by this loop. 180 func (ct *costTracker) gfLoop() { 181 var gfUsage, gfSum, gfWeight float64 182 lastUpdate := mclock.Now() 183 expUpdate := lastUpdate 184 185 data, _ := ct.db.Get([]byte(gfDbKey)) 186 if len(data) == 16 { 187 gfSum = math.Float64frombits(binary.BigEndian.Uint64(data[0:8])) 188 gfWeight = math.Float64frombits(binary.BigEndian.Uint64(data[8:16])) 189 } 190 if gfWeight < float64(gfInitWeight) { 191 gfSum = float64(gfInitWeight) 192 gfWeight = float64(gfInitWeight) 193 } 194 gf := gfSum / gfWeight 195 ct.gf = gf 196 ct.gfUpdateCh = make(chan gfUpdate, 100) 197 198 go func() { 199 for { 200 select { 201 case r := <-ct.gfUpdateCh: 202 now := mclock.Now() 203 max := r.servingTime * gf 204 if r.avgTime > max { 205 max = r.avgTime 206 } 207 dt := float64(now - expUpdate) 208 expUpdate = now 209 gfUsage = gfUsage*math.Exp(-dt/float64(gfUsageTC)) + max*1000000/float64(gfUsageTC) 210 211 if gfUsage >= gfUsageThreshold*ct.utilTarget*gf { 212 gfSum += r.avgTime 213 gfWeight += r.servingTime 214 if time.Duration(now-lastUpdate) > time.Second { 215 gf = gfSum / gfWeight 216 if gfWeight >= float64(gfMaxWeight) { 217 gfSum = gf * float64(gfMaxWeight) 218 gfWeight = float64(gfMaxWeight) 219 } 220 lastUpdate = now 221 ct.gfLock.Lock() 222 ct.gf = gf 223 ch := ct.totalRechargeCh 224 ct.gfLock.Unlock() 225 if ch != nil { 226 select { 227 case ct.totalRechargeCh <- uint64(ct.utilTarget * gf): 228 default: 229 } 230 } 231 log.Debug("global cost factor updated", "gf", gf, "weight", time.Duration(gfWeight)) 232 } 233 } 234 case stopCh := <-ct.stopCh: 235 var data [16]byte 236 binary.BigEndian.PutUint64(data[0:8], math.Float64bits(gfSum)) 237 binary.BigEndian.PutUint64(data[8:16], math.Float64bits(gfWeight)) 238 ct.db.Put([]byte(gfDbKey), data[:]) 239 log.Debug("global cost factor saved", "sum", time.Duration(gfSum), "weight", time.Duration(gfWeight)) 240 close(stopCh) 241 return 242 } 243 } 244 }() 245 } 246 247 // globalFactor returns the current value of the global cost factor 248 func (ct *costTracker) globalFactor() float64 { 249 ct.gfLock.RLock() 250 defer ct.gfLock.RUnlock() 251 252 return ct.gf 253 } 254 255 // totalRecharge returns the current total recharge parameter which is used by 256 // flowcontrol.ClientManager and is scaled by the global cost factor 257 func (ct *costTracker) totalRecharge() uint64 { 258 ct.gfLock.RLock() 259 defer ct.gfLock.RUnlock() 260 261 return uint64(ct.gf * ct.utilTarget) 262 } 263 264 // subscribeTotalRecharge returns all future updates to the total recharge value 265 // through a channel and also returns the current value 266 func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 { 267 ct.gfLock.Lock() 268 defer ct.gfLock.Unlock() 269 270 ct.totalRechargeCh = ch 271 return uint64(ct.gf * ct.utilTarget) 272 } 273 274 // updateStats updates the global cost factor and (if enabled) the real cost vs. 275 // average estimate statistics 276 func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) { 277 avg := reqAvgTimeCost[code] 278 avgTime := avg.baseCost + amount*avg.reqCost 279 select { 280 case ct.gfUpdateCh <- gfUpdate{float64(avgTime), float64(servingTime)}: 281 default: 282 } 283 if makeCostStats { 284 realCost <<= 4 285 l := 0 286 for l < 9 && realCost > avgTime { 287 l++ 288 realCost >>= 1 289 } 290 atomic.AddUint64(&ct.stats[code][l], 1) 291 } 292 } 293 294 // realCost calculates the final cost of a request based on actual serving time, 295 // incoming and outgoing message size 296 // 297 // Note: message size is only taken into account if bandwidth limitation is applied 298 // and the cost based on either message size is greater than the cost based on 299 // serving time. A maximum of the three costs is applied instead of their sum 300 // because the three limited resources (serving thread time and i/o bandwidth) can 301 // also be maxed out simultaneously. 302 func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 { 303 cost := float64(servingTime) 304 inSizeCost := float64(inSize) * ct.inSizeFactor 305 if inSizeCost > cost { 306 cost = inSizeCost 307 } 308 outSizeCost := float64(outSize) * ct.outSizeFactor 309 if outSizeCost > cost { 310 cost = outSizeCost 311 } 312 return uint64(cost * ct.globalFactor()) 313 } 314 315 // printStats prints the distribution of real request cost relative to the average estimates 316 func (ct *costTracker) printStats() { 317 if ct.stats == nil { 318 return 319 } 320 for code, arr := range ct.stats { 321 log.Info("Request cost statistics", "code", code, "1/16", arr[0], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9]) 322 } 323 } 324 325 type ( 326 // requestCostTable assigns a cost estimate function to each request type 327 // which is a linear function of the requested amount 328 // (cost = baseCost + reqCost * amount) 329 requestCostTable map[uint64]*requestCosts 330 requestCosts struct { 331 baseCost, reqCost uint64 332 } 333 334 // RequestCostList is a list representation of request costs which is used for 335 // database storage and communication through the network 336 RequestCostList []requestCostListItem 337 requestCostListItem struct { 338 MsgCode, BaseCost, ReqCost uint64 339 } 340 ) 341 342 // getCost calculates the estimated cost for a given request type and amount 343 func (table requestCostTable) getCost(code, amount uint64) uint64 { 344 costs := table[code] 345 return costs.baseCost + amount*costs.reqCost 346 } 347 348 // decode converts a cost list to a cost table 349 func (list RequestCostList) decode() requestCostTable { 350 table := make(requestCostTable) 351 for _, e := range list { 352 table[e.MsgCode] = &requestCosts{ 353 baseCost: e.BaseCost, 354 reqCost: e.ReqCost, 355 } 356 } 357 return table 358 } 359 360 // testCostList returns a dummy request cost list used by tests 361 func testCostList() RequestCostList { 362 cl := make(RequestCostList, len(reqAvgTimeCost)) 363 var max uint64 364 for code := range reqAvgTimeCost { 365 if code > max { 366 max = code 367 } 368 } 369 i := 0 370 for code := uint64(0); code <= max; code++ { 371 if _, ok := reqAvgTimeCost[code]; ok { 372 cl[i].MsgCode = code 373 cl[i].BaseCost = 0 374 cl[i].ReqCost = 0 375 i++ 376 } 377 } 378 return cl 379 }