mirror of https://github.com/ethereum/go-ethereum
les: UDP pre-negotiation of available server capacity (#22183)
This PR implements the first one of the "lespay" UDP queries which is already useful in itself: the capacity query. The server pool is making use of this query by doing a cheap UDP query to determine whether it is worth starting the more expensive TCP connection process.pull/22399/head
parent
498458b410
commit
d96870428f
@ -0,0 +1,180 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vflux |
||||
|
||||
import ( |
||||
"errors" |
||||
"math" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
var ErrNoReply = errors.New("no reply for given request") |
||||
|
||||
const ( |
||||
MaxRequestLength = 16 // max number of individual requests in a batch
|
||||
CapacityQueryName = "cq" |
||||
CapacityQueryMaxLen = 16 |
||||
) |
||||
|
||||
type ( |
||||
// Request describes a single vflux request inside a batch. Service and request
|
||||
// type are identified by strings, parameters are RLP encoded.
|
||||
Request struct { |
||||
Service, Name string |
||||
Params []byte |
||||
} |
||||
// Requests are a batch of vflux requests
|
||||
Requests []Request |
||||
|
||||
// Replies are the replies to a batch of requests
|
||||
Replies [][]byte |
||||
|
||||
// CapacityQueryReq is the encoding format of the capacity query
|
||||
CapacityQueryReq struct { |
||||
Bias uint64 // seconds
|
||||
AddTokens []IntOrInf |
||||
} |
||||
// CapacityQueryReq is the encoding format of the response to the capacity query
|
||||
CapacityQueryReply []uint64 |
||||
) |
||||
|
||||
// Add encodes and adds a new request to the batch
|
||||
func (r *Requests) Add(service, name string, val interface{}) (int, error) { |
||||
enc, err := rlp.EncodeToBytes(val) |
||||
if err != nil { |
||||
return -1, err |
||||
} |
||||
*r = append(*r, Request{ |
||||
Service: service, |
||||
Name: name, |
||||
Params: enc, |
||||
}) |
||||
return len(*r) - 1, nil |
||||
} |
||||
|
||||
// Get decodes the reply to the i-th request in the batch
|
||||
func (r Replies) Get(i int, val interface{}) error { |
||||
if i < 0 || i >= len(r) { |
||||
return ErrNoReply |
||||
} |
||||
return rlp.DecodeBytes(r[i], val) |
||||
} |
||||
|
||||
const ( |
||||
IntNonNegative = iota |
||||
IntNegative |
||||
IntPlusInf |
||||
IntMinusInf |
||||
) |
||||
|
||||
// IntOrInf is the encoding format for arbitrary length signed integers that can also
|
||||
// hold the values of +Inf or -Inf
|
||||
type IntOrInf struct { |
||||
Type uint8 |
||||
Value big.Int |
||||
} |
||||
|
||||
// BigInt returns the value as a big.Int or panics if the value is infinity
|
||||
func (i *IntOrInf) BigInt() *big.Int { |
||||
switch i.Type { |
||||
case IntNonNegative: |
||||
return new(big.Int).Set(&i.Value) |
||||
case IntNegative: |
||||
return new(big.Int).Neg(&i.Value) |
||||
case IntPlusInf: |
||||
panic(nil) // caller should check Inf() before trying to convert to big.Int
|
||||
case IntMinusInf: |
||||
panic(nil) |
||||
} |
||||
return &big.Int{} // invalid type decodes to 0 value
|
||||
} |
||||
|
||||
// Inf returns 1 if the value is +Inf, -1 if it is -Inf, 0 otherwise
|
||||
func (i *IntOrInf) Inf() int { |
||||
switch i.Type { |
||||
case IntPlusInf: |
||||
return 1 |
||||
case IntMinusInf: |
||||
return -1 |
||||
} |
||||
return 0 // invalid type decodes to 0 value
|
||||
} |
||||
|
||||
// Int64 limits the value between MinInt64 and MaxInt64 (even if it is +-Inf) and returns an int64 type
|
||||
func (i *IntOrInf) Int64() int64 { |
||||
switch i.Type { |
||||
case IntNonNegative: |
||||
if i.Value.IsInt64() { |
||||
return i.Value.Int64() |
||||
} else { |
||||
return math.MaxInt64 |
||||
} |
||||
case IntNegative: |
||||
if i.Value.IsInt64() { |
||||
return -i.Value.Int64() |
||||
} else { |
||||
return math.MinInt64 |
||||
} |
||||
case IntPlusInf: |
||||
return math.MaxInt64 |
||||
case IntMinusInf: |
||||
return math.MinInt64 |
||||
} |
||||
return 0 // invalid type decodes to 0 value
|
||||
} |
||||
|
||||
// SetBigInt sets the value to the given big.Int
|
||||
func (i *IntOrInf) SetBigInt(v *big.Int) { |
||||
if v.Sign() >= 0 { |
||||
i.Type = IntNonNegative |
||||
i.Value.Set(v) |
||||
} else { |
||||
i.Type = IntNegative |
||||
i.Value.Neg(v) |
||||
} |
||||
} |
||||
|
||||
// SetInt64 sets the value to the given int64. Note that MaxInt64 translates to +Inf
|
||||
// while MinInt64 translates to -Inf.
|
||||
func (i *IntOrInf) SetInt64(v int64) { |
||||
if v >= 0 { |
||||
if v == math.MaxInt64 { |
||||
i.Type = IntPlusInf |
||||
} else { |
||||
i.Type = IntNonNegative |
||||
i.Value.SetInt64(v) |
||||
} |
||||
} else { |
||||
if v == math.MinInt64 { |
||||
i.Type = IntMinusInf |
||||
} else { |
||||
i.Type = IntNegative |
||||
i.Value.SetInt64(-v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// SetInf sets the value to +Inf or -Inf
|
||||
func (i *IntOrInf) SetInf(sign int) { |
||||
if sign == 1 { |
||||
i.Type = IntPlusInf |
||||
} else { |
||||
i.Type = IntMinusInf |
||||
} |
||||
} |
@ -0,0 +1,122 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package server |
||||
|
||||
import ( |
||||
"net" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/les/utils" |
||||
"github.com/ethereum/go-ethereum/les/vflux" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
"github.com/ethereum/go-ethereum/p2p/enode" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
) |
||||
|
||||
type ( |
||||
// Server serves vflux requests
|
||||
Server struct { |
||||
limiter *utils.Limiter |
||||
lock sync.Mutex |
||||
services map[string]*serviceEntry |
||||
delayPerRequest time.Duration |
||||
} |
||||
|
||||
// Service is a service registered at the Server and identified by a string id
|
||||
Service interface { |
||||
ServiceInfo() (id, desc string) // only called during registration
|
||||
Handle(id enode.ID, address string, name string, data []byte) []byte // never called concurrently
|
||||
} |
||||
|
||||
serviceEntry struct { |
||||
id, desc string |
||||
backend Service |
||||
} |
||||
) |
||||
|
||||
// NewServer creates a new Server
|
||||
func NewServer(delayPerRequest time.Duration) *Server { |
||||
return &Server{ |
||||
limiter: utils.NewLimiter(1000), |
||||
delayPerRequest: delayPerRequest, |
||||
services: make(map[string]*serviceEntry), |
||||
} |
||||
} |
||||
|
||||
// Register registers a Service
|
||||
func (s *Server) Register(b Service) { |
||||
srv := &serviceEntry{backend: b} |
||||
srv.id, srv.desc = b.ServiceInfo() |
||||
if strings.Contains(srv.id, ":") { |
||||
// srv.id + ":" will be used as a service database prefix
|
||||
log.Error("Service ID contains ':'", "id", srv.id) |
||||
return |
||||
} |
||||
s.lock.Lock() |
||||
s.services[srv.id] = srv |
||||
s.lock.Unlock() |
||||
} |
||||
|
||||
// Serve serves a vflux request batch
|
||||
// Note: requests are served by the Handle functions of the registered services. Serve
|
||||
// may be called concurrently but the Handle functions are called sequentially and
|
||||
// therefore thread safety is guaranteed.
|
||||
func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vflux.Replies { |
||||
reqLen := uint(len(requests)) |
||||
if reqLen == 0 || reqLen > vflux.MaxRequestLength { |
||||
return nil |
||||
} |
||||
// Note: the value parameter will be supplied by the token sale module (total amount paid)
|
||||
ch := <-s.limiter.Add(id, address, 0, reqLen) |
||||
if ch == nil { |
||||
return nil |
||||
} |
||||
// Note: the limiter ensures that the following section is not running concurrently,
|
||||
// the lock only protects against contention caused by new service registration
|
||||
s.lock.Lock() |
||||
results := make(vflux.Replies, len(requests)) |
||||
for i, req := range requests { |
||||
if service := s.services[req.Service]; service != nil { |
||||
results[i] = service.backend.Handle(id, address, req.Name, req.Params) |
||||
} |
||||
} |
||||
s.lock.Unlock() |
||||
time.Sleep(s.delayPerRequest * time.Duration(reqLen)) |
||||
close(ch) |
||||
return results |
||||
} |
||||
|
||||
// ServeEncoded serves an encoded vflux request batch and returns the encoded replies
|
||||
func (s *Server) ServeEncoded(id enode.ID, addr *net.UDPAddr, req []byte) []byte { |
||||
var requests vflux.Requests |
||||
if err := rlp.DecodeBytes(req, &requests); err != nil { |
||||
return nil |
||||
} |
||||
results := s.Serve(id, addr.String(), requests) |
||||
if results == nil { |
||||
return nil |
||||
} |
||||
res, _ := rlp.EncodeToBytes(&results) |
||||
return res |
||||
} |
||||
|
||||
// Stop shuts down the server
|
||||
func (s *Server) Stop() { |
||||
s.limiter.Stop() |
||||
} |
Loading…
Reference in new issue