github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/api.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 "errors" 21 "fmt" 22 "time" 23 24 "github.com/cryptogateway/go-paymex/common/hexutil" 25 "github.com/cryptogateway/go-paymex/common/mclock" 26 lps "github.com/cryptogateway/go-paymex/les/lespay/server" 27 "github.com/cryptogateway/go-paymex/p2p/enode" 28 ) 29 30 var ( 31 errNoCheckpoint = errors.New("no local checkpoint provided") 32 errNotActivated = errors.New("checkpoint registrar is not activated") 33 errUnknownBenchmarkType = errors.New("unknown benchmark type") 34 errNoPriority = errors.New("priority too low to raise capacity") 35 ) 36 37 // PrivateLightServerAPI provides an API to access the LES light server. 38 type PrivateLightServerAPI struct { 39 server *LesServer 40 defaultPosFactors, defaultNegFactors lps.PriceFactors 41 } 42 43 // NewPrivateLightServerAPI creates a new LES light server API. 44 func NewPrivateLightServerAPI(server *LesServer) *PrivateLightServerAPI { 45 return &PrivateLightServerAPI{ 46 server: server, 47 defaultPosFactors: server.clientPool.defaultPosFactors, 48 defaultNegFactors: server.clientPool.defaultNegFactors, 49 } 50 } 51 52 // ServerInfo returns global server parameters 53 func (api *PrivateLightServerAPI) ServerInfo() map[string]interface{} { 54 res := make(map[string]interface{}) 55 res["minimumCapacity"] = api.server.minCapacity 56 res["maximumCapacity"] = api.server.maxCapacity 57 res["totalCapacity"], res["totalConnectedCapacity"], res["priorityConnectedCapacity"] = api.server.clientPool.capacityInfo() 58 return res 59 } 60 61 // ClientInfo returns information about clients listed in the ids list or matching the given tags 62 func (api *PrivateLightServerAPI) ClientInfo(ids []enode.ID) map[enode.ID]map[string]interface{} { 63 res := make(map[enode.ID]map[string]interface{}) 64 api.server.clientPool.forClients(ids, func(client *clientInfo) { 65 res[client.node.ID()] = api.clientInfo(client) 66 }) 67 return res 68 } 69 70 // PriorityClientInfo returns information about clients with a positive balance 71 // in the given ID range (stop excluded). If stop is null then the iterator stops 72 // only at the end of the ID space. MaxCount limits the number of results returned. 73 // If maxCount limit is applied but there are more potential results then the ID 74 // of the next potential result is included in the map with an empty structure 75 // assigned to it. 76 func (api *PrivateLightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int) map[enode.ID]map[string]interface{} { 77 res := make(map[enode.ID]map[string]interface{}) 78 ids := api.server.clientPool.bt.GetPosBalanceIDs(start, stop, maxCount+1) 79 if len(ids) > maxCount { 80 res[ids[maxCount]] = make(map[string]interface{}) 81 ids = ids[:maxCount] 82 } 83 if len(ids) != 0 { 84 api.server.clientPool.forClients(ids, func(client *clientInfo) { 85 res[client.node.ID()] = api.clientInfo(client) 86 }) 87 } 88 return res 89 } 90 91 // clientInfo creates a client info data structure 92 func (api *PrivateLightServerAPI) clientInfo(c *clientInfo) map[string]interface{} { 93 info := make(map[string]interface{}) 94 pb, nb := c.balance.GetBalance() 95 info["isConnected"] = c.connected 96 info["pricing/balance"] = pb 97 info["priority"] = pb != 0 98 // cb := api.server.clientPool.ndb.getCurrencyBalance(id) 99 // info["pricing/currency"] = cb.amount 100 if c.connected { 101 info["connectionTime"] = float64(mclock.Now()-c.connectedAt) / float64(time.Second) 102 info["capacity"], _ = api.server.clientPool.ns.GetField(c.node, priorityPoolSetup.CapacityField).(uint64) 103 info["pricing/negBalance"] = nb 104 } 105 return info 106 } 107 108 // setParams either sets the given parameters for a single connected client (if specified) 109 // or the default parameters applicable to clients connected in the future 110 func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *lps.PriceFactors) (updateFactors bool, err error) { 111 defParams := client == nil 112 for name, value := range params { 113 errValue := func() error { 114 return fmt.Errorf("invalid value for parameter '%s'", name) 115 } 116 setFactor := func(v *float64) { 117 if val, ok := value.(float64); ok && val >= 0 { 118 *v = val / float64(time.Second) 119 updateFactors = true 120 } else { 121 err = errValue() 122 } 123 } 124 125 switch { 126 case name == "pricing/timeFactor": 127 setFactor(&posFactors.TimeFactor) 128 case name == "pricing/capacityFactor": 129 setFactor(&posFactors.CapacityFactor) 130 case name == "pricing/requestCostFactor": 131 setFactor(&posFactors.RequestFactor) 132 case name == "pricing/negative/timeFactor": 133 setFactor(&negFactors.TimeFactor) 134 case name == "pricing/negative/capacityFactor": 135 setFactor(&negFactors.CapacityFactor) 136 case name == "pricing/negative/requestCostFactor": 137 setFactor(&negFactors.RequestFactor) 138 case !defParams && name == "capacity": 139 if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity { 140 _, err = api.server.clientPool.setCapacity(client.node, client.address, uint64(capacity), 0, true) 141 // Don't have to call factor update explicitly. It's already done 142 // in setCapacity function. 143 } else { 144 err = errValue() 145 } 146 default: 147 if defParams { 148 err = fmt.Errorf("invalid default parameter '%s'", name) 149 } else { 150 err = fmt.Errorf("invalid client parameter '%s'", name) 151 } 152 } 153 if err != nil { 154 return 155 } 156 } 157 return 158 } 159 160 // SetClientParams sets client parameters for all clients listed in the ids list 161 // or all connected clients if the list is empty 162 func (api *PrivateLightServerAPI) SetClientParams(ids []enode.ID, params map[string]interface{}) error { 163 var err error 164 api.server.clientPool.forClients(ids, func(client *clientInfo) { 165 if client.connected { 166 posFactors, negFactors := client.balance.GetPriceFactors() 167 update, e := api.setParams(params, client, &posFactors, &negFactors) 168 if update { 169 client.balance.SetPriceFactors(posFactors, negFactors) 170 } 171 if e != nil { 172 err = e 173 } 174 } else { 175 err = fmt.Errorf("client %064x is not connected", client.node.ID()) 176 } 177 }) 178 return err 179 } 180 181 // SetDefaultParams sets the default parameters applicable to clients connected in the future 182 func (api *PrivateLightServerAPI) SetDefaultParams(params map[string]interface{}) error { 183 update, err := api.setParams(params, nil, &api.defaultPosFactors, &api.defaultNegFactors) 184 if update { 185 api.server.clientPool.setDefaultFactors(api.defaultPosFactors, api.defaultNegFactors) 186 } 187 return err 188 } 189 190 // SetConnectedBias set the connection bias, which is applied to already connected clients 191 // So that already connected client won't be kicked out very soon and we can ensure all 192 // connected clients can have enough time to request or sync some data. 193 // When the input parameter `bias` < 0 (illegal), return error. 194 func (api *PrivateLightServerAPI) SetConnectedBias(bias time.Duration) error { 195 if bias < time.Duration(0) { 196 return fmt.Errorf("bias illegal: %v less than 0", bias) 197 } 198 api.server.clientPool.setConnectedBias(bias) 199 return nil 200 } 201 202 // AddBalance adds the given amount to the balance of a client if possible and returns 203 // the balance before and after the operation 204 func (api *PrivateLightServerAPI) AddBalance(id enode.ID, amount int64) (balance [2]uint64, err error) { 205 api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { 206 balance[0], balance[1], err = c.balance.AddBalance(amount) 207 }) 208 return 209 } 210 211 // Benchmark runs a request performance benchmark with a given set of measurement setups 212 // in multiple passes specified by passCount. The measurement time for each setup in each 213 // pass is specified in milliseconds by length. 214 // 215 // Note: measurement time is adjusted for each pass depending on the previous ones. 216 // Therefore a controlled total measurement time is achievable in multiple passes. 217 func (api *PrivateLightServerAPI) Benchmark(setups []map[string]interface{}, passCount, length int) ([]map[string]interface{}, error) { 218 benchmarks := make([]requestBenchmark, len(setups)) 219 for i, setup := range setups { 220 if t, ok := setup["type"].(string); ok { 221 getInt := func(field string, def int) int { 222 if value, ok := setup[field].(float64); ok { 223 return int(value) 224 } 225 return def 226 } 227 getBool := func(field string, def bool) bool { 228 if value, ok := setup[field].(bool); ok { 229 return value 230 } 231 return def 232 } 233 switch t { 234 case "header": 235 benchmarks[i] = &benchmarkBlockHeaders{ 236 amount: getInt("amount", 1), 237 skip: getInt("skip", 1), 238 byHash: getBool("byHash", false), 239 reverse: getBool("reverse", false), 240 } 241 case "body": 242 benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: false} 243 case "receipts": 244 benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: true} 245 case "proof": 246 benchmarks[i] = &benchmarkProofsOrCode{code: false} 247 case "code": 248 benchmarks[i] = &benchmarkProofsOrCode{code: true} 249 case "cht": 250 benchmarks[i] = &benchmarkHelperTrie{ 251 bloom: false, 252 reqCount: getInt("amount", 1), 253 } 254 case "bloom": 255 benchmarks[i] = &benchmarkHelperTrie{ 256 bloom: true, 257 reqCount: getInt("amount", 1), 258 } 259 case "txSend": 260 benchmarks[i] = &benchmarkTxSend{} 261 case "txStatus": 262 benchmarks[i] = &benchmarkTxStatus{} 263 default: 264 return nil, errUnknownBenchmarkType 265 } 266 } else { 267 return nil, errUnknownBenchmarkType 268 } 269 } 270 rs := api.server.handler.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length)) 271 result := make([]map[string]interface{}, len(setups)) 272 for i, r := range rs { 273 res := make(map[string]interface{}) 274 if r.err == nil { 275 res["totalCount"] = r.totalCount 276 res["avgTime"] = r.avgTime 277 res["maxInSize"] = r.maxInSize 278 res["maxOutSize"] = r.maxOutSize 279 } else { 280 res["error"] = r.err.Error() 281 } 282 result[i] = res 283 } 284 return result, nil 285 } 286 287 // PrivateDebugAPI provides an API to debug LES light server functionality. 288 type PrivateDebugAPI struct { 289 server *LesServer 290 } 291 292 // NewPrivateDebugAPI creates a new LES light server debug API. 293 func NewPrivateDebugAPI(server *LesServer) *PrivateDebugAPI { 294 return &PrivateDebugAPI{ 295 server: server, 296 } 297 } 298 299 // FreezeClient forces a temporary client freeze which normally happens when the server is overloaded 300 func (api *PrivateDebugAPI) FreezeClient(id enode.ID) error { 301 var err error 302 api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { 303 if c.connected { 304 c.peer.freeze() 305 } else { 306 err = fmt.Errorf("client %064x is not connected", id[:]) 307 } 308 }) 309 return err 310 } 311 312 // PrivateLightAPI provides an API to access the LES light server or light client. 313 type PrivateLightAPI struct { 314 backend *lesCommons 315 } 316 317 // NewPrivateLightAPI creates a new LES service API. 318 func NewPrivateLightAPI(backend *lesCommons) *PrivateLightAPI { 319 return &PrivateLightAPI{backend: backend} 320 } 321 322 // LatestCheckpoint returns the latest local checkpoint package. 323 // 324 // The checkpoint package consists of 4 strings: 325 // result[0], hex encoded latest section index 326 // result[1], 32 bytes hex encoded latest section head hash 327 // result[2], 32 bytes hex encoded latest section canonical hash trie root hash 328 // result[3], 32 bytes hex encoded latest section bloom trie root hash 329 func (api *PrivateLightAPI) LatestCheckpoint() ([4]string, error) { 330 var res [4]string 331 cp := api.backend.latestLocalCheckpoint() 332 if cp.Empty() { 333 return res, errNoCheckpoint 334 } 335 res[0] = hexutil.EncodeUint64(cp.SectionIndex) 336 res[1], res[2], res[3] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex() 337 return res, nil 338 } 339 340 // GetLocalCheckpoint returns the specific local checkpoint package. 341 // 342 // The checkpoint package consists of 3 strings: 343 // result[0], 32 bytes hex encoded latest section head hash 344 // result[1], 32 bytes hex encoded latest section canonical hash trie root hash 345 // result[2], 32 bytes hex encoded latest section bloom trie root hash 346 func (api *PrivateLightAPI) GetCheckpoint(index uint64) ([3]string, error) { 347 var res [3]string 348 cp := api.backend.localCheckpoint(index) 349 if cp.Empty() { 350 return res, errNoCheckpoint 351 } 352 res[0], res[1], res[2] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex() 353 return res, nil 354 } 355 356 // GetCheckpointContractAddress returns the contract contract address in hex format. 357 func (api *PrivateLightAPI) GetCheckpointContractAddress() (string, error) { 358 if api.backend.oracle == nil { 359 return "", errNotActivated 360 } 361 return api.backend.oracle.Contract().ContractAddr().Hex(), nil 362 }