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