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.
This commit is contained in:
@ -94,7 +94,7 @@ type nodeHistoryEnc struct {
|
||||
type queryFunc func(*enode.Node) int
|
||||
|
||||
var (
|
||||
clientSetup = &nodestate.Setup{Version: 1}
|
||||
clientSetup = &nodestate.Setup{Version: 2}
|
||||
sfHasValue = clientSetup.NewPersistentFlag("hasValue")
|
||||
sfQueried = clientSetup.NewFlag("queried")
|
||||
sfCanDial = clientSetup.NewFlag("canDial")
|
||||
@ -131,9 +131,25 @@ var (
|
||||
)
|
||||
sfiNodeWeight = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0)))
|
||||
sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{}))
|
||||
sfiLocalAddress = clientSetup.NewPersistentField("localAddress", reflect.TypeOf(&enr.Record{}),
|
||||
func(field interface{}) ([]byte, error) {
|
||||
if enr, ok := field.(*enr.Record); ok {
|
||||
enc, err := rlp.EncodeToBytes(enr)
|
||||
return enc, err
|
||||
}
|
||||
return nil, errors.New("invalid field type")
|
||||
},
|
||||
func(enc []byte) (interface{}, error) {
|
||||
var enr enr.Record
|
||||
if err := rlp.DecodeBytes(enc, &enr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &enr, nil
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
// newServerPool creates a new server pool
|
||||
// NewServerPool creates a new server pool
|
||||
func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) {
|
||||
s := &ServerPool{
|
||||
db: db,
|
||||
@ -151,15 +167,10 @@ func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duratio
|
||||
s.mixSources = append(s.mixSources, knownSelector)
|
||||
s.mixSources = append(s.mixSources, alwaysConnect)
|
||||
|
||||
iter := enode.Iterator(s.mixer)
|
||||
s.dialIterator = s.mixer
|
||||
if query != nil {
|
||||
iter = s.addPreNegFilter(iter, query)
|
||||
s.dialIterator = s.addPreNegFilter(s.dialIterator, query)
|
||||
}
|
||||
s.dialIterator = enode.Filter(iter, func(node *enode.Node) bool {
|
||||
s.ns.SetState(node, sfDialing, sfCanDial, 0)
|
||||
s.ns.SetState(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10)
|
||||
return true
|
||||
})
|
||||
|
||||
s.ns.SubscribeState(nodestate.MergeFlags(sfWaitDialTimeout, sfConnected), func(n *enode.Node, oldState, newState nodestate.Flags) {
|
||||
if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() {
|
||||
@ -169,7 +180,41 @@ func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duratio
|
||||
}
|
||||
})
|
||||
|
||||
return s, s.dialIterator
|
||||
return s, &serverPoolIterator{
|
||||
dialIterator: s.dialIterator,
|
||||
nextFn: func(node *enode.Node) {
|
||||
s.ns.Operation(func() {
|
||||
s.ns.SetStateSub(node, sfDialing, sfCanDial, 0)
|
||||
s.ns.SetStateSub(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10)
|
||||
})
|
||||
},
|
||||
nodeFn: s.DialNode,
|
||||
}
|
||||
}
|
||||
|
||||
type serverPoolIterator struct {
|
||||
dialIterator enode.Iterator
|
||||
nextFn func(*enode.Node)
|
||||
nodeFn func(*enode.Node) *enode.Node
|
||||
}
|
||||
|
||||
// Next implements enode.Iterator
|
||||
func (s *serverPoolIterator) Next() bool {
|
||||
if s.dialIterator.Next() {
|
||||
s.nextFn(s.dialIterator.Node())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Node implements enode.Iterator
|
||||
func (s *serverPoolIterator) Node() *enode.Node {
|
||||
return s.nodeFn(s.dialIterator.Node())
|
||||
}
|
||||
|
||||
// Close implements enode.Iterator
|
||||
func (s *serverPoolIterator) Close() {
|
||||
s.dialIterator.Close()
|
||||
}
|
||||
|
||||
// AddMetrics adds metrics to the server pool. Should be called before Start().
|
||||
@ -285,7 +330,6 @@ func (s *ServerPool) Start() {
|
||||
|
||||
// stop stops the server pool
|
||||
func (s *ServerPool) Stop() {
|
||||
s.dialIterator.Close()
|
||||
if s.fillSet != nil {
|
||||
s.fillSet.Close()
|
||||
}
|
||||
@ -299,18 +343,23 @@ func (s *ServerPool) Stop() {
|
||||
s.vt.Stop()
|
||||
}
|
||||
|
||||
// registerPeer implements serverPeerSubscriber
|
||||
// RegisterNode implements serverPeerSubscriber
|
||||
func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) {
|
||||
if atomic.LoadUint32(&s.started) == 0 {
|
||||
return nil, errors.New("server pool not started yet")
|
||||
}
|
||||
s.ns.SetState(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0)
|
||||
nvt := s.vt.Register(node.ID())
|
||||
s.ns.SetField(node, sfiConnectedStats, nvt.RtStats())
|
||||
s.ns.Operation(func() {
|
||||
s.ns.SetStateSub(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0)
|
||||
s.ns.SetFieldSub(node, sfiConnectedStats, nvt.RtStats())
|
||||
if node.IP().IsLoopback() {
|
||||
s.ns.SetFieldSub(node, sfiLocalAddress, node.Record())
|
||||
}
|
||||
})
|
||||
return nvt, nil
|
||||
}
|
||||
|
||||
// unregisterPeer implements serverPeerSubscriber
|
||||
// UnregisterNode implements serverPeerSubscriber
|
||||
func (s *ServerPool) UnregisterNode(node *enode.Node) {
|
||||
s.ns.Operation(func() {
|
||||
s.setRedialWait(node, dialCost, dialWaitStep)
|
||||
@ -430,6 +479,7 @@ func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDia
|
||||
s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0)
|
||||
s.ns.SetFieldSub(node, sfiNodeWeight, nil)
|
||||
s.ns.SetFieldSub(node, sfiNodeHistory, nil)
|
||||
s.ns.SetFieldSub(node, sfiLocalAddress, nil)
|
||||
}
|
||||
s.ns.Persist(node) // saved if node history or hasValue changed
|
||||
}
|
||||
@ -520,3 +570,28 @@ func (s *ServerPool) calculateWeight(node *enode.Node) {
|
||||
func (s *ServerPool) API() *PrivateClientAPI {
|
||||
return NewPrivateClientAPI(s.vt)
|
||||
}
|
||||
|
||||
type dummyIdentity enode.ID
|
||||
|
||||
func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil }
|
||||
func (id dummyIdentity) NodeAddr(r *enr.Record) []byte { return id[:] }
|
||||
|
||||
// DialNode replaces the given enode with a locally generated one containing the ENR
|
||||
// stored in the sfiLocalAddress field if present. This workaround ensures that nodes
|
||||
// on the local network can be dialed at the local address if a connection has been
|
||||
// successfully established previously.
|
||||
// Note that NodeStateMachine always remembers the enode with the latest version of
|
||||
// the remote signed ENR. ENR filtering should be performed on that version while
|
||||
// dialNode should be used for dialing the node over TCP or UDP.
|
||||
func (s *ServerPool) DialNode(n *enode.Node) *enode.Node {
|
||||
if enr, ok := s.ns.GetField(n, sfiLocalAddress).(*enr.Record); ok {
|
||||
n, _ := enode.New(dummyIdentity(n.ID()), enr)
|
||||
return n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Persist immediately stores the state of a node in the node database
|
||||
func (s *ServerPool) Persist(n *enode.Node) {
|
||||
s.ns.Persist(n)
|
||||
}
|
||||
|
Reference in New Issue
Block a user