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