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