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