les: implement client connection logic (#16899)
This PR implements les.freeClientPool. It also adds a simulated clock in common/mclock, which enables time-sensitive tests to run quickly and still produce accurate results, and package common/prque which is a generalised variant of prque that enables removing elements other than the top one from the queue. les.freeClientPool implements a client database that limits the connection time of each client and manages accepting/rejecting incoming connections and even kicking out some connected clients. The pool calculates recent usage time for each known client (a value that increases linearly when the client is connected and decreases exponentially when not connected). Clients with lower recent usage are preferred, unknown nodes have the highest priority. Already connected nodes receive a small bias in their favor in order to avoid accepting and instantly kicking out clients. Note: the pool can use any string for client identification. Using signature keys for that purpose would not make sense when being known has a negative value for the client. Currently the LES protocol manager uses IP addresses (without port address) to identify clients.
This commit is contained in:
committed by
Felix Lange
parent
a1783d1697
commit
b2ddb1fcbf
@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
@ -104,6 +105,7 @@ type ProtocolManager struct {
|
||||
odr *LesOdr
|
||||
server *LesServer
|
||||
serverPool *serverPool
|
||||
clientPool *freeClientPool
|
||||
lesTopic discv5.Topic
|
||||
reqDist *requestDistributor
|
||||
retriever *retrieveManager
|
||||
@ -226,6 +228,7 @@ func (pm *ProtocolManager) Start(maxPeers int) {
|
||||
if pm.lightSync {
|
||||
go pm.syncer()
|
||||
} else {
|
||||
pm.clientPool = newFreeClientPool(pm.chainDb, maxPeers, 10000, mclock.System{})
|
||||
go func() {
|
||||
for range pm.newPeerCh {
|
||||
}
|
||||
@ -243,6 +246,9 @@ func (pm *ProtocolManager) Stop() {
|
||||
pm.noMorePeers <- struct{}{}
|
||||
|
||||
close(pm.quitSync) // quits syncer, fetcher
|
||||
if pm.clientPool != nil {
|
||||
pm.clientPool.stop()
|
||||
}
|
||||
|
||||
// Disconnect existing sessions.
|
||||
// This also closes the gate for any new registrations on the peer set.
|
||||
@ -264,7 +270,8 @@ func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgRea
|
||||
// this function terminates, the peer is disconnected.
|
||||
func (pm *ProtocolManager) handle(p *peer) error {
|
||||
// Ignore maxPeers if this is a trusted peer
|
||||
if pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted {
|
||||
// In server mode we try to check into the client pool after handshake
|
||||
if pm.lightSync && pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted {
|
||||
return p2p.DiscTooManyPeers
|
||||
}
|
||||
|
||||
@ -282,6 +289,19 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||
p.Log().Debug("Light Ethereum handshake failed", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !pm.lightSync && !p.Peer.Info().Network.Trusted {
|
||||
addr, ok := p.RemoteAddr().(*net.TCPAddr)
|
||||
// test peer address is not a tcp address, don't use client pool if can not typecast
|
||||
if ok {
|
||||
id := addr.IP.String()
|
||||
if !pm.clientPool.connect(id, func() { go pm.removePeer(p.id) }) {
|
||||
return p2p.DiscTooManyPeers
|
||||
}
|
||||
defer pm.clientPool.disconnect(id)
|
||||
}
|
||||
}
|
||||
|
||||
if rw, ok := p.rw.(*meteredMsgReadWriter); ok {
|
||||
rw.Init(p.version)
|
||||
}
|
||||
|
Reference in New Issue
Block a user