github.com/juliankolbe/go-ethereum@v1.9.992/les/vflux/server/balance_tracker.go (about) 1 // Copyright 2020 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 "reflect" 21 "sync" 22 "time" 23 24 "github.com/juliankolbe/go-ethereum/common/mclock" 25 "github.com/juliankolbe/go-ethereum/ethdb" 26 "github.com/juliankolbe/go-ethereum/les/utils" 27 "github.com/juliankolbe/go-ethereum/p2p/enode" 28 "github.com/juliankolbe/go-ethereum/p2p/nodestate" 29 ) 30 31 const ( 32 posThreshold = 1000000 // minimum positive balance that is persisted in the database 33 negThreshold = 1000000 // minimum negative balance that is persisted in the database 34 persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence 35 ) 36 37 // BalanceTrackerSetup contains node state flags and fields used by BalanceTracker 38 type BalanceTrackerSetup struct { 39 // controlled by PriorityPool 40 PriorityFlag, UpdateFlag nodestate.Flags 41 BalanceField nodestate.Field 42 // external connections 43 connAddressField, capacityField nodestate.Field 44 } 45 46 // NewBalanceTrackerSetup creates a new BalanceTrackerSetup and initializes the fields 47 // and flags controlled by BalanceTracker 48 func NewBalanceTrackerSetup(setup *nodestate.Setup) BalanceTrackerSetup { 49 return BalanceTrackerSetup{ 50 // PriorityFlag is set if the node has a positive balance 51 PriorityFlag: setup.NewFlag("priorityNode"), 52 // UpdateFlag set and then immediately reset if the balance has been updated and 53 // therefore priority is suddenly changed 54 UpdateFlag: setup.NewFlag("balanceUpdate"), 55 // BalanceField contains the NodeBalance struct which implements nodePriority, 56 // allowing on-demand priority calculation and future priority estimation 57 BalanceField: setup.NewField("balance", reflect.TypeOf(&NodeBalance{})), 58 } 59 } 60 61 // Connect sets the fields used by BalanceTracker as an input 62 func (bts *BalanceTrackerSetup) Connect(connAddressField, capacityField nodestate.Field) { 63 bts.connAddressField = connAddressField 64 bts.capacityField = capacityField 65 } 66 67 // BalanceTracker tracks positive and negative balances for connected nodes. 68 // After connAddressField is set externally, a NodeBalance is created and previous 69 // balance values are loaded from the database. Both balances are exponentially expired 70 // values. Costs are deducted from the positive balance if present, otherwise added to 71 // the negative balance. If the capacity is non-zero then a time cost is applied 72 // continuously while individual request costs are applied immediately. 73 // The two balances are translated into a single priority value that also depends 74 // on the actual capacity. 75 type BalanceTracker struct { 76 BalanceTrackerSetup 77 clock mclock.Clock 78 lock sync.Mutex 79 ns *nodestate.NodeStateMachine 80 ndb *nodeDB 81 posExp, negExp utils.ValueExpirer 82 posExpTC, negExpTC uint64 83 84 active, inactive utils.ExpiredValue 85 balanceTimer *utils.UpdateTimer 86 quit chan struct{} 87 } 88 89 // NewBalanceTracker creates a new BalanceTracker 90 func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup, db ethdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *BalanceTracker { 91 ndb := newNodeDB(db, clock) 92 bt := &BalanceTracker{ 93 ns: ns, 94 BalanceTrackerSetup: setup, 95 ndb: ndb, 96 clock: clock, 97 posExp: posExp, 98 negExp: negExp, 99 balanceTimer: utils.NewUpdateTimer(clock, time.Second*10), 100 quit: make(chan struct{}), 101 } 102 posOffset, negOffset := bt.ndb.getExpiration() 103 posExp.SetLogOffset(clock.Now(), posOffset) 104 negExp.SetLogOffset(clock.Now(), negOffset) 105 106 bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool { 107 bt.inactive.AddExp(balance) 108 return true 109 }) 110 111 ns.SubscribeField(bt.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { 112 n, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance) 113 if n == nil { 114 return 115 } 116 117 ov, _ := oldValue.(uint64) 118 nv, _ := newValue.(uint64) 119 if ov == 0 && nv != 0 { 120 n.activate() 121 } 122 if nv != 0 { 123 n.setCapacity(nv) 124 } 125 if ov != 0 && nv == 0 { 126 n.deactivate() 127 } 128 }) 129 ns.SubscribeField(bt.connAddressField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { 130 if newValue != nil { 131 ns.SetFieldSub(node, bt.BalanceField, bt.newNodeBalance(node, newValue.(string))) 132 } else { 133 ns.SetStateSub(node, nodestate.Flags{}, bt.PriorityFlag, 0) 134 if b, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance); b != nil { 135 b.deactivate() 136 } 137 ns.SetFieldSub(node, bt.BalanceField, nil) 138 } 139 }) 140 141 // The positive and negative balances of clients are stored in database 142 // and both of these decay exponentially over time. Delete them if the 143 // value is small enough. 144 bt.ndb.evictCallBack = bt.canDropBalance 145 146 go func() { 147 for { 148 select { 149 case <-clock.After(persistExpirationRefresh): 150 now := clock.Now() 151 bt.ndb.setExpiration(posExp.LogOffset(now), negExp.LogOffset(now)) 152 case <-bt.quit: 153 return 154 } 155 } 156 }() 157 return bt 158 } 159 160 // Stop saves expiration offset and unsaved node balances and shuts BalanceTracker down 161 func (bt *BalanceTracker) Stop() { 162 now := bt.clock.Now() 163 bt.ndb.setExpiration(bt.posExp.LogOffset(now), bt.negExp.LogOffset(now)) 164 close(bt.quit) 165 bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { 166 if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok { 167 n.lock.Lock() 168 n.storeBalance(true, true) 169 n.lock.Unlock() 170 bt.ns.SetField(node, bt.BalanceField, nil) 171 } 172 }) 173 bt.ndb.close() 174 } 175 176 // TotalTokenAmount returns the current total amount of service tokens in existence 177 func (bt *BalanceTracker) TotalTokenAmount() uint64 { 178 bt.lock.Lock() 179 defer bt.lock.Unlock() 180 181 bt.balanceTimer.Update(func(_ time.Duration) bool { 182 bt.active = utils.ExpiredValue{} 183 bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { 184 if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok && n.active { 185 pos, _ := n.GetRawBalance() 186 bt.active.AddExp(pos) 187 } 188 }) 189 return true 190 }) 191 total := bt.active 192 total.AddExp(bt.inactive) 193 return total.Value(bt.posExp.LogOffset(bt.clock.Now())) 194 } 195 196 // GetPosBalanceIDs lists node IDs with an associated positive balance 197 func (bt *BalanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { 198 return bt.ndb.getPosBalanceIDs(start, stop, maxCount) 199 } 200 201 // SetExpirationTCs sets positive and negative token expiration time constants. 202 // Specified in seconds, 0 means infinite (no expiration). 203 func (bt *BalanceTracker) SetExpirationTCs(pos, neg uint64) { 204 bt.lock.Lock() 205 defer bt.lock.Unlock() 206 207 bt.posExpTC, bt.negExpTC = pos, neg 208 now := bt.clock.Now() 209 if pos > 0 { 210 bt.posExp.SetRate(now, 1/float64(pos*uint64(time.Second))) 211 } else { 212 bt.posExp.SetRate(now, 0) 213 } 214 if neg > 0 { 215 bt.negExp.SetRate(now, 1/float64(neg*uint64(time.Second))) 216 } else { 217 bt.negExp.SetRate(now, 0) 218 } 219 } 220 221 // GetExpirationTCs returns the current positive and negative token expiration 222 // time constants 223 func (bt *BalanceTracker) GetExpirationTCs() (pos, neg uint64) { 224 bt.lock.Lock() 225 defer bt.lock.Unlock() 226 227 return bt.posExpTC, bt.negExpTC 228 } 229 230 // newNodeBalance loads balances from the database and creates a NodeBalance instance 231 // for the given node. It also sets the PriorityFlag and adds balanceCallbackZero if 232 // the node has a positive balance. 233 // Note: this function should run inside a NodeStateMachine operation 234 func (bt *BalanceTracker) newNodeBalance(node *enode.Node, negBalanceKey string) *NodeBalance { 235 pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false) 236 nb := bt.ndb.getOrNewBalance([]byte(negBalanceKey), true) 237 n := &NodeBalance{ 238 bt: bt, 239 node: node, 240 connAddress: negBalanceKey, 241 balance: balance{pos: pb, neg: nb}, 242 initTime: bt.clock.Now(), 243 lastUpdate: bt.clock.Now(), 244 } 245 for i := range n.callbackIndex { 246 n.callbackIndex[i] = -1 247 } 248 if n.checkPriorityStatus() { 249 n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) 250 } 251 return n 252 } 253 254 // storeBalance stores either a positive or a negative balance in the database 255 func (bt *BalanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) { 256 if bt.canDropBalance(bt.clock.Now(), neg, value) { 257 bt.ndb.delBalance(id, neg) // balance is small enough, drop it directly. 258 } else { 259 bt.ndb.setBalance(id, neg, value) 260 } 261 } 262 263 // canDropBalance tells whether a positive or negative balance is below the threshold 264 // and therefore can be dropped from the database 265 func (bt *BalanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { 266 if neg { 267 return b.Value(bt.negExp.LogOffset(now)) <= negThreshold 268 } 269 return b.Value(bt.posExp.LogOffset(now)) <= posThreshold 270 } 271 272 // updateTotalBalance adjusts the total balance after executing given callback. 273 func (bt *BalanceTracker) updateTotalBalance(n *NodeBalance, callback func() bool) { 274 bt.lock.Lock() 275 defer bt.lock.Unlock() 276 277 n.lock.Lock() 278 defer n.lock.Unlock() 279 280 original, active := n.balance.pos, n.active 281 if !callback() { 282 return 283 } 284 if active { 285 bt.active.SubExp(original) 286 } else { 287 bt.inactive.SubExp(original) 288 } 289 if n.active { 290 bt.active.AddExp(n.balance.pos) 291 } else { 292 bt.inactive.AddExp(n.balance.pos) 293 } 294 }