github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/lespay/server/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 server 18 19 import ( 20 "errors" 21 "math" 22 "sync" 23 "time" 24 25 "github.com/cryptogateway/go-paymex/common/mclock" 26 "github.com/cryptogateway/go-paymex/les/utils" 27 "github.com/cryptogateway/go-paymex/p2p/enode" 28 "github.com/cryptogateway/go-paymex/p2p/nodestate" 29 ) 30 31 var errBalanceOverflow = errors.New("balance overflow") 32 33 const maxBalance = math.MaxInt64 // maximum allowed balance value 34 35 const ( 36 balanceCallbackUpdate = iota // called when priority drops below the last minimum estimate 37 balanceCallbackZero // called when priority drops to zero (positive balance exhausted) 38 balanceCallbackCount // total number of balance callbacks 39 ) 40 41 // PriceFactors determine the pricing policy (may apply either to positive or 42 // negative balances which may have different factors). 43 // - TimeFactor is cost unit per nanosecond of connection time 44 // - CapacityFactor is cost unit per nanosecond of connection time per 1000000 capacity 45 // - RequestFactor is cost unit per request "realCost" unit 46 type PriceFactors struct { 47 TimeFactor, CapacityFactor, RequestFactor float64 48 } 49 50 // timePrice returns the price of connection per nanosecond at the given capacity 51 func (p PriceFactors) timePrice(cap uint64) float64 { 52 return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 53 } 54 55 // NodeBalance keeps track of the positive and negative balances of a connected 56 // client and calculates actual and projected future priority values. 57 // Implements nodePriority interface. 58 type NodeBalance struct { 59 bt *BalanceTracker 60 lock sync.RWMutex 61 node *enode.Node 62 connAddress string 63 active bool 64 priority bool 65 capacity uint64 66 balance balance 67 posFactor, negFactor PriceFactors 68 sumReqCost uint64 69 lastUpdate, nextUpdate, initTime mclock.AbsTime 70 updateEvent mclock.Timer 71 // since only a limited and fixed number of callbacks are needed, they are 72 // stored in a fixed size array ordered by priority threshold. 73 callbacks [balanceCallbackCount]balanceCallback 74 // callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active) 75 callbackIndex [balanceCallbackCount]int 76 callbackCount int // number of active callbacks 77 } 78 79 // balance represents a pair of positive and negative balances 80 type balance struct { 81 pos, neg utils.ExpiredValue 82 } 83 84 // balanceCallback represents a single callback that is activated when client priority 85 // reaches the given threshold 86 type balanceCallback struct { 87 id int 88 threshold int64 89 callback func() 90 } 91 92 // GetBalance returns the current positive and negative balance. 93 func (n *NodeBalance) GetBalance() (uint64, uint64) { 94 n.lock.Lock() 95 defer n.lock.Unlock() 96 97 now := n.bt.clock.Now() 98 n.updateBalance(now) 99 return n.balance.pos.Value(n.bt.posExp.LogOffset(now)), n.balance.neg.Value(n.bt.negExp.LogOffset(now)) 100 } 101 102 // GetRawBalance returns the current positive and negative balance 103 // but in the raw(expired value) format. 104 func (n *NodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) { 105 n.lock.Lock() 106 defer n.lock.Unlock() 107 108 now := n.bt.clock.Now() 109 n.updateBalance(now) 110 return n.balance.pos, n.balance.neg 111 } 112 113 // AddBalance adds the given amount to the positive balance and returns the balance 114 // before and after the operation. Exceeding maxBalance results in an error (balance is 115 // unchanged) while adding a negative amount higher than the current balance results in 116 // zero balance. 117 func (n *NodeBalance) AddBalance(amount int64) (uint64, uint64, error) { 118 var ( 119 err error 120 old, new uint64 121 ) 122 n.bt.ns.Operation(func() { 123 var ( 124 callbacks []func() 125 setPriority bool 126 ) 127 n.bt.updateTotalBalance(n, func() bool { 128 now := n.bt.clock.Now() 129 n.updateBalance(now) 130 131 // Ensure the given amount is valid to apply. 132 offset := n.bt.posExp.LogOffset(now) 133 old = n.balance.pos.Value(offset) 134 if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) { 135 err = errBalanceOverflow 136 return false 137 } 138 139 // Update the total positive balance counter. 140 n.balance.pos.Add(amount, offset) 141 callbacks = n.checkCallbacks(now) 142 setPriority = n.checkPriorityStatus() 143 new = n.balance.pos.Value(offset) 144 n.storeBalance(true, false) 145 return true 146 }) 147 for _, cb := range callbacks { 148 cb() 149 } 150 if setPriority { 151 n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) 152 } 153 n.signalPriorityUpdate() 154 }) 155 if err != nil { 156 return old, old, err 157 } 158 159 return old, new, nil 160 } 161 162 // SetBalance sets the positive and negative balance to the given values 163 func (n *NodeBalance) SetBalance(pos, neg uint64) error { 164 if pos > maxBalance || neg > maxBalance { 165 return errBalanceOverflow 166 } 167 n.bt.ns.Operation(func() { 168 var ( 169 callbacks []func() 170 setPriority bool 171 ) 172 n.bt.updateTotalBalance(n, func() bool { 173 now := n.bt.clock.Now() 174 n.updateBalance(now) 175 176 var pb, nb utils.ExpiredValue 177 pb.Add(int64(pos), n.bt.posExp.LogOffset(now)) 178 nb.Add(int64(neg), n.bt.negExp.LogOffset(now)) 179 n.balance.pos = pb 180 n.balance.neg = nb 181 callbacks = n.checkCallbacks(now) 182 setPriority = n.checkPriorityStatus() 183 n.storeBalance(true, true) 184 return true 185 }) 186 for _, cb := range callbacks { 187 cb() 188 } 189 if setPriority { 190 n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) 191 } 192 n.signalPriorityUpdate() 193 }) 194 return nil 195 } 196 197 // RequestServed should be called after serving a request for the given peer 198 func (n *NodeBalance) RequestServed(cost uint64) uint64 { 199 n.lock.Lock() 200 var callbacks []func() 201 defer func() { 202 n.lock.Unlock() 203 if callbacks != nil { 204 n.bt.ns.Operation(func() { 205 for _, cb := range callbacks { 206 cb() 207 } 208 }) 209 } 210 }() 211 212 now := n.bt.clock.Now() 213 n.updateBalance(now) 214 fcost := float64(cost) 215 216 posExp := n.bt.posExp.LogOffset(now) 217 var check bool 218 if !n.balance.pos.IsZero() { 219 if n.posFactor.RequestFactor != 0 { 220 c := -int64(fcost * n.posFactor.RequestFactor) 221 cc := n.balance.pos.Add(c, posExp) 222 if c == cc { 223 fcost = 0 224 } else { 225 fcost *= 1 - float64(cc)/float64(c) 226 } 227 check = true 228 } else { 229 fcost = 0 230 } 231 } 232 if fcost > 0 { 233 if n.negFactor.RequestFactor != 0 { 234 n.balance.neg.Add(int64(fcost*n.negFactor.RequestFactor), n.bt.negExp.LogOffset(now)) 235 check = true 236 } 237 } 238 if check { 239 callbacks = n.checkCallbacks(now) 240 } 241 n.sumReqCost += cost 242 return n.balance.pos.Value(posExp) 243 } 244 245 // Priority returns the actual priority based on the current balance 246 func (n *NodeBalance) Priority(now mclock.AbsTime, capacity uint64) int64 { 247 n.lock.Lock() 248 defer n.lock.Unlock() 249 250 n.updateBalance(now) 251 return n.balanceToPriority(n.balance, capacity) 252 } 253 254 // EstMinPriority gives a lower estimate for the priority at a given time in the future. 255 // An average request cost per time is assumed that is twice the average cost per time 256 // in the current session. 257 // If update is true then a priority callback is added that turns UpdateFlag on and off 258 // in case the priority goes below the estimated minimum. 259 func (n *NodeBalance) EstMinPriority(at mclock.AbsTime, capacity uint64, update bool) int64 { 260 n.lock.Lock() 261 defer n.lock.Unlock() 262 263 var avgReqCost float64 264 dt := time.Duration(n.lastUpdate - n.initTime) 265 if dt > time.Second { 266 avgReqCost = float64(n.sumReqCost) * 2 / float64(dt) 267 } 268 pri := n.balanceToPriority(n.reducedBalance(at, capacity, avgReqCost), capacity) 269 if update { 270 n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) 271 } 272 return pri 273 } 274 275 // PosBalanceMissing calculates the missing amount of positive balance in order to 276 // connect at targetCapacity, stay connected for the given amount of time and then 277 // still have a priority of targetPriority 278 func (n *NodeBalance) PosBalanceMissing(targetPriority int64, targetCapacity uint64, after time.Duration) uint64 { 279 n.lock.Lock() 280 defer n.lock.Unlock() 281 282 now := n.bt.clock.Now() 283 if targetPriority < 0 { 284 timePrice := n.negFactor.timePrice(targetCapacity) 285 timeCost := uint64(float64(after) * timePrice) 286 negBalance := n.balance.neg.Value(n.bt.negExp.LogOffset(now)) 287 if timeCost+negBalance < uint64(-targetPriority) { 288 return 0 289 } 290 if uint64(-targetPriority) > negBalance && timePrice > 1e-100 { 291 if negTime := time.Duration(float64(uint64(-targetPriority)-negBalance) / timePrice); negTime < after { 292 after -= negTime 293 } else { 294 after = 0 295 } 296 } 297 targetPriority = 0 298 } 299 timePrice := n.posFactor.timePrice(targetCapacity) 300 posRequired := uint64(float64(targetPriority)*float64(targetCapacity)+float64(after)*timePrice) + 1 301 if posRequired >= maxBalance { 302 return math.MaxUint64 // target not reachable 303 } 304 posBalance := n.balance.pos.Value(n.bt.posExp.LogOffset(now)) 305 if posRequired > posBalance { 306 return posRequired - posBalance 307 } 308 return 0 309 } 310 311 // SetPriceFactors sets the price factors. TimeFactor is the price of a nanosecond of 312 // connection while RequestFactor is the price of a request cost unit. 313 func (n *NodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) { 314 n.lock.Lock() 315 now := n.bt.clock.Now() 316 n.updateBalance(now) 317 n.posFactor, n.negFactor = posFactor, negFactor 318 callbacks := n.checkCallbacks(now) 319 n.lock.Unlock() 320 if callbacks != nil { 321 n.bt.ns.Operation(func() { 322 for _, cb := range callbacks { 323 cb() 324 } 325 }) 326 } 327 } 328 329 // GetPriceFactors returns the price factors 330 func (n *NodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) { 331 n.lock.Lock() 332 defer n.lock.Unlock() 333 334 return n.posFactor, n.negFactor 335 } 336 337 // activate starts time/capacity cost deduction. 338 func (n *NodeBalance) activate() { 339 n.bt.updateTotalBalance(n, func() bool { 340 if n.active { 341 return false 342 } 343 n.active = true 344 n.lastUpdate = n.bt.clock.Now() 345 return true 346 }) 347 } 348 349 // deactivate stops time/capacity cost deduction and saves the balances in the database 350 func (n *NodeBalance) deactivate() { 351 n.bt.updateTotalBalance(n, func() bool { 352 if !n.active { 353 return false 354 } 355 n.updateBalance(n.bt.clock.Now()) 356 if n.updateEvent != nil { 357 n.updateEvent.Stop() 358 n.updateEvent = nil 359 } 360 n.storeBalance(true, true) 361 n.active = false 362 return true 363 }) 364 } 365 366 // updateBalance updates balance based on the time factor 367 func (n *NodeBalance) updateBalance(now mclock.AbsTime) { 368 if n.active && now > n.lastUpdate { 369 n.balance = n.reducedBalance(now, n.capacity, 0) 370 n.lastUpdate = now 371 } 372 } 373 374 // storeBalance stores the positive and/or negative balance of the node in the database 375 func (n *NodeBalance) storeBalance(pos, neg bool) { 376 if pos { 377 n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos) 378 } 379 if neg { 380 n.bt.storeBalance([]byte(n.connAddress), true, n.balance.neg) 381 } 382 } 383 384 // addCallback sets up a one-time callback to be called when priority reaches 385 // the threshold. If it has already reached the threshold the callback is called 386 // immediately. 387 // Note: should be called while n.lock is held 388 // Note 2: the callback function runs inside a NodeStateMachine operation 389 func (n *NodeBalance) addCallback(id int, threshold int64, callback func()) { 390 n.removeCallback(id) 391 idx := 0 392 for idx < n.callbackCount && threshold > n.callbacks[idx].threshold { 393 idx++ 394 } 395 for i := n.callbackCount - 1; i >= idx; i-- { 396 n.callbackIndex[n.callbacks[i].id]++ 397 n.callbacks[i+1] = n.callbacks[i] 398 } 399 n.callbackCount++ 400 n.callbackIndex[id] = idx 401 n.callbacks[idx] = balanceCallback{id, threshold, callback} 402 now := n.bt.clock.Now() 403 n.updateBalance(now) 404 n.scheduleCheck(now) 405 } 406 407 // removeCallback removes the given callback and returns true if it was active 408 // Note: should be called while n.lock is held 409 func (n *NodeBalance) removeCallback(id int) bool { 410 idx := n.callbackIndex[id] 411 if idx == -1 { 412 return false 413 } 414 n.callbackIndex[id] = -1 415 for i := idx; i < n.callbackCount-1; i++ { 416 n.callbackIndex[n.callbacks[i+1].id]-- 417 n.callbacks[i] = n.callbacks[i+1] 418 } 419 n.callbackCount-- 420 return true 421 } 422 423 // checkCallbacks checks whether the threshold of any of the active callbacks 424 // have been reached and returns triggered callbacks. 425 // Note: checkCallbacks assumes that the balance has been recently updated. 426 func (n *NodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { 427 if n.callbackCount == 0 || n.capacity == 0 { 428 return 429 } 430 pri := n.balanceToPriority(n.balance, n.capacity) 431 for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri { 432 n.callbackCount-- 433 n.callbackIndex[n.callbacks[n.callbackCount].id] = -1 434 callbacks = append(callbacks, n.callbacks[n.callbackCount].callback) 435 } 436 n.scheduleCheck(now) 437 return 438 } 439 440 // scheduleCheck sets up or updates a scheduled event to ensure that it will be called 441 // again just after the next threshold has been reached. 442 func (n *NodeBalance) scheduleCheck(now mclock.AbsTime) { 443 if n.callbackCount != 0 { 444 d, ok := n.timeUntil(n.callbacks[n.callbackCount-1].threshold) 445 if !ok { 446 n.nextUpdate = 0 447 n.updateAfter(0) 448 return 449 } 450 if n.nextUpdate == 0 || n.nextUpdate > now+mclock.AbsTime(d) { 451 if d > time.Second { 452 // Note: if the scheduled update is not in the very near future then we 453 // schedule the update a bit earlier. This way we do need to update a few 454 // extra times but don't need to reschedule every time a processed request 455 // brings the expected firing time a little bit closer. 456 d = ((d - time.Second) * 7 / 8) + time.Second 457 } 458 n.nextUpdate = now + mclock.AbsTime(d) 459 n.updateAfter(d) 460 } 461 } else { 462 n.nextUpdate = 0 463 n.updateAfter(0) 464 } 465 } 466 467 // updateAfter schedules a balance update and callback check in the future 468 func (n *NodeBalance) updateAfter(dt time.Duration) { 469 if n.updateEvent == nil || n.updateEvent.Stop() { 470 if dt == 0 { 471 n.updateEvent = nil 472 } else { 473 n.updateEvent = n.bt.clock.AfterFunc(dt, func() { 474 var callbacks []func() 475 n.lock.Lock() 476 if n.callbackCount != 0 { 477 now := n.bt.clock.Now() 478 n.updateBalance(now) 479 callbacks = n.checkCallbacks(now) 480 } 481 n.lock.Unlock() 482 if callbacks != nil { 483 n.bt.ns.Operation(func() { 484 for _, cb := range callbacks { 485 cb() 486 } 487 }) 488 } 489 }) 490 } 491 } 492 } 493 494 // balanceExhausted should be called when the positive balance is exhausted (priority goes to zero/negative) 495 // Note: this function should run inside a NodeStateMachine operation 496 func (n *NodeBalance) balanceExhausted() { 497 n.lock.Lock() 498 n.storeBalance(true, false) 499 n.priority = false 500 n.lock.Unlock() 501 n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.PriorityFlag, 0) 502 } 503 504 // checkPriorityStatus checks whether the node has gained priority status and sets the priority 505 // callback and flag if necessary. It assumes that the balance has been recently updated. 506 // Note that the priority flag has to be set by the caller after the mutex has been released. 507 func (n *NodeBalance) checkPriorityStatus() bool { 508 if !n.priority && !n.balance.pos.IsZero() { 509 n.priority = true 510 n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() }) 511 return true 512 } 513 return false 514 } 515 516 // signalPriorityUpdate signals that the priority fell below the previous minimum estimate 517 // Note: this function should run inside a NodeStateMachine operation 518 func (n *NodeBalance) signalPriorityUpdate() { 519 n.bt.ns.SetStateSub(n.node, n.bt.UpdateFlag, nodestate.Flags{}, 0) 520 n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.UpdateFlag, 0) 521 } 522 523 // setCapacity updates the capacity value used for priority calculation 524 // Note: capacity should never be zero 525 // Note 2: this function should run inside a NodeStateMachine operation 526 func (n *NodeBalance) setCapacity(capacity uint64) { 527 n.lock.Lock() 528 now := n.bt.clock.Now() 529 n.updateBalance(now) 530 n.capacity = capacity 531 callbacks := n.checkCallbacks(now) 532 n.lock.Unlock() 533 for _, cb := range callbacks { 534 cb() 535 } 536 } 537 538 // balanceToPriority converts a balance to a priority value. Lower priority means 539 // first to disconnect. Positive balance translates to positive priority. If positive 540 // balance is zero then negative balance translates to a negative priority. 541 func (n *NodeBalance) balanceToPriority(b balance, capacity uint64) int64 { 542 if !b.pos.IsZero() { 543 return int64(b.pos.Value(n.bt.posExp.LogOffset(n.bt.clock.Now())) / capacity) 544 } 545 return -int64(b.neg.Value(n.bt.negExp.LogOffset(n.bt.clock.Now()))) 546 } 547 548 // reducedBalance estimates the reduced balance at a given time in the fututre based 549 // on the current balance, the time factor and an estimated average request cost per time ratio 550 func (n *NodeBalance) reducedBalance(at mclock.AbsTime, capacity uint64, avgReqCost float64) balance { 551 dt := float64(at - n.lastUpdate) 552 b := n.balance 553 if !b.pos.IsZero() { 554 factor := n.posFactor.timePrice(capacity) + n.posFactor.RequestFactor*avgReqCost 555 diff := -int64(dt * factor) 556 dd := b.pos.Add(diff, n.bt.posExp.LogOffset(at)) 557 if dd == diff { 558 dt = 0 559 } else { 560 dt += float64(dd) / factor 561 } 562 } 563 if dt > 0 { 564 factor := n.negFactor.timePrice(capacity) + n.negFactor.RequestFactor*avgReqCost 565 b.neg.Add(int64(dt*factor), n.bt.negExp.LogOffset(at)) 566 } 567 return b 568 } 569 570 // timeUntil calculates the remaining time needed to reach a given priority level 571 // assuming that no requests are processed until then. If the given level is never 572 // reached then (0, false) is returned. 573 // Note: the function assumes that the balance has been recently updated and 574 // calculates the time starting from the last update. 575 func (n *NodeBalance) timeUntil(priority int64) (time.Duration, bool) { 576 now := n.bt.clock.Now() 577 var dt float64 578 if !n.balance.pos.IsZero() { 579 posBalance := n.balance.pos.Value(n.bt.posExp.LogOffset(now)) 580 timePrice := n.posFactor.timePrice(n.capacity) 581 if timePrice < 1e-100 { 582 return 0, false 583 } 584 if priority > 0 { 585 newBalance := uint64(priority) * n.capacity 586 if newBalance > posBalance { 587 return 0, false 588 } 589 dt = float64(posBalance-newBalance) / timePrice 590 return time.Duration(dt), true 591 } 592 dt = float64(posBalance) / timePrice 593 } else { 594 if priority > 0 { 595 return 0, false 596 } 597 } 598 // if we have a positive balance then dt equals the time needed to get it to zero 599 negBalance := n.balance.neg.Value(n.bt.negExp.LogOffset(now)) 600 timePrice := n.negFactor.timePrice(n.capacity) 601 if uint64(-priority) > negBalance { 602 if timePrice < 1e-100 { 603 return 0, false 604 } 605 dt += float64(uint64(-priority)-negBalance) / timePrice 606 } 607 return time.Duration(dt), true 608 }