requestbasket.go 9.3 KB

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