github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/lespay/client/requestbasket.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 client 18 19 import ( 20 "io" 21 22 "github.com/cryptogateway/go-paymex/les/utils" 23 "github.com/cryptogateway/go-paymex/rlp" 24 ) 25 26 const basketFactor = 1000000 // reference basket amount and value scale factor 27 28 // referenceBasket keeps track of global request usage statistics and the usual prices 29 // of each used request type relative to each other. The amounts in the basket are scaled 30 // up by basketFactor because of the exponential expiration of long-term statistical data. 31 // Values are scaled so that the sum of all amounts and the sum of all values are equal. 32 // 33 // reqValues represent the internal relative value estimates for each request type and are 34 // calculated as value / amount. The average reqValue of all used requests is 1. 35 // In other words: SUM(refBasket[type].amount * reqValue[type]) = SUM(refBasket[type].amount) 36 type referenceBasket struct { 37 basket requestBasket 38 reqValues []float64 // contents are read only, new slice is created for each update 39 } 40 41 // serverBasket collects served request amount and value statistics for a single server. 42 // 43 // Values are gradually transferred to the global reference basket with a long time 44 // constant so that each server basket represents long term usage and price statistics. 45 // When the transferred part is added to the reference basket the values are scaled so 46 // that their sum equals the total value calculated according to the previous reqValues. 47 // The ratio of request values coming from the server basket represent the pricing of 48 // the specific server and modify the global estimates with a weight proportional to 49 // the amount of service provided by the server. 50 type serverBasket struct { 51 basket requestBasket 52 rvFactor float64 53 } 54 55 type ( 56 // requestBasket holds amounts and values for each request type. 57 // These values are exponentially expired (see utils.ExpiredValue). The power of 2 58 // exponent is applicable to all values within. 59 requestBasket struct { 60 items []basketItem 61 exp uint64 62 } 63 // basketItem holds amount and value for a single request type. Value is the total 64 // relative request value accumulated for served requests while amount is the counter 65 // for each request type. 66 // Note that these values are both scaled up by basketFactor because of the exponential 67 // expiration. 68 basketItem struct { 69 amount, value uint64 70 } 71 ) 72 73 // setExp sets the power of 2 exponent of the structure, scaling base values (the amounts 74 // and request values) up or down if necessary. 75 func (b *requestBasket) setExp(exp uint64) { 76 if exp > b.exp { 77 shift := exp - b.exp 78 for i, item := range b.items { 79 item.amount >>= shift 80 item.value >>= shift 81 b.items[i] = item 82 } 83 b.exp = exp 84 } 85 if exp < b.exp { 86 shift := b.exp - exp 87 for i, item := range b.items { 88 item.amount <<= shift 89 item.value <<= shift 90 b.items[i] = item 91 } 92 b.exp = exp 93 } 94 } 95 96 // init initializes a new server basket with the given service vector size (number of 97 // different request types) 98 func (s *serverBasket) init(size int) { 99 if s.basket.items == nil { 100 s.basket.items = make([]basketItem, size) 101 } 102 } 103 104 // add adds the give type and amount of requests to the basket. Cost is calculated 105 // according to the server's own cost table. 106 func (s *serverBasket) add(reqType, reqAmount uint32, reqCost uint64, expFactor utils.ExpirationFactor) { 107 s.basket.setExp(expFactor.Exp) 108 i := &s.basket.items[reqType] 109 i.amount += uint64(float64(uint64(reqAmount)*basketFactor) * expFactor.Factor) 110 i.value += uint64(float64(reqCost) * s.rvFactor * expFactor.Factor) 111 } 112 113 // updateRvFactor updates the request value factor that scales server costs into the 114 // local value dimensions. 115 func (s *serverBasket) updateRvFactor(rvFactor float64) { 116 s.rvFactor = rvFactor 117 } 118 119 // transfer decreases amounts and values in the basket with the given ratio and 120 // moves the removed amounts into a new basket which is returned and can be added 121 // to the global reference basket. 122 func (s *serverBasket) transfer(ratio float64) requestBasket { 123 res := requestBasket{ 124 items: make([]basketItem, len(s.basket.items)), 125 exp: s.basket.exp, 126 } 127 for i, v := range s.basket.items { 128 ta := uint64(float64(v.amount) * ratio) 129 tv := uint64(float64(v.value) * ratio) 130 if ta > v.amount { 131 ta = v.amount 132 } 133 if tv > v.value { 134 tv = v.value 135 } 136 s.basket.items[i] = basketItem{v.amount - ta, v.value - tv} 137 res.items[i] = basketItem{ta, tv} 138 } 139 return res 140 } 141 142 // init initializes the reference basket with the given service vector size (number of 143 // different request types) 144 func (r *referenceBasket) init(size int) { 145 r.reqValues = make([]float64, size) 146 r.normalize() 147 r.updateReqValues() 148 } 149 150 // add adds the transferred part of a server basket to the reference basket while scaling 151 // value amounts so that their sum equals the total value calculated according to the 152 // previous reqValues. 153 func (r *referenceBasket) add(newBasket requestBasket) { 154 r.basket.setExp(newBasket.exp) 155 // scale newBasket to match service unit value 156 var ( 157 totalCost uint64 158 totalValue float64 159 ) 160 for i, v := range newBasket.items { 161 totalCost += v.value 162 totalValue += float64(v.amount) * r.reqValues[i] 163 } 164 if totalCost > 0 { 165 // add to reference with scaled values 166 scaleValues := totalValue / float64(totalCost) 167 for i, v := range newBasket.items { 168 r.basket.items[i].amount += v.amount 169 r.basket.items[i].value += uint64(float64(v.value) * scaleValues) 170 } 171 } 172 r.updateReqValues() 173 } 174 175 // updateReqValues recalculates reqValues after adding transferred baskets. Note that 176 // values should be normalized first. 177 func (r *referenceBasket) updateReqValues() { 178 r.reqValues = make([]float64, len(r.reqValues)) 179 for i, b := range r.basket.items { 180 if b.amount > 0 { 181 r.reqValues[i] = float64(b.value) / float64(b.amount) 182 } else { 183 r.reqValues[i] = 0 184 } 185 } 186 } 187 188 // normalize ensures that the sum of values equal the sum of amounts in the basket. 189 func (r *referenceBasket) normalize() { 190 var sumAmount, sumValue uint64 191 for _, b := range r.basket.items { 192 sumAmount += b.amount 193 sumValue += b.value 194 } 195 add := float64(int64(sumAmount-sumValue)) / float64(sumValue) 196 for i, b := range r.basket.items { 197 b.value += uint64(int64(float64(b.value) * add)) 198 r.basket.items[i] = b 199 } 200 } 201 202 // reqValueFactor calculates the request value factor applicable to the server with 203 // the given announced request cost list 204 func (r *referenceBasket) reqValueFactor(costList []uint64) float64 { 205 var ( 206 totalCost float64 207 totalValue uint64 208 ) 209 for i, b := range r.basket.items { 210 totalCost += float64(costList[i]) * float64(b.amount) // use floats to avoid overflow 211 totalValue += b.value 212 } 213 if totalCost < 1 { 214 return 0 215 } 216 return float64(totalValue) * basketFactor / totalCost 217 } 218 219 // EncodeRLP implements rlp.Encoder 220 func (b *basketItem) EncodeRLP(w io.Writer) error { 221 return rlp.Encode(w, []interface{}{b.amount, b.value}) 222 } 223 224 // DecodeRLP implements rlp.Decoder 225 func (b *basketItem) DecodeRLP(s *rlp.Stream) error { 226 var item struct { 227 Amount, Value uint64 228 } 229 if err := s.Decode(&item); err != nil { 230 return err 231 } 232 b.amount, b.value = item.Amount, item.Value 233 return nil 234 } 235 236 // EncodeRLP implements rlp.Encoder 237 func (r *requestBasket) EncodeRLP(w io.Writer) error { 238 return rlp.Encode(w, []interface{}{r.items, r.exp}) 239 } 240 241 // DecodeRLP implements rlp.Decoder 242 func (r *requestBasket) DecodeRLP(s *rlp.Stream) error { 243 var enc struct { 244 Items []basketItem 245 Exp uint64 246 } 247 if err := s.Decode(&enc); err != nil { 248 return err 249 } 250 r.items, r.exp = enc.Items, enc.Exp 251 return nil 252 } 253 254 // convertMapping converts a basket loaded from the database into the current format. 255 // If the available request types and their mapping into the service vector differ from 256 // the one used when saving the basket then this function reorders old fields and fills 257 // in previously unknown fields by scaling up amounts and values taken from the 258 // initialization basket. 259 func (r requestBasket) convertMapping(oldMapping, newMapping []string, initBasket requestBasket) requestBasket { 260 nameMap := make(map[string]int) 261 for i, name := range oldMapping { 262 nameMap[name] = i 263 } 264 rc := requestBasket{items: make([]basketItem, len(newMapping))} 265 var scale, oldScale, newScale float64 266 for i, name := range newMapping { 267 if ii, ok := nameMap[name]; ok { 268 rc.items[i] = r.items[ii] 269 oldScale += float64(initBasket.items[i].amount) * float64(initBasket.items[i].amount) 270 newScale += float64(rc.items[i].amount) * float64(initBasket.items[i].amount) 271 } 272 } 273 if oldScale > 1e-10 { 274 scale = newScale / oldScale 275 } else { 276 scale = 1 277 } 278 for i, name := range newMapping { 279 if _, ok := nameMap[name]; !ok { 280 rc.items[i].amount = uint64(float64(initBasket.items[i].amount) * scale) 281 rc.items[i].value = uint64(float64(initBasket.items[i].value) * scale) 282 } 283 } 284 return rc 285 }