github.com/c2s/go-ethereum@v1.9.7/les/balance.go (about) 1 // Copyright 2019 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 "sync" 21 "time" 22 23 "github.com/ethereum/go-ethereum/common/mclock" 24 ) 25 26 const ( 27 balanceCallbackQueue = iota 28 balanceCallbackZero 29 balanceCallbackCount 30 ) 31 32 // balanceTracker keeps track of the positive and negative balances of a connected 33 // client and calculates actual and projected future priority values required by 34 // prque.LazyQueue. 35 type balanceTracker struct { 36 lock sync.Mutex 37 clock mclock.Clock 38 stopped bool 39 capacity uint64 40 balance balance 41 timeFactor, requestFactor float64 42 negTimeFactor, negRequestFactor float64 43 sumReqCost uint64 44 lastUpdate, nextUpdate, initTime mclock.AbsTime 45 updateEvent mclock.Timer 46 // since only a limited and fixed number of callbacks are needed, they are 47 // stored in a fixed size array ordered by priority threshold. 48 callbacks [balanceCallbackCount]balanceCallback 49 // callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active) 50 callbackIndex [balanceCallbackCount]int 51 callbackCount int // number of active callbacks 52 } 53 54 // balance represents a pair of positive and negative balances 55 type balance struct { 56 pos, neg uint64 57 } 58 59 // balanceCallback represents a single callback that is activated when client priority 60 // reaches the given threshold 61 type balanceCallback struct { 62 id int 63 threshold int64 64 callback func() 65 } 66 67 // init initializes balanceTracker 68 func (bt *balanceTracker) init(clock mclock.Clock, capacity uint64) { 69 bt.clock = clock 70 bt.initTime, bt.lastUpdate = clock.Now(), clock.Now() // Init timestamps 71 for i := range bt.callbackIndex { 72 bt.callbackIndex[i] = -1 73 } 74 bt.capacity = capacity 75 } 76 77 // stop shuts down the balance tracker 78 func (bt *balanceTracker) stop(now mclock.AbsTime) { 79 bt.lock.Lock() 80 defer bt.lock.Unlock() 81 82 bt.stopped = true 83 bt.updateBalance(now) 84 bt.negTimeFactor = 0 85 bt.negRequestFactor = 0 86 bt.timeFactor = 0 87 bt.requestFactor = 0 88 if bt.updateEvent != nil { 89 bt.updateEvent.Stop() 90 bt.updateEvent = nil 91 } 92 } 93 94 // balanceToPriority converts a balance to a priority value. Higher priority means 95 // first to disconnect. Positive balance translates to negative priority. If positive 96 // balance is zero then negative balance translates to a positive priority. 97 func (bt *balanceTracker) balanceToPriority(b balance) int64 { 98 if b.pos > 0 { 99 return ^int64(b.pos / bt.capacity) 100 } 101 return int64(b.neg) 102 } 103 104 // reducedBalance estimates the reduced balance at a given time in the fututre based 105 // on the current balance, the time factor and an estimated average request cost per time ratio 106 func (bt *balanceTracker) reducedBalance(at mclock.AbsTime, avgReqCost float64) balance { 107 dt := float64(at - bt.lastUpdate) 108 b := bt.balance 109 if b.pos != 0 { 110 factor := bt.timeFactor + bt.requestFactor*avgReqCost 111 diff := uint64(dt * factor) 112 if diff <= b.pos { 113 b.pos -= diff 114 dt = 0 115 } else { 116 dt -= float64(b.pos) / factor 117 b.pos = 0 118 } 119 } 120 if dt != 0 { 121 factor := bt.negTimeFactor + bt.negRequestFactor*avgReqCost 122 b.neg += uint64(dt * factor) 123 } 124 return b 125 } 126 127 // timeUntil calculates the remaining time needed to reach a given priority level 128 // assuming that no requests are processed until then. If the given level is never 129 // reached then (0, false) is returned. 130 // Note: the function assumes that the balance has been recently updated and 131 // calculates the time starting from the last update. 132 func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) { 133 var dt float64 134 if bt.balance.pos != 0 { 135 if bt.timeFactor < 1e-100 { 136 return 0, false 137 } 138 if priority < 0 { 139 newBalance := uint64(^priority) * bt.capacity 140 if newBalance > bt.balance.pos { 141 return 0, false 142 } 143 dt = float64(bt.balance.pos-newBalance) / bt.timeFactor 144 return time.Duration(dt), true 145 } else { 146 dt = float64(bt.balance.pos) / bt.timeFactor 147 } 148 } else { 149 if priority < 0 { 150 return 0, false 151 } 152 } 153 // if we have a positive balance then dt equals the time needed to get it to zero 154 if uint64(priority) > bt.balance.neg { 155 if bt.negTimeFactor < 1e-100 { 156 return 0, false 157 } 158 dt += float64(uint64(priority)-bt.balance.neg) / bt.negTimeFactor 159 } 160 return time.Duration(dt), true 161 } 162 163 // getPriority returns the actual priority based on the current balance 164 func (bt *balanceTracker) getPriority(now mclock.AbsTime) int64 { 165 bt.lock.Lock() 166 defer bt.lock.Unlock() 167 168 bt.updateBalance(now) 169 return bt.balanceToPriority(bt.balance) 170 } 171 172 // estimatedPriority gives an upper estimate for the priority at a given time in the future. 173 // If addReqCost is true then an average request cost per time is assumed that is twice the 174 // average cost per time in the current session. If false, zero request cost is assumed. 175 func (bt *balanceTracker) estimatedPriority(at mclock.AbsTime, addReqCost bool) int64 { 176 bt.lock.Lock() 177 defer bt.lock.Unlock() 178 179 var avgReqCost float64 180 if addReqCost { 181 dt := time.Duration(bt.lastUpdate - bt.initTime) 182 if dt > time.Second { 183 avgReqCost = float64(bt.sumReqCost) * 2 / float64(dt) 184 } 185 } 186 return bt.balanceToPriority(bt.reducedBalance(at, avgReqCost)) 187 } 188 189 // updateBalance updates balance based on the time factor 190 func (bt *balanceTracker) updateBalance(now mclock.AbsTime) { 191 if now > bt.lastUpdate { 192 bt.balance = bt.reducedBalance(now, 0) 193 bt.lastUpdate = now 194 } 195 } 196 197 // checkCallbacks checks whether the threshold of any of the active callbacks 198 // have been reached and calls them if necessary. It also sets up or updates 199 // a scheduled event to ensure that is will be called again just after the next 200 // threshold has been reached. 201 // Note: checkCallbacks assumes that the balance has been recently updated. 202 func (bt *balanceTracker) checkCallbacks(now mclock.AbsTime) { 203 if bt.callbackCount == 0 { 204 return 205 } 206 pri := bt.balanceToPriority(bt.balance) 207 for bt.callbackCount != 0 && bt.callbacks[bt.callbackCount-1].threshold <= pri { 208 bt.callbackCount-- 209 bt.callbackIndex[bt.callbacks[bt.callbackCount].id] = -1 210 go bt.callbacks[bt.callbackCount].callback() 211 } 212 if bt.callbackCount != 0 { 213 d, ok := bt.timeUntil(bt.callbacks[bt.callbackCount-1].threshold) 214 if !ok { 215 bt.nextUpdate = 0 216 bt.updateAfter(0) 217 return 218 } 219 if bt.nextUpdate == 0 || bt.nextUpdate > now+mclock.AbsTime(d) { 220 if d > time.Second { 221 // Note: if the scheduled update is not in the very near future then we 222 // schedule the update a bit earlier. This way we do need to update a few 223 // extra times but don't need to reschedule every time a processed request 224 // brings the expected firing time a little bit closer. 225 d = ((d - time.Second) * 7 / 8) + time.Second 226 } 227 bt.nextUpdate = now + mclock.AbsTime(d) 228 bt.updateAfter(d) 229 } 230 } else { 231 bt.nextUpdate = 0 232 bt.updateAfter(0) 233 } 234 } 235 236 // updateAfter schedules a balance update and callback check in the future 237 func (bt *balanceTracker) updateAfter(dt time.Duration) { 238 if bt.updateEvent == nil || bt.updateEvent.Stop() { 239 if dt == 0 { 240 bt.updateEvent = nil 241 } else { 242 bt.updateEvent = bt.clock.AfterFunc(dt, func() { 243 bt.lock.Lock() 244 defer bt.lock.Unlock() 245 246 if bt.callbackCount != 0 { 247 now := bt.clock.Now() 248 bt.updateBalance(now) 249 bt.checkCallbacks(now) 250 } 251 }) 252 } 253 } 254 } 255 256 // requestCost should be called after serving a request for the given peer 257 func (bt *balanceTracker) requestCost(cost uint64) { 258 bt.lock.Lock() 259 defer bt.lock.Unlock() 260 261 if bt.stopped { 262 return 263 } 264 now := bt.clock.Now() 265 bt.updateBalance(now) 266 fcost := float64(cost) 267 268 if bt.balance.pos != 0 { 269 if bt.requestFactor != 0 { 270 c := uint64(fcost * bt.requestFactor) 271 if bt.balance.pos >= c { 272 bt.balance.pos -= c 273 fcost = 0 274 } else { 275 fcost *= 1 - float64(bt.balance.pos)/float64(c) 276 bt.balance.pos = 0 277 } 278 bt.checkCallbacks(now) 279 } else { 280 fcost = 0 281 } 282 } 283 if fcost > 0 { 284 if bt.negRequestFactor != 0 { 285 bt.balance.neg += uint64(fcost * bt.negRequestFactor) 286 bt.checkCallbacks(now) 287 } 288 } 289 bt.sumReqCost += cost 290 } 291 292 // getBalance returns the current positive and negative balance 293 func (bt *balanceTracker) getBalance(now mclock.AbsTime) (uint64, uint64) { 294 bt.lock.Lock() 295 defer bt.lock.Unlock() 296 297 bt.updateBalance(now) 298 return bt.balance.pos, bt.balance.neg 299 } 300 301 // setBalance sets the positive and negative balance to the given values 302 func (bt *balanceTracker) setBalance(pos, neg uint64) error { 303 bt.lock.Lock() 304 defer bt.lock.Unlock() 305 306 now := bt.clock.Now() 307 bt.updateBalance(now) 308 bt.balance.pos = pos 309 bt.balance.neg = neg 310 bt.checkCallbacks(now) 311 return nil 312 } 313 314 // setFactors sets the price factors. timeFactor is the price of a nanosecond of 315 // connection while requestFactor is the price of a "realCost" unit. 316 func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64) { 317 bt.lock.Lock() 318 defer bt.lock.Unlock() 319 320 if bt.stopped { 321 return 322 } 323 now := bt.clock.Now() 324 bt.updateBalance(now) 325 if neg { 326 bt.negTimeFactor = timeFactor 327 bt.negRequestFactor = requestFactor 328 } else { 329 bt.timeFactor = timeFactor 330 bt.requestFactor = requestFactor 331 } 332 bt.checkCallbacks(now) 333 } 334 335 // setCallback sets up a one-time callback to be called when priority reaches 336 // the threshold. If it has already reached the threshold the callback is called 337 // immediately. 338 func (bt *balanceTracker) addCallback(id int, threshold int64, callback func()) { 339 bt.lock.Lock() 340 defer bt.lock.Unlock() 341 342 bt.removeCb(id) 343 idx := 0 344 for idx < bt.callbackCount && threshold < bt.callbacks[idx].threshold { 345 idx++ 346 } 347 for i := bt.callbackCount - 1; i >= idx; i-- { 348 bt.callbackIndex[bt.callbacks[i].id]++ 349 bt.callbacks[i+1] = bt.callbacks[i] 350 } 351 bt.callbackCount++ 352 bt.callbackIndex[id] = idx 353 bt.callbacks[idx] = balanceCallback{id, threshold, callback} 354 now := bt.clock.Now() 355 bt.updateBalance(now) 356 bt.checkCallbacks(now) 357 } 358 359 // removeCallback removes the given callback and returns true if it was active 360 func (bt *balanceTracker) removeCallback(id int) bool { 361 bt.lock.Lock() 362 defer bt.lock.Unlock() 363 364 return bt.removeCb(id) 365 } 366 367 // removeCb removes the given callback and returns true if it was active 368 // Note: should be called while bt.lock is held 369 func (bt *balanceTracker) removeCb(id int) bool { 370 idx := bt.callbackIndex[id] 371 if idx == -1 { 372 return false 373 } 374 bt.callbackIndex[id] = -1 375 for i := idx; i < bt.callbackCount-1; i++ { 376 bt.callbackIndex[bt.callbacks[i+1].id]-- 377 bt.callbacks[i] = bt.callbacks[i+1] 378 } 379 bt.callbackCount-- 380 return true 381 }