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  }