les: implement inactive peer state

This commit is contained in:
Zsolt Felfoldi 2019-12-20 18:47:46 +01:00
parent 21f760b68e
commit 0fc06ea8af
14 changed files with 600 additions and 306 deletions

View file

@ -68,6 +68,7 @@ type balanceCallback struct {
} }
// init initializes balanceTracker // init initializes balanceTracker
// Note: capacity should never be zero
func (bt *balanceTracker) init(clock mclock.Clock, capacity uint64) { func (bt *balanceTracker) init(clock mclock.Clock, capacity uint64) {
bt.clock = clock bt.clock = clock
bt.initTime, bt.lastUpdate = clock.Now(), clock.Now() // Init timestamps bt.initTime, bt.lastUpdate = clock.Now(), clock.Now() // Init timestamps
@ -99,7 +100,7 @@ func (bt *balanceTracker) stop(now mclock.AbsTime) {
// balance is zero then negative balance translates to a positive priority. // balance is zero then negative balance translates to a positive priority.
func (bt *balanceTracker) balanceToPriority(b balance) int64 { func (bt *balanceTracker) balanceToPriority(b balance) int64 {
if b.pos > 0 { if b.pos > 0 {
return ^int64(b.pos / bt.capacity) return -int64(b.pos / bt.capacity)
} }
return int64(b.neg) return int64(b.neg)
} }
@ -107,7 +108,7 @@ func (bt *balanceTracker) balanceToPriority(b balance) int64 {
func (bt *balanceTracker) posBalanceMissing(targetPriority int64, targetCapacity uint64, after time.Duration) uint64 { func (bt *balanceTracker) posBalanceMissing(targetPriority int64, targetCapacity uint64, after time.Duration) uint64 {
if targetPriority > 0 { if targetPriority > 0 {
negPrice := uint64(float64(after) * bt.negTimeFactor) negPrice := uint64(float64(after) * bt.negTimeFactor)
if negPrice+bt.balance.neg <= uint64(targetPriority) { if negPrice+bt.balance.neg < uint64(targetPriority) {
return 0 return 0
} }
if uint64(targetPriority) > bt.balance.neg && bt.negTimeFactor > 1e-100 { if uint64(targetPriority) > bt.balance.neg && bt.negTimeFactor > 1e-100 {
@ -119,7 +120,7 @@ func (bt *balanceTracker) posBalanceMissing(targetPriority int64, targetCapacity
} }
targetPriority = 0 targetPriority = 0
} }
posRequired := uint64(float64(^targetPriority)*float64(targetCapacity) + float64(after)*bt.timeFactor) posRequired := uint64(float64(-targetPriority)*float64(targetCapacity)+float64(after)*bt.timeFactor) + 1
if posRequired >= maxBalance { if posRequired >= maxBalance {
return math.MaxUint64 // target not reachable return math.MaxUint64 // target not reachable
} }
@ -164,7 +165,7 @@ func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) {
return 0, false return 0, false
} }
if priority < 0 { if priority < 0 {
newBalance := uint64(^priority) * bt.capacity newBalance := uint64(-priority) * bt.capacity
if newBalance > bt.balance.pos { if newBalance > bt.balance.pos {
return 0, false return 0, false
} }
@ -189,6 +190,7 @@ func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) {
} }
// setCapacity updates the capacity value used for priority calculation // setCapacity updates the capacity value used for priority calculation
// Note: capacity should never be zero
func (bt *balanceTracker) setCapacity(capacity uint64) { func (bt *balanceTracker) setCapacity(capacity uint64) {
bt.lock.Lock() bt.lock.Lock()
defer bt.lock.Unlock() defer bt.lock.Unlock()

View file

@ -141,8 +141,8 @@ func TestBalanceToPriority(t *testing.T) {
neg uint64 neg uint64
priority int64 priority int64
}{ }{
{1000, 0, ^int64(1)}, {1000, 0, -1},
{2000, 0, ^int64(2)}, // Higher balance, lower priority value {2000, 0, -2}, // Higher balance, lower priority value
{0, 0, 0}, {0, 0, 0},
{0, 1000, 1000}, {0, 1000, 1000},
} }
@ -172,16 +172,16 @@ func TestEstimatedPriority(t *testing.T) {
reqCost uint64 // single request cost reqCost uint64 // single request cost
priority int64 // expected estimated priority priority int64 // expected estimated priority
}{ }{
{time.Second, time.Second, 0, ^int64(58)}, {time.Second, time.Second, 0, -58},
{0, time.Second, 0, ^int64(58)}, {0, time.Second, 0, -58},
// 2 seconds time cost, 1 second estimated time cost, 10^9 request cost, // 2 seconds time cost, 1 second estimated time cost, 10^9 request cost,
// 10^9 estimated request cost per second. // 10^9 estimated request cost per second.
{time.Second, time.Second, 1000000000, ^int64(55)}, {time.Second, time.Second, 1000000000, -55},
// 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost, // 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost,
// 4*10^9 estimated request cost. // 4*10^9 estimated request cost.
{time.Second, 3 * time.Second, 1000000000, ^int64(48)}, {time.Second, 3 * time.Second, 1000000000, -48},
// All positive balance is used up // All positive balance is used up
{time.Second * 55, 0, 0, 0}, {time.Second * 55, 0, 0, 0},
@ -213,7 +213,7 @@ func TestCallbackChecking(t *testing.T) {
priority int64 priority int64
expDiff time.Duration expDiff time.Duration
}{ }{
{^int64(500), time.Millisecond * 500}, {-500, time.Millisecond * 500},
{0, time.Second}, {0, time.Second},
{int64(time.Second), 2 * time.Second}, {int64(time.Second), 2 * time.Second},
} }

View file

@ -116,28 +116,48 @@ func (h *clientHandler) handle(p *peer) error {
p.Log().Debug("Light Ethereum handshake failed", "err", err) p.Log().Debug("Light Ethereum handshake failed", "err", err)
return err return err
} }
var (
connectedAt mclock.AbsTime
lastActive bool
)
activate := func() {
// Register the peer locally // Register the peer locally
if err := h.backend.peers.Register(p); err != nil { if err := h.backend.peers.Register(p); err != nil {
p.Log().Error("Light Ethereum peer registration failed", "err", err) p.Log().Error("Light Ethereum peer registration failed", "err", err)
return err return
} }
serverConnectionGauge.Update(int64(h.backend.peers.Len())) serverConnectionGauge.Update(int64(h.backend.peers.Len()))
connectedAt = mclock.Now()
connectedAt := mclock.Now() h.fetcher.announce(p, p.headInfo)
defer func() { lastActive = true
h.backend.peers.Unregister(p.id) }
deactivate := func() {
h.backend.peers.Unregister(p)
connectionTimer.Update(time.Duration(mclock.Now() - connectedAt)) connectionTimer.Update(time.Duration(mclock.Now() - connectedAt))
serverConnectionGauge.Update(int64(h.backend.peers.Len())) serverConnectionGauge.Update(int64(h.backend.peers.Len()))
lastActive = false
}
defer func() {
if lastActive {
deactivate()
}
h.backend.peers.Disconnect(p.id)
}() }()
h.fetcher.announce(p, p.headInfo)
// pool entry can be nil during the unit test. // pool entry can be nil during the unit test.
if p.poolEntry != nil { if p.poolEntry != nil {
h.backend.serverPool.registered(p.poolEntry) h.backend.serverPool.registered(p.poolEntry)
} }
// Spawn a main loop to handle all incoming messages. // Spawn a main loop to handle all incoming messages.
for { for {
if p.active && !lastActive {
activate()
}
if !p.active && lastActive {
deactivate()
}
if err := h.handleMsg(p); err != nil { if err := h.handleMsg(p); err != nil {
p.Log().Debug("Light Ethereum message handling failed", "err", err) p.Log().Debug("Light Ethereum message handling failed", "err", err)
p.fcServer.DumpLogs() p.fcServer.DumpLogs()
@ -400,7 +420,7 @@ func (h *clientHandler) makeLespayCall(p *peer, cmd []byte, handler func([]byte)
} }
func (h *clientHandler) removePeer(id string) { func (h *clientHandler) removePeer(id string) {
h.backend.peers.Unregister(id) h.backend.peers.Disconnect(id)
} }
type peerConnection struct { type peerConnection struct {

View file

@ -39,19 +39,20 @@ const (
negBalanceExpTC = time.Hour // time constant for exponentially reducing negative balance negBalanceExpTC = time.Hour // time constant for exponentially reducing negative balance
fixedPointMultiplier = 0x1000000 // constant to convert logarithms to fixed point format fixedPointMultiplier = 0x1000000 // constant to convert logarithms to fixed point format
lazyQueueRefresh = time.Second * 10 // refresh period of the connected queue lazyQueueRefresh = time.Second * 10 // refresh period of the connected queue
tryActivatePeriod = time.Second * 5 // periodically check whether inactive clients can be activated
persistCumulativeTimeRefresh = time.Minute * 5 // refresh period of the cumulative running time persistence persistCumulativeTimeRefresh = time.Minute * 5 // refresh period of the cumulative running time persistence
posBalanceCacheLimit = 8192 // the maximum number of cached items in positive balance queue posBalanceCacheLimit = 8192 // the maximum number of cached items in positive balance queue
negBalanceCacheLimit = 8192 // the maximum number of cached items in negative balance queue negBalanceCacheLimit = 8192 // the maximum number of cached items in negative balance queue
fullRatioTC = time.Hour fullRatioTC = time.Hour
// connectedBias is applied to already connected clients So that // activeBias is applied to already connected clients So that
// already connected client won't be kicked out very soon and we // already connected client won't be kicked out very soon and we
// can ensure all connected clients can have enough time to request // can ensure all connected clients can have enough time to request
// or sync some data. // or sync some data.
// //
// todo(rjl493456442) make it configurable. It can be the option of // todo(rjl493456442) make it configurable. It can be the option of
// free trial time! // free trial time!
connectedBias = time.Minute * 3 activeBias = time.Minute * 3
) )
// clientPool implements a client database that assigns a priority to each client // clientPool implements a client database that assigns a priority to each client
@ -61,7 +62,7 @@ const (
// then negative balance is accumulated. // then negative balance is accumulated.
// //
// Balance tracking and priority calculation for connected clients is done by // Balance tracking and priority calculation for connected clients is done by
// balanceTracker. connectedQueue ensures that clients with the lowest positive or // balanceTracker. activeQueue ensures that clients with the lowest positive or
// highest negative balance get evicted when the total capacity allowance is full // highest negative balance get evicted when the total capacity allowance is full
// and new clients with a better balance want to connect. // and new clients with a better balance want to connect.
// //
@ -84,18 +85,19 @@ type clientPool struct {
removePeer func(enode.ID) removePeer func(enode.ID)
connectedMap map[enode.ID]*clientInfo connectedMap map[enode.ID]*clientInfo
connectedQueue *prque.LazyQueue activeQueue *prque.LazyQueue
inactiveQueue *prque.Prque
connectedBalances, disconnectedBalances uint64 activeBalances, inactiveBalances uint64
lastConnectedBalanceUpdate, fullRatioLastUpdate mclock.AbsTime lastConnectedBalanceUpdate, fullRatioLastUpdate mclock.AbsTime
fullRatio float64 fullRatio float64
defaultPosFactors, defaultNegFactors priceFactors defaultPosFactors, defaultNegFactors priceFactors
connLimit int // The maximum number of connections that clientpool can support activeLimit int // The maximum number of connections that clientpool can support
capLimit uint64 // The maximum cumulative capacity that clientpool can support capLimit uint64 // The maximum cumulative capacity that clientpool can support
connectedCap uint64 // The sum of the capacity of the current clientpool connected activeCap uint64 // The sum of the capacity of the current clientpool connected
priorityConnected uint64 // The sum of the capacity of currently connected priority clients priorityActive uint64 // The sum of the capacity of currently connected priority clients
minCap uint64 // The minimal capacity value allowed for any client minCap uint64 // The minimal capacity value allowed for any client
freeClientCap uint64 // The capacity value of each free client freeClientCap uint64 // The capacity value of each free client
startTime mclock.AbsTime // The timestamp at which the clientpool started running startTime mclock.AbsTime // The timestamp at which the clientpool started running
@ -120,36 +122,37 @@ type clientInfo struct {
address string address string
id enode.ID id enode.ID
freeID string freeID string
active bool
connectedAt mclock.AbsTime connectedAt mclock.AbsTime
capacity uint64 capacity uint64
priority bool priority bool
pool *clientPool pool *clientPool
peer clientPeer peer clientPeer
queueIndex int // position in connectedQueue queueIndex int // position in activeQueue
balanceTracker balanceTracker balanceTracker balanceTracker
posFactors, negFactors priceFactors posFactors, negFactors priceFactors
balanceMetaInfo string balanceMetaInfo string
} }
// connSetIndex callback updates clientInfo item index in connectedQueue // connSetIndex callback updates clientInfo item index in activeQueue
func connSetIndex(a interface{}, index int) { func connSetIndex(a interface{}, index int) {
a.(*clientInfo).queueIndex = index a.(*clientInfo).queueIndex = index
} }
// connPriority callback returns actual priority of clientInfo item in connectedQueue // connPriority callback returns actual priority of clientInfo item in activeQueue
func connPriority(a interface{}, now mclock.AbsTime) int64 { func connPriority(a interface{}, now mclock.AbsTime) int64 {
c := a.(*clientInfo) c := a.(*clientInfo)
return c.balanceTracker.getPriority(now) return c.balanceTracker.getPriority(now)
} }
// connMaxPriority callback returns estimated maximum priority of clientInfo item in connectedQueue // connMaxPriority callback returns estimated maximum priority of clientInfo item in activeQueue
func connMaxPriority(a interface{}, until mclock.AbsTime) int64 { func connMaxPriority(a interface{}, until mclock.AbsTime) int64 {
c := a.(*clientInfo) c := a.(*clientInfo)
pri := c.balanceTracker.estimatedPriority(until, true) pri := c.balanceTracker.estimatedPriority(until, true)
c.balanceTracker.addCallback(balanceCallbackQueue, pri+1, func() { c.balanceTracker.addCallback(balanceCallbackQueue, pri+1, func() {
c.pool.lock.Lock() c.pool.lock.Lock()
if c.queueIndex != -1 { if c.active && c.queueIndex != -1 {
c.pool.connectedQueue.Update(c.queueIndex) c.pool.activeQueue.Update(c.queueIndex)
} }
c.pool.lock.Unlock() c.pool.lock.Unlock()
}) })
@ -172,7 +175,8 @@ func newClientPool(db ethdb.Database, minCap, freeClientCap uint64, clock mclock
ndb: ndb, ndb: ndb,
clock: clock, clock: clock,
connectedMap: make(map[enode.ID]*clientInfo), connectedMap: make(map[enode.ID]*clientInfo),
connectedQueue: prque.NewLazyQueue(connSetIndex, connPriority, connMaxPriority, clock, lazyQueueRefresh), activeQueue: prque.NewLazyQueue(connSetIndex, connPriority, connMaxPriority, clock, lazyQueueRefresh),
inactiveQueue: prque.New(connSetIndex),
minCap: minCap, minCap: minCap,
freeClientCap: freeClientCap, freeClientCap: freeClientCap,
removePeer: removePeer, removePeer: removePeer,
@ -193,7 +197,7 @@ func newClientPool(db ethdb.Database, minCap, freeClientCap uint64, clock mclock
stop = true stop = true
} }
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
pool.disconnectedBalances += pool.ndb.getOrNewPB(ids[i]).value pool.inactiveBalances += pool.ndb.getOrNewPB(ids[i]).value
} }
if stop { if stop {
break break
@ -210,8 +214,16 @@ func newClientPool(db ethdb.Database, minCap, freeClientCap uint64, clock mclock
select { select {
case <-clock.After(lazyQueueRefresh): case <-clock.After(lazyQueueRefresh):
pool.lock.Lock() pool.lock.Lock()
pool.connectedQueue.Refresh() pool.activeQueue.Refresh()
pool.lock.Unlock() pool.lock.Unlock()
case <-pool.stopCh:
return
}
}
}()
go func() {
for {
select {
case <-clock.After(persistCumulativeTimeRefresh): case <-clock.After(persistCumulativeTimeRefresh):
pool.ndb.setCumulativeTime(pool.logOffset(clock.Now())) pool.ndb.setCumulativeTime(pool.logOffset(clock.Now()))
case <-pool.stopCh: case <-pool.stopCh:
@ -219,6 +231,18 @@ func newClientPool(db ethdb.Database, minCap, freeClientCap uint64, clock mclock
} }
} }
}() }()
go func() {
for {
select {
case <-clock.After(tryActivatePeriod):
pool.lock.Lock()
pool.tryActivateClients()
pool.lock.Unlock()
case <-pool.stopCh:
return
}
}
}()
return pool return pool
} }
@ -234,8 +258,8 @@ func (f *clientPool) stop() {
func (f *clientPool) updateFullRatio() { func (f *clientPool) updateFullRatio() {
full := float64(1) full := float64(1)
if f.priorityConnected < f.capLimit { if f.priorityActive < f.capLimit {
freeCap := f.capLimit - f.priorityConnected freeCap := f.capLimit - f.priorityActive
if freeCap > f.freeClientCap { if freeCap > f.freeClientCap {
freeCapThreshold := f.capLimit / 4 freeCapThreshold := f.capLimit / 4
if freeCap > freeCapThreshold { if freeCap > freeCapThreshold {
@ -275,43 +299,37 @@ func (f *clientPool) totalTokenAmount() uint64 {
now := f.clock.Now() now := f.clock.Now()
if now > f.lastConnectedBalanceUpdate+mclock.AbsTime(time.Second) { if now > f.lastConnectedBalanceUpdate+mclock.AbsTime(time.Second) {
f.connectedBalances = 0 f.activeBalances = 0
for _, c := range f.connectedMap { for _, c := range f.connectedMap {
pos, _ := c.balanceTracker.getBalance(now) pos, _ := c.balanceTracker.getBalance(now)
f.connectedBalances += pos f.activeBalances += pos
} }
f.lastConnectedBalanceUpdate = now f.lastConnectedBalanceUpdate = now
} }
return f.connectedBalances + f.disconnectedBalances return f.activeBalances + f.inactiveBalances
} }
// connect should be called after a successful handshake. If the connection was // connect should be called after a successful handshake. If the connection was
// rejected, there is no need to call disconnect. // rejected, there is no need to call disconnect.
func (f *clientPool) connect(peer clientPeer, capacity uint64) bool { func (f *clientPool) connect(peer clientPeer, reqCapacity uint64) (uint64, error) {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
// Short circuit if clientPool is already closed. // Short circuit if clientPool is already closed.
if f.closed { if f.closed {
return false return 0, fmt.Errorf("Client pool is already closed")
} }
// Dedup connected peers. // Dedup connected peers.
id, freeID := peer.ID(), peer.freeClientId() id, freeID := peer.ID(), peer.freeClientId()
if _, ok := f.connectedMap[id]; ok { if _, ok := f.connectedMap[id]; ok {
clientRejectedMeter.Mark(1) clientRejectedMeter.Mark(1)
log.Debug("Client already connected", "address", freeID, "id", peerIdToString(id)) log.Debug("Client already connected", "address", freeID, "id", peerIdToString(id))
return false return 0, fmt.Errorf("Client already connected address = %s id = %s", freeID, peerIdToString(id))
} }
var missing uint64
missing, capacity = f.capAvailable(id, freeID, capacity, 0, true)
if missing != 0 {
return false
}
// capacity is available, create and add client
pb := f.ndb.getOrNewPB(id) pb := f.ndb.getOrNewPB(id)
nb := f.ndb.getOrNewNB(freeID) nb := f.ndb.getOrNewNB(freeID)
e := &clientInfo{ e := &clientInfo{
capacity: capacity, capacity: reqCapacity,
pool: f, pool: f,
peer: peer, peer: peer,
address: freeID, address: freeID,
@ -324,33 +342,38 @@ func (f *clientPool) connect(peer clientPeer, capacity uint64) bool {
negFactors: f.defaultNegFactors, negFactors: f.defaultNegFactors,
balanceMetaInfo: pb.meta, balanceMetaInfo: pb.meta,
} }
f.initBalanceTracker(&e.balanceTracker, pb, nb, capacity) missing, capacity := f.capAvailable(id, freeID, reqCapacity, 0, true)
// Register new client to connection queue.
f.disconnectedBalances -= pb.value
f.connectedBalances += pb.value
f.connectedMap[id] = e f.connectedMap[id] = e
f.connectedQueue.Push(e) if missing != 0 {
f.connectedCap += e.capacity // capacity is not available, add client to inactive queue
f.initBalanceTracker(&e.balanceTracker, pb, nb, capacity, false)
f.inactiveQueue.Push(e, -connPriority(e, f.clock.Now()))
return 0, nil
}
// capacity is available, add client
e.active = true
e.capacity = capacity
f.initBalanceTracker(&e.balanceTracker, pb, nb, capacity, true)
// Register new client to connection queue.
f.inactiveBalances -= pb.value
f.activeBalances += pb.value
f.activeQueue.Push(e)
f.activeCap += e.capacity
// If the current client is a paid client, monitor the status of client, // If the current client is a paid client, monitor the status of client,
// downgrade it to normal client if positive balance is used up. // downgrade it to normal client if positive balance is used up.
if e.priority { if e.priority {
f.updateFullRatio() f.updateFullRatio()
f.priorityConnected += capacity f.priorityActive += capacity
e.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) e.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) })
} }
// If the capacity of client is not the default value(free capacity), notify totalConnectedGauge.Update(int64(f.activeCap))
// it to update capacity.
if e.capacity != f.freeClientCap {
e.peer.updateCapacity(e.capacity)
}
totalConnectedGauge.Update(int64(f.connectedCap))
clientConnectedMeter.Mark(1) clientConnectedMeter.Mark(1)
log.Debug("Client accepted", "address", freeID) log.Debug("Client accepted", "address", freeID)
return true return e.capacity, nil
} }
func (f *clientPool) initBalanceTracker(bt *balanceTracker, pb posBalance, nb negBalance, capacity uint64) { func (f *clientPool) initBalanceTracker(bt *balanceTracker, pb posBalance, nb negBalance, capacity uint64, active bool) {
posBalance := pb.value posBalance := pb.value
var negBalance uint64 var negBalance uint64
if nb.logValue != 0 { if nb.logValue != 0 {
@ -358,7 +381,11 @@ func (f *clientPool) initBalanceTracker(bt *balanceTracker, pb posBalance, nb ne
} }
bt.init(f.clock, capacity) bt.init(f.clock, capacity)
bt.setBalance(posBalance, negBalance) bt.setBalance(posBalance, negBalance)
if active {
updatePriceFactors(bt, f.defaultPosFactors, f.defaultNegFactors, capacity) updatePriceFactors(bt, f.defaultPosFactors, f.defaultNegFactors, capacity)
} else {
zeroPriceFactors(bt)
}
} }
// disconnect should be called when a connection is terminated. If the disconnection // disconnect should be called when a connection is terminated. If the disconnection
@ -372,13 +399,20 @@ func (f *clientPool) disconnect(p clientPeer) {
if f.closed { if f.closed {
return return
} }
// Short circuit if the peer hasn't been registered. e, ok := f.connectedMap[p.ID()]
e := f.connectedMap[p.ID()] if !ok {
if e == nil {
log.Debug("Client not connected", "address", p.freeClientId(), "id", peerIdToString(p.ID())) log.Debug("Client not connected", "address", p.freeClientId(), "id", peerIdToString(p.ID()))
return return
} }
f.dropClient(e, f.clock.Now(), false) if e.active {
f.deactivateClient(e)
}
f.finalizeBalance(e, f.clock.Now())
f.inactiveQueue.Remove(e.queueIndex)
delete(f.connectedMap, e.id)
clientDisconnectedMeter.Mark(1)
log.Debug("Client disconnected", "address", e.address)
f.tryActivateClients()
} }
// capAvailable checks whether the current priority level of the given client is enough to // capAvailable checks whether the current priority level of the given client is enough to
@ -392,19 +426,19 @@ func (f *clientPool) capAvailable(id enode.ID, freeID string, capacity uint64, m
if capacity < f.minCap { if capacity < f.minCap {
capacity = f.minCap capacity = f.minCap
} }
newCapacity := f.connectedCap + capacity newCapacity := f.activeCap + capacity
newCount := f.connectedQueue.Size() + 1 newCount := f.activeQueue.Size() + 1
client := f.connectedMap[id] client := f.connectedMap[id]
if client != nil { if client != nil && client.active {
newCapacity -= client.capacity newCapacity -= client.capacity
newCount-- newCount--
} }
if newCapacity > f.capLimit || newCount > f.connLimit { if newCapacity > f.capLimit || newCount > f.activeLimit {
var ( var (
popList []*clientInfo popList []*clientInfo
targetPriority int64 targetPriority int64
) )
f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { f.activeQueue.MultiPop(func(data interface{}, priority int64) bool {
c := data.(*clientInfo) c := data.(*clientInfo)
popList = append(popList, c) popList = append(popList, c)
if c != client { if c != client {
@ -412,9 +446,9 @@ func (f *clientPool) capAvailable(id enode.ID, freeID string, capacity uint64, m
newCapacity -= c.capacity newCapacity -= c.capacity
newCount-- newCount--
} }
return newCapacity > f.capLimit || newCount > f.connLimit return newCapacity > f.capLimit || newCount > f.activeLimit
}) })
if newCapacity > f.capLimit || newCount > f.connLimit { if newCapacity > f.capLimit || newCount > f.activeLimit {
missing = math.MaxUint64 missing = math.MaxUint64
} else { } else {
var bt *balanceTracker var bt *balanceTracker
@ -422,12 +456,12 @@ func (f *clientPool) capAvailable(id enode.ID, freeID string, capacity uint64, m
bt = &client.balanceTracker bt = &client.balanceTracker
} else { } else {
bt = &balanceTracker{} bt = &balanceTracker{}
f.initBalanceTracker(bt, f.ndb.getOrNewPB(id), f.ndb.getOrNewNB(freeID), capacity) f.initBalanceTracker(bt, f.ndb.getOrNewPB(id), f.ndb.getOrNewNB(freeID), capacity, true)
} }
if capacity != f.freeClientCap && targetPriority >= -1 { if capacity != f.freeClientCap && targetPriority >= 0 {
targetPriority = -2 targetPriority = -1
} }
bias := connectedBias bias := activeBias
if f.disableBias { if f.disableBias {
bias = 0 bias = 0
} }
@ -441,9 +475,9 @@ func (f *clientPool) capAvailable(id enode.ID, freeID string, capacity uint64, m
} }
for _, c := range popList { for _, c := range popList {
if kick && c != client { if kick && c != client {
f.dropClient(c, f.clock.Now(), true) f.deactivateClient(c)
} else { } else {
f.connectedQueue.Push(c) f.activeQueue.Push(c)
} }
} }
} }
@ -482,28 +516,56 @@ func (f *clientPool) setDefaultFactors(posFactors, negFactors priceFactors) {
f.defaultNegFactors = negFactors f.defaultNegFactors = negFactors
} }
// dropClient removes a client from the connected queue and finalizes its balance. func (f *clientPool) deactivateClient(e *clientInfo) {
// If kick is true then it also initiates the disconnection. if _, ok := f.connectedMap[e.id]; !ok || !e.active {
func (f *clientPool) dropClient(e *clientInfo, now mclock.AbsTime, kick bool) {
if _, ok := f.connectedMap[e.id]; !ok {
return return
} }
f.finalizeBalance(e, now) f.activeQueue.Remove(e.queueIndex)
f.connectedQueue.Remove(e.queueIndex) f.activeCap -= e.capacity
delete(f.connectedMap, e.id)
f.connectedCap -= e.capacity
if e.priority { if e.priority {
f.updateFullRatio() f.updateFullRatio()
f.priorityConnected -= e.capacity f.priorityActive -= e.capacity
} }
totalConnectedGauge.Update(int64(f.connectedCap)) e.active = false
if kick { e.peer.updateCapacity(0)
clientKickedMeter.Mark(1) totalConnectedGauge.Update(int64(f.activeCap))
log.Debug("Client kicked out", "address", e.address) f.inactiveQueue.Push(e, -connPriority(e, f.clock.Now()))
f.removePeer(e.id) //TODO start timer
} else { }
clientDisconnectedMeter.Mark(1)
log.Debug("Client disconnected", "address", e.address) func (f *clientPool) tryActivateClients() {
now := f.clock.Now()
for f.inactiveQueue.Size() != 0 {
e := f.inactiveQueue.PopItem().(*clientInfo)
missing, capacity := f.capAvailable(e.id, e.freeID, e.capacity, 0, true)
if missing != 0 {
f.inactiveQueue.Push(e, -connPriority(e, now))
return
}
// capacity is available, activate client
e.active = true
e.capacity = capacity
e.peer.updateCapacity(capacity)
balance, _ := e.balanceTracker.getBalance(now)
e.balanceTracker.setCapacity(capacity)
updatePriceFactors(&e.balanceTracker, f.defaultPosFactors, f.defaultNegFactors, capacity)
// Register activated client to connection queue.
f.inactiveBalances -= balance
f.activeBalances += balance
f.activeQueue.Push(e)
f.activeCap += e.capacity
// If the current client is a paid client, monitor the status of client,
// downgrade it to normal client if positive balance is used up.
if e.priority {
f.updateFullRatio()
f.priorityActive += capacity
e.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(e.id) })
}
e.peer.updateCapacity(e.capacity)
totalConnectedGauge.Update(int64(f.activeCap))
clientConnectedMeter.Mark(1)
log.Debug("Client activated", "address", e.freeID)
} }
} }
@ -513,7 +575,7 @@ func (f *clientPool) capacityInfo() (uint64, uint64, uint64) {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
return f.capLimit, f.connectedCap, f.priorityConnected return f.capLimit, f.activeCap, f.priorityActive
} }
// finalizeBalance stops the balance tracker, retrieves the final balances and // finalizeBalance stops the balance tracker, retrieves the final balances and
@ -521,8 +583,8 @@ func (f *clientPool) capacityInfo() (uint64, uint64, uint64) {
func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) { func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) {
c.balanceTracker.stop(now) c.balanceTracker.stop(now)
pos, neg := c.balanceTracker.getBalance(now) pos, neg := c.balanceTracker.getBalance(now)
f.disconnectedBalances += pos f.inactiveBalances += pos
f.connectedBalances -= pos f.activeBalances -= pos
pb, nb := f.ndb.getOrNewPB(c.id), f.ndb.getOrNewNB(c.address) pb, nb := f.ndb.getOrNewPB(c.id), f.ndb.getOrNewNB(c.address)
pb.value = pos pb.value = pos
@ -549,12 +611,12 @@ func (f *clientPool) balanceExhausted(id enode.ID) {
} }
if c.priority { if c.priority {
f.updateFullRatio() f.updateFullRatio()
f.priorityConnected -= c.capacity f.priorityActive -= c.capacity
} }
c.priority = false c.priority = false
if c.capacity != f.freeClientCap { if c.capacity != f.freeClientCap {
f.connectedCap += f.freeClientCap - c.capacity f.activeCap += f.freeClientCap - c.capacity
totalConnectedGauge.Update(int64(f.connectedCap)) totalConnectedGauge.Update(int64(f.activeCap))
c.capacity = f.freeClientCap c.capacity = f.freeClientCap
c.balanceTracker.setCapacity(c.capacity) c.balanceTracker.setCapacity(c.capacity)
c.peer.updateCapacity(c.capacity) c.peer.updateCapacity(c.capacity)
@ -564,20 +626,22 @@ func (f *clientPool) balanceExhausted(id enode.ID) {
f.ndb.setPB(id, pb) f.ndb.setPB(id, pb)
} }
// setConnLimit sets the maximum number and total capacity of connected clients, // setactiveLimit sets the maximum number and total capacity of connected clients,
// dropping some of them if necessary. // dropping some of them if necessary.
func (f *clientPool) setLimits(totalConn int, totalCap uint64) { func (f *clientPool) setLimits(totalConn int, totalCap uint64) {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
f.updateFullRatio() f.updateFullRatio()
f.connLimit = totalConn f.activeLimit = totalConn
f.capLimit = totalCap f.capLimit = totalCap
if f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit { if f.activeCap > f.capLimit || f.activeQueue.Size() > f.activeLimit {
f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { f.activeQueue.MultiPop(func(data interface{}, priority int64) bool {
f.dropClient(data.(*clientInfo), mclock.Now(), true) f.deactivateClient(data.(*clientInfo))
return f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit return f.activeCap > f.capLimit || f.activeQueue.Size() > f.activeLimit
}) })
} else {
f.tryActivateClients()
} }
} }
@ -599,15 +663,16 @@ func (f *clientPool) setCapacity(id enode.ID, freeID string, capacity uint64, mi
if c == nil { if c == nil {
return 0, capacity, fmt.Errorf("client %064x is not connected", c.id[:]) return 0, capacity, fmt.Errorf("client %064x is not connected", c.id[:])
} }
f.connectedCap += capacity - c.capacity f.activeCap += capacity - c.capacity
f.updateFullRatio() f.updateFullRatio()
f.priorityConnected += capacity - c.capacity f.priorityActive += capacity - c.capacity
c.capacity = capacity c.capacity = capacity
c.balanceTracker.setCapacity(capacity) c.balanceTracker.setCapacity(capacity)
f.connectedQueue.Update(c.queueIndex) f.activeQueue.Update(c.queueIndex)
totalConnectedGauge.Update(int64(f.connectedCap)) totalConnectedGauge.Update(int64(f.activeCap))
updatePriceFactors(&c.balanceTracker, c.posFactors, c.negFactors, c.capacity) updatePriceFactors(&c.balanceTracker, c.posFactors, c.negFactors, c.capacity)
c.peer.updateCapacity(c.capacity) c.peer.updateCapacity(c.capacity)
f.tryActivateClients()
} }
return 0, capacity, nil return 0, capacity, nil
} }
@ -625,11 +690,11 @@ func (f *clientPool) requestCost(p *peer, cost uint64) uint64 {
f.lock.Lock() f.lock.Lock()
defer f.lock.Unlock() defer f.lock.Unlock()
info, exist := f.connectedMap[p.ID()] c := f.connectedMap[p.ID()]
if !exist || f.closed { if c == nil || f.closed {
return 0 return 0
} }
return info.balanceTracker.requestCost(cost) return c.balanceTracker.requestCost(cost)
} }
// logOffset calculates the time-dependent offset for the logarithmic // logOffset calculates the time-dependent offset for the logarithmic
@ -650,6 +715,11 @@ func updatePriceFactors(bt *balanceTracker, posFactors, negFactors priceFactors,
bt.setFactors(false, posFactors.timeFactor+float64(capacity)*posFactors.capacityFactor/1000000, posFactors.requestFactor) bt.setFactors(false, posFactors.timeFactor+float64(capacity)*posFactors.capacityFactor/1000000, posFactors.requestFactor)
} }
func zeroPriceFactors(bt *balanceTracker) {
bt.setFactors(true, 0, 0)
bt.setFactors(false, 0, 0)
}
// getPosBalance retrieves a single positive balance entry from cache or the database // getPosBalance retrieves a single positive balance entry from cache or the database
func (f *clientPool) getPosBalance(id enode.ID) posBalance { func (f *clientPool) getPosBalance(id enode.ID) posBalance {
f.lock.Lock() f.lock.Lock()
@ -692,22 +762,32 @@ func (f *clientPool) addBalance(id enode.ID, amount int64, meta string) (uint64,
f.ndb.setPB(id, pb) f.ndb.setPB(id, pb)
if c != nil { if c != nil {
c.balanceTracker.setBalance(pb.value, negBalance) c.balanceTracker.setBalance(pb.value, negBalance)
if c.active {
f.activeQueue.Update(c.queueIndex)
if !c.priority && pb.value > 0 { if !c.priority && pb.value > 0 {
// The capacity should be adjusted based on the requirement, // The capacity should be adjusted based on the requirement,
// but we have no idea about the new capacity, need a second // but we have no idea about the new capacity, need a second
// call to udpate it. // call to udpate it.
f.updateFullRatio() f.updateFullRatio()
c.priority = true f.priorityActive += c.capacity
f.priorityConnected += c.capacity
c.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) c.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) })
} }
c.balanceMetaInfo = meta
f.activeBalances += pb.value - oldBalance
} else {
f.inactiveQueue.Remove(c.queueIndex)
f.inactiveQueue.Push(c, -connPriority(c, f.clock.Now()))
f.inactiveBalances += pb.value - oldBalance
}
if pb.value > 0 {
c.priority = true
// if balance is set to zero then reverting to non-priority status // if balance is set to zero then reverting to non-priority status
// is handled by the balanceExhausted callback // is handled by the balanceExhausted callback
c.balanceMetaInfo = meta
f.connectedBalances += pb.value - oldBalance
} else {
f.disconnectedBalances += pb.value - oldBalance
} }
} else {
f.inactiveBalances += pb.value - oldBalance
}
f.tryActivateClients()
return oldBalance, pb.value, nil return oldBalance, pb.value, nil
} }

View file

@ -56,29 +56,34 @@ func TestClientPoolL100C300P20(t *testing.T) {
const testClientPoolTicks = 100000 const testClientPoolTicks = 100000
type poolTestPeer int type poolTestPeer struct {
index int
func (i poolTestPeer) ID() enode.ID { disconnCh chan int
return enode.ID{byte(i % 256), byte(i >> 8)}
}
func (i poolTestPeer) freeClientId() string {
return fmt.Sprintf("addr #%d", i)
}
func (i poolTestPeer) updateCapacity(uint64) {}
type poolTestPeerWithCap struct {
poolTestPeer
cap uint64 cap uint64
} }
func (i *poolTestPeerWithCap) updateCapacity(cap uint64) { i.cap = cap } func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer {
return &poolTestPeer{index: i, disconnCh: disconnCh}
}
func (i poolTestPeer) freezeClient() {} func (i *poolTestPeer) ID() enode.ID {
return enode.ID{byte(i.index % 256), byte(i.index >> 8)}
}
func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomDisconnect bool) { func (i *poolTestPeer) freeClientId() string {
return fmt.Sprintf("addr #%d", i)
}
func (i *poolTestPeer) updateCapacity(cap uint64) {
i.cap = cap
if cap == 0 && i.disconnCh != nil {
i.disconnCh <- i.index
}
}
func (i *poolTestPeer) freezeClient() {}
func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, randomDisconnect bool) {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
var ( var (
clock mclock.Simulated clock mclock.Simulated
@ -91,13 +96,14 @@ func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomD
} }
pool = newClientPool(db, 1, 1, &clock, disconnFn) pool = newClientPool(db, 1, 1, &clock, disconnFn)
) )
pool.disableBias = true pool.disableBias = true
pool.setLimits(connLimit, uint64(connLimit)) pool.setLimits(activeLimit, uint64(activeLimit))
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
// pool should accept new peers up to its connected limit // pool should accept new peers up to its connected limit
for i := 0; i < connLimit; i++ { for i := 0; i < activeLimit; i++ {
if pool.connect(poolTestPeer(i), 0) { if cap, _ := pool.connect(newPoolTestPeer(i, disconnCh), 0); cap != 0 {
connected[i] = true connected[i] = true
} else { } else {
t.Fatalf("Test peer #%d rejected", i) t.Fatalf("Test peer #%d rejected", i)
@ -111,28 +117,30 @@ func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomD
// give a positive balance to some of the peers // give a positive balance to some of the peers
amount := testClientPoolTicks / 2 * int64(time.Second) // enough for half of the simulation period amount := testClientPoolTicks / 2 * int64(time.Second) // enough for half of the simulation period
for i := 0; i < paidCount; i++ { for i := 0; i < paidCount; i++ {
pool.addBalance(poolTestPeer(i).ID(), amount, "") pool.addBalance(newPoolTestPeer(i, disconnCh).ID(), amount, "")
} }
} }
i := rand.Intn(clientCount) i := rand.Intn(clientCount)
if connected[i] { if connected[i] {
if randomDisconnect { if randomDisconnect {
pool.disconnect(poolTestPeer(i)) pool.disconnect(newPoolTestPeer(i, disconnCh))
connected[i] = false connected[i] = false
connTicks[i] += tickCounter connTicks[i] += tickCounter
} }
} else { } else {
if pool.connect(poolTestPeer(i), 0) { if cap, _ := pool.connect(newPoolTestPeer(i, disconnCh), 0); cap != 0 {
connected[i] = true connected[i] = true
connTicks[i] -= tickCounter connTicks[i] -= tickCounter
} else {
pool.disconnect(newPoolTestPeer(i, disconnCh))
} }
} }
pollDisconnects: pollDisconnects:
for { for {
select { select {
case i := <-disconnCh: case i := <-disconnCh:
pool.disconnect(poolTestPeer(i)) pool.disconnect(newPoolTestPeer(i, disconnCh))
if connected[i] { if connected[i] {
connTicks[i] += tickCounter connTicks[i] += tickCounter
connected[i] = false connected[i] = false
@ -143,10 +151,10 @@ func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomD
} }
} }
expTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2*(connLimit-paidCount)/(clientCount-paidCount) expTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2*(activeLimit-paidCount)/(clientCount-paidCount)
expMin := expTicks - expTicks/5 expMin := expTicks - expTicks/5
expMax := expTicks + expTicks/5 expMax := expTicks + expTicks/5
paidTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2 paidTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2
paidMin := paidTicks - paidTicks/5 paidMin := paidTicks - paidTicks/5
paidMax := paidTicks + paidTicks/5 paidMax := paidTicks + paidTicks/5
@ -178,9 +186,9 @@ func TestConnectPaidClient(t *testing.T) {
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
// Add balance for an external client and mark it as paid client // Add balance for an external client and mark it as paid client
pool.addBalance(poolTestPeer(0).ID(), 1000, "") pool.addBalance(newPoolTestPeer(0, nil).ID(), 1000, "")
if !pool.connect(poolTestPeer(0), 10) { if cap, _ := pool.connect(newPoolTestPeer(0, nil), 10); cap == 0 {
t.Fatalf("Failed to connect paid client") t.Fatalf("Failed to connect paid client")
} }
} }
@ -196,10 +204,10 @@ func TestConnectPaidClientToSmallPool(t *testing.T) {
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
// Add balance for an external client and mark it as paid client // Add balance for an external client and mark it as paid client
pool.addBalance(poolTestPeer(0).ID(), 1000, "") pool.addBalance(newPoolTestPeer(0, nil).ID(), 1000, "")
// Connect a fat paid client to pool, should reject it. // Connect a fat paid client to pool, should reject it.
if pool.connect(poolTestPeer(0), 100) { if cap, _ := pool.connect(newPoolTestPeer(0, nil), 100); cap != 0 {
t.Fatalf("Connected fat paid client, should reject it") t.Fatalf("Connected fat paid client, should reject it")
} }
} }
@ -216,17 +224,17 @@ func TestConnectPaidClientToFullPool(t *testing.T) {
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.addBalance(poolTestPeer(i).ID(), 1000000000, "") pool.addBalance(newPoolTestPeer(i, nil).ID(), 1000000000, "")
pool.connect(poolTestPeer(i), 1) pool.connect(newPoolTestPeer(i, nil), 1)
} }
pool.addBalance(poolTestPeer(11).ID(), 1000, "") // Add low balance to new paid client pool.addBalance(newPoolTestPeer(11, nil).ID(), 1000, "") // Add low balance to new paid client
if pool.connect(poolTestPeer(11), 1) { if cap, _ := pool.connect(newPoolTestPeer(11, nil), 1); cap != 0 {
t.Fatalf("Low balance paid client should be rejected") t.Fatalf("Low balance paid client should be rejected")
} }
clock.Run(time.Second) clock.Run(time.Second)
pool.addBalance(poolTestPeer(12).ID(), 1000000000*60*3, "") // Add high balance to new paid client pool.addBalance(newPoolTestPeer(12, nil).ID(), 1000000000*60*3+1, "") // Add high balance to new paid client
if !pool.connect(poolTestPeer(12), 1) { if cap, _ := pool.connect(newPoolTestPeer(12, nil), 1); cap == 0 {
t.Fatalf("High balance paid client should be accpected") t.Fatalf("High balance paid client should be accepted")
} }
} }
@ -243,13 +251,13 @@ func TestPaidClientKickedOut(t *testing.T) {
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.addBalance(poolTestPeer(i).ID(), 1000000000, "") // 1 second allowance pool.addBalance(newPoolTestPeer(i, kickedCh).ID(), 1000000000, "") // 1 second allowance
pool.connect(poolTestPeer(i), 1) pool.connect(newPoolTestPeer(i, kickedCh), 1)
clock.Run(time.Millisecond) clock.Run(time.Millisecond)
} }
clock.Run(time.Second) clock.Run(time.Second)
clock.Run(connectedBias) clock.Run(activeBias)
if !pool.connect(poolTestPeer(11), 0) { if cap, _ := pool.connect(newPoolTestPeer(11, kickedCh), 0); cap == 0 {
t.Fatalf("Free client should be accectped") t.Fatalf("Free client should be accectped")
} }
select { select {
@ -271,7 +279,7 @@ func TestConnectFreeClient(t *testing.T) {
defer pool.stop() defer pool.stop()
pool.setLimits(10, uint64(10)) pool.setLimits(10, uint64(10))
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
if !pool.connect(poolTestPeer(0), 10) { if cap, _ := pool.connect(newPoolTestPeer(0, nil), 10); cap == 0 {
t.Fatalf("Failed to connect free client") t.Fatalf("Failed to connect free client")
} }
} }
@ -288,18 +296,18 @@ func TestConnectFreeClientToFullPool(t *testing.T) {
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.connect(poolTestPeer(i), 1) pool.connect(newPoolTestPeer(i, nil), 1)
} }
if pool.connect(poolTestPeer(11), 1) { if cap, _ := pool.connect(newPoolTestPeer(11, nil), 1); cap != 0 {
t.Fatalf("New free client should be rejected") t.Fatalf("New free client should be rejected")
} }
clock.Run(time.Minute) clock.Run(time.Minute)
if pool.connect(poolTestPeer(12), 1) { if cap, _ := pool.connect(newPoolTestPeer(12, nil), 1); cap != 0 {
t.Fatalf("New free client should be rejected") t.Fatalf("New free client should be rejected")
} }
clock.Run(time.Millisecond) clock.Run(time.Millisecond)
clock.Run(4 * time.Minute) clock.Run(4 * time.Minute)
if !pool.connect(poolTestPeer(13), 1) { if cap, _ := pool.connect(newPoolTestPeer(13, nil), 1); cap == 0 {
t.Fatalf("Old client connects more than 5min should be kicked") t.Fatalf("Old client connects more than 5min should be kicked")
} }
} }
@ -317,15 +325,16 @@ func TestFreeClientKickedOut(t *testing.T) {
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.connect(poolTestPeer(i), 1) pool.connect(newPoolTestPeer(i, kicked), 1)
clock.Run(time.Millisecond) clock.Run(time.Millisecond)
} }
if pool.connect(poolTestPeer(10), 1) { if cap, _ := pool.connect(newPoolTestPeer(10, kicked), 1); cap != 0 {
t.Fatalf("New free client should be rejected") t.Fatalf("New free client should be rejected")
} }
pool.disconnect(newPoolTestPeer(10, kicked))
clock.Run(5 * time.Minute) clock.Run(5 * time.Minute)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.connect(poolTestPeer(i+10), 1) pool.connect(newPoolTestPeer(i+10, kicked), 1)
} }
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
select { select {
@ -351,12 +360,12 @@ func TestPositiveBalanceCalculation(t *testing.T) {
pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setLimits(10, uint64(10)) // Total capacity limit is 10
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
pool.addBalance(poolTestPeer(0).ID(), int64(time.Minute*3), "") pool.addBalance(newPoolTestPeer(0, kicked).ID(), int64(time.Minute*3), "")
pool.connect(poolTestPeer(0), 10) pool.connect(newPoolTestPeer(0, kicked), 10)
clock.Run(time.Minute) clock.Run(time.Minute)
pool.disconnect(poolTestPeer(0)) pool.disconnect(newPoolTestPeer(0, kicked))
pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID()) pb := pool.ndb.getOrNewPB(newPoolTestPeer(0, kicked).ID())
if pb.value != uint64(time.Minute*2) { if pb.value != uint64(time.Minute*2) {
t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb.value) t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb.value)
} }
@ -374,11 +383,9 @@ func TestDowngradePriorityClient(t *testing.T) {
pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setLimits(10, uint64(10)) // Total capacity limit is 10
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
p := &poolTestPeerWithCap{ p := newPoolTestPeer(0, kicked)
poolTestPeer: poolTestPeer(0),
}
pool.addBalance(p.ID(), int64(time.Minute), "") pool.addBalance(p.ID(), int64(time.Minute), "")
pool.connect(p, 10) p.cap, _ = pool.connect(p, 10)
if p.cap != 10 { if p.cap != 10 {
t.Fatalf("The capcacity of priority peer hasn't been updated, got: %d", p.cap) t.Fatalf("The capcacity of priority peer hasn't been updated, got: %d", p.cap)
} }
@ -388,13 +395,13 @@ func TestDowngradePriorityClient(t *testing.T) {
if p.cap != 1 { if p.cap != 1 {
t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap) t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap)
} }
pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID()) pb := pool.ndb.getOrNewPB(newPoolTestPeer(0, kicked).ID())
if pb.value != 0 { if pb.value != 0 {
t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb.value) t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb.value)
} }
pool.addBalance(poolTestPeer(0).ID(), int64(time.Minute), "") pool.addBalance(newPoolTestPeer(0, kicked).ID(), int64(time.Minute), "")
pb = pool.ndb.getOrNewPB(poolTestPeer(0).ID()) pb = pool.ndb.getOrNewPB(newPoolTestPeer(0, kicked).ID())
if pb.value != uint64(time.Minute) { if pb.value != uint64(time.Minute) {
t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb.value) t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb.value)
} }
@ -404,34 +411,32 @@ func TestNegativeBalanceCalculation(t *testing.T) {
var ( var (
clock mclock.Simulated clock mclock.Simulated
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
kicked = make(chan int, 10)
) )
removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop pool := newClientPool(db, 1, 1, &clock, nil)
pool := newClientPool(db, 1, 1, &clock, removeFn)
defer pool.stop() defer pool.stop()
pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setLimits(10, uint64(10)) // Total capacity limit is 10
pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.connect(poolTestPeer(i), 1) pool.connect(newPoolTestPeer(i, nil), 1)
} }
clock.Run(time.Second) clock.Run(time.Second)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.disconnect(poolTestPeer(i)) pool.disconnect(newPoolTestPeer(i, nil))
nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId()) nb := pool.ndb.getOrNewNB(newPoolTestPeer(i, nil).freeClientId())
if nb.logValue != 0 { if nb.logValue != 0 {
t.Fatalf("Short connection shouldn't be recorded") t.Fatalf("Short connection shouldn't be recorded")
} }
} }
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.connect(poolTestPeer(i), 1) pool.connect(newPoolTestPeer(i, nil), 1)
} }
clock.Run(time.Minute) clock.Run(time.Minute)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
pool.disconnect(poolTestPeer(i)) pool.disconnect(newPoolTestPeer(i, nil))
nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId()) nb := pool.ndb.getOrNewNB(newPoolTestPeer(i, nil).freeClientId())
nb.logValue -= pool.logOffset(clock.Now()) nb.logValue -= pool.logOffset(clock.Now())
nb.logValue /= fixedPointMultiplier nb.logValue /= fixedPointMultiplier
if nb.logValue != int64(math.Log(float64(time.Minute/time.Second))) { if nb.logValue != int64(math.Log(float64(time.Minute/time.Second))) {
@ -541,3 +546,83 @@ func TestNodeDBExpiration(t *testing.T) {
t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated) t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated)
} }
} }
func TestInactiveClient(t *testing.T) {
var (
clock mclock.Simulated
db = rawdb.NewMemoryDatabase()
)
pool := newClientPool(db, 1, 1, &clock, nil)
defer pool.stop()
pool.setLimits(2, uint64(2)) // Total capacity limit is 10
p1 := newPoolTestPeer(1, nil)
p2 := newPoolTestPeer(2, nil)
p3 := newPoolTestPeer(3, nil)
pool.addBalance(p1.ID(), 1000, "")
pool.addBalance(p3.ID(), 2000, "")
// p1: 1000 p2: 0 p3: 2000
p1.cap, _ = pool.connect(p1, 1)
if p1.cap != 1 {
t.Fatalf("Failed to connect peer #1")
}
p2.cap, _ = pool.connect(p2, 1)
if p2.cap != 1 {
t.Fatalf("Failed to connect peer #2")
}
p3.cap, _ = pool.connect(p3, 1)
if p3.cap != 1 {
t.Fatalf("Failed to connect peer #3")
}
if p2.cap != 0 {
t.Fatalf("Failed to deactivate peer #2")
}
pool.addBalance(p2.ID(), 3000, "")
// p1: 1000 p2: 3000 p3: 2000
if p2.cap != 1 {
t.Fatalf("Failed to activate peer #2")
}
if p1.cap != 0 {
t.Fatalf("Failed to deactivate peer #1")
}
pool.addBalance(p2.ID(), -2500, "")
// p1: 1000 p2: 500 p3: 2000
if p1.cap != 1 {
t.Fatalf("Failed to activate peer #1")
}
if p2.cap != 0 {
t.Fatalf("Failed to deactivate peer #2")
}
pool.setDefaultFactors(priceFactors{1e-9, 0, 0}, priceFactors{1e-9, 0, 0})
p4 := newPoolTestPeer(4, nil)
pool.addBalance(p4.ID(), 1500, "")
// p1: 1000 p2: 500 p3: 2000 p4: 1500
p4.cap, _ = pool.connect(p4, 1)
if p4.cap != 1 {
t.Fatalf("Failed to activate peer #4")
}
if p1.cap != 0 {
t.Fatalf("Failed to deactivate peer #1")
}
clock.Run(time.Second * 600)
// manually trigger a check to avoid a long real-time wait
pool.lock.Lock()
pool.tryActivateClients()
pool.lock.Unlock()
// p1: 1000 p2: 500 p3: 2000 p4: 900
if p1.cap != 1 {
t.Fatalf("Failed to activate peer #1")
}
if p4.cap != 0 {
t.Fatalf("Failed to deactivate peer #4")
}
pool.disconnect(p2)
pool.disconnect(p4)
pool.addBalance(p1.ID(), -1000, "")
if p1.cap != 1 {
t.Fatalf("Should not deactivate peer #1")
}
if p2.cap != 0 {
t.Fatalf("Should not activate peer #2")
}
}

View file

@ -159,15 +159,9 @@ func newCostTracker(db ethdb.Database, config *eth.Config) (*costTracker, uint64
} }
ct.gfLoop() ct.gfLoop()
costList := ct.makeCostList(ct.globalFactor() * 1.25) costList := ct.makeCostList(ct.globalFactor() * 1.25)
for _, c := range costList { var minRecharge uint64
amount := minBufferReqAmount[c.MsgCode] ct.minBufLimit, minRecharge = costList.decode(ProtocolLengths[ServerProtocolVersions[len(ServerProtocolVersions)-1]]).reqParams()
cost := c.BaseCost + amount*c.ReqCost return ct, minRecharge
if cost > ct.minBufLimit {
ct.minBufLimit = cost
}
}
ct.minBufLimit *= uint64(minBufferMultiplier)
return ct, (ct.minBufLimit-1)/bufLimitRatio + 1
} }
// stop stops the cost tracker and saves the cost factor statistics to the database // stop stops the cost tracker and saves the cost factor statistics to the database
@ -480,6 +474,22 @@ func (table requestCostTable) getMaxCost(code, amount uint64) uint64 {
return costs.baseCost + amount*costs.reqCost return costs.baseCost + amount*costs.reqCost
} }
func (table requestCostTable) reqParams() (minRecharge, minBufLimit uint64) {
for code, c := range table {
amount := minBufferReqAmount[code]
cost := c.baseCost + amount*c.reqCost
if cost > minBufLimit {
minBufLimit = cost
}
}
minBufLimit *= uint64(minBufferMultiplier)
if minBufLimit < 1 {
minBufLimit = 1
}
minRecharge = (minBufLimit-1)/bufLimitRatio + 1
return
}
// decode converts a cost list to a cost table // decode converts a cost list to a cost table
func (list RequestCostList) decode(protocolLength uint64) requestCostTable { func (list RequestCostList) decode(protocolLength uint64) requestCostTable {
table := make(requestCostTable) table := make(requestCostTable)

View file

@ -60,7 +60,7 @@ func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) }
func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) } func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) }
func testGetBlockHeaders(t *testing.T, protocol int) { func testGetBlockHeaders(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -190,7 +190,7 @@ func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) }
func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) } func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) }
func testGetBlockBodies(t *testing.T, protocol int) { func testGetBlockBodies(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -269,7 +269,7 @@ func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) }
func testGetCode(t *testing.T, protocol int) { func testGetCode(t *testing.T, protocol int) {
// Assemble the test environment // Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -299,7 +299,7 @@ func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) }
func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) } func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) }
func testGetStaleCode(t *testing.T, protocol int) { func testGetStaleCode(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -325,7 +325,7 @@ func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) }
func testGetReceipt(t *testing.T, protocol int) { func testGetReceipt(t *testing.T, protocol int) {
// Assemble the test environment // Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -353,7 +353,7 @@ func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) }
func testGetProofs(t *testing.T, protocol int) { func testGetProofs(t *testing.T, protocol int) {
// Assemble the test environment // Assemble the test environment
server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -388,7 +388,7 @@ func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) }
func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) } func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) }
func testGetStaleProof(t *testing.T, protocol int) { func testGetStaleProof(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -436,7 +436,7 @@ func testGetCHTProofs(t *testing.T, protocol int) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
} }
server, tearDown := newServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false, true, 0) server, tearDown := newServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -485,7 +485,7 @@ func testGetBloombitsProofs(t *testing.T, protocol int) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
} }
} }
server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), protocol, waitIndexers, false, true, 0) server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), protocol, waitIndexers, false, true, 0, true)
defer tearDown() defer tearDown()
bc := server.handler.blockchain bc := server.handler.blockchain
@ -523,7 +523,7 @@ func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, 3) }
func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, 4) } func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, 4) }
func testTransactionStatus(t *testing.T, protocol int) { func testTransactionStatus(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0) server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0, true)
defer tearDown() defer tearDown()
server.handler.addTxsSync = true server.handler.addTxsSync = true
@ -620,7 +620,7 @@ func TestStopResumeLes3(t *testing.T) { testStopResume(t, 3) }
func TestStopResumeLes4(t *testing.T) { testStopResume(t, 4) } func TestStopResumeLes4(t *testing.T) { testStopResume(t, 4) }
func testStopResume(t *testing.T, protocol int) { func testStopResume(t *testing.T, protocol int) {
server, tearDown := newServerEnv(t, 0, protocol, nil, true, true, testBufLimit/10) server, tearDown := newServerEnv(t, 0, protocol, nil, true, true, testBufLimit/10, true)
defer tearDown() defer tearDown()
server.handler.server.costTracker.testing = true server.handler.server.costTracker.testing = true

View file

@ -236,7 +236,7 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od
// still expect all retrievals to pass, now data should be cached locally // still expect all retrievals to pass, now data should be cached locally
if checkCached { if checkCached {
client.handler.backend.peers.Unregister(client.peer.peer.id) client.handler.backend.peers.Disconnect(client.peer.peer.id)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
test(5) test(5)
} }

View file

@ -101,6 +101,9 @@ type peer struct {
responseCount uint64 responseCount uint64
invalidCount uint32 invalidCount uint32
active bool
activate, deactivate func()
poolEntry *poolEntry poolEntry *poolEntry
hasBlock func(common.Hash, uint64, bool) bool hasBlock func(common.Hash, uint64, bool) bool
responseErrors int responseErrors int
@ -297,13 +300,21 @@ func (p *peer) updateCapacity(cap uint64) {
p.responseLock.Lock() p.responseLock.Lock()
defer p.responseLock.Unlock() defer p.responseLock.Unlock()
if !p.active && cap != 0 && p.activate != nil {
p.activate()
}
if cap != 0 || p.version >= lpv4 {
p.fcParams = flowcontrol.ServerParams{MinRecharge: cap, BufLimit: cap * bufLimitRatio} p.fcParams = flowcontrol.ServerParams{MinRecharge: cap, BufLimit: cap * bufLimitRatio}
p.fcClient.UpdateParams(p.fcParams) p.fcClient.UpdateParams(p.fcParams)
var kvList keyValueList var kvList keyValueList
kvList = kvList.add("flowControl/MRR", cap)
kvList = kvList.add("flowControl/BL", cap*bufLimitRatio) kvList = kvList.add("flowControl/BL", cap*bufLimitRatio)
kvList = kvList.add("flowControl/MRR", cap)
p.queueSend(func() { p.SendAnnounce(announceData{Update: kvList}) }) p.queueSend(func() { p.SendAnnounce(announceData{Update: kvList}) })
} }
if p.active && cap == 0 && p.deactivate != nil {
p.deactivate()
}
}
func (p *peer) responseID() uint64 { func (p *peer) responseID() uint64 {
p.responseCount += 1 p.responseCount += 1
@ -628,8 +639,15 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
send = send.add("serveRecentState", stateRecent) send = send.add("serveRecentState", stateRecent)
send = send.add("txRelay", nil) send = send.add("txRelay", nil)
} }
send = send.add("flowControl/BL", server.defParams.BufLimit)
send = send.add("flowControl/MRR", server.defParams.MinRecharge) p.active = p.version < lpv4
if p.active {
p.fcParams = server.defParams
} else {
p.fcParams = flowcontrol.ServerParams{}
}
send = send.add("flowControl/BL", p.fcParams.BufLimit)
send = send.add("flowControl/MRR", p.fcParams.MinRecharge)
var costList RequestCostList var costList RequestCostList
if server.costTracker.testCostList != nil { if server.costTracker.testCostList != nil {
@ -639,7 +657,6 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
} }
send = send.add("flowControl/MRC", costList) send = send.add("flowControl/MRC", costList)
p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)]) p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
p.fcParams = server.defParams
// Add advertised checkpoint and register block height which // Add advertised checkpoint and register block height which
// client can verify the checkpoint validity. // client can verify the checkpoint validity.
@ -710,7 +727,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
// set default announceType on server side // set default announceType on server side
p.announceType = announceTypeSimple p.announceType = announceTypeSimple
} }
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams) p.fcClient = flowcontrol.NewClientNode(server.fcManager, p.fcParams)
} }
} else { } else {
if recv.get("serveChainSince", &p.chainSince) != nil { if recv.get("serveChainSince", &p.chainSince) != nil {
@ -747,6 +764,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
p.fcParams = sParams p.fcParams = sParams
p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{}) p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{})
p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)]) p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)])
p.active = p.paramsUseful()
recv.get("checkpoint/value", &p.checkpoint) recv.get("checkpoint/value", &p.checkpoint)
recv.get("checkpoint/registerHeight", &p.checkpointNumber) recv.get("checkpoint/registerHeight", &p.checkpointNumber)
@ -772,10 +790,12 @@ func (p *peer) updateFlowControl(update keyValueMap) {
} }
// If any of the flow control params is nil, refuse to update. // If any of the flow control params is nil, refuse to update.
var params flowcontrol.ServerParams var params flowcontrol.ServerParams
updated := false
if update.get("flowControl/BL", &params.BufLimit) == nil && update.get("flowControl/MRR", &params.MinRecharge) == nil { if update.get("flowControl/BL", &params.BufLimit) == nil && update.get("flowControl/MRR", &params.MinRecharge) == nil {
// todo can light client set a minimal acceptable flow control params? // todo can light client set a minimal acceptable flow control params?
p.fcParams = params p.fcParams = params
p.fcServer.UpdateParams(params) p.fcServer.UpdateParams(params)
updated = true
} }
var MRC RequestCostList var MRC RequestCostList
if update.get("flowControl/MRC", &MRC) == nil { if update.get("flowControl/MRC", &MRC) == nil {
@ -783,7 +803,16 @@ func (p *peer) updateFlowControl(update keyValueMap) {
for code, cost := range costUpdate { for code, cost := range costUpdate {
p.fcCosts[code] = cost p.fcCosts[code] = cost
} }
updated = true
} }
if updated {
p.active = p.paramsUseful()
}
}
func (p *peer) paramsUseful() bool {
reqRecharge, reqBufLimit := p.fcCosts.reqParams()
return p.fcParams.MinRecharge >= reqRecharge && p.fcParams.BufLimit >= reqBufLimit
} }
// String implements fmt.Stringer. // String implements fmt.Stringer.
@ -803,7 +832,7 @@ type peerSetNotify interface {
// peerSet represents the collection of active peers currently participating in // peerSet represents the collection of active peers currently participating in
// the Light Ethereum sub-protocol. // the Light Ethereum sub-protocol.
type peerSet struct { type peerSet struct {
peers map[string]*peer active, inactive map[string]*peer
lock sync.RWMutex lock sync.RWMutex
notifyList []peerSetNotify notifyList []peerSetNotify
closed bool closed bool
@ -812,7 +841,8 @@ type peerSet struct {
// newPeerSet creates a new peer set to track the active participants. // newPeerSet creates a new peer set to track the active participants.
func newPeerSet() *peerSet { func newPeerSet() *peerSet {
return &peerSet{ return &peerSet{
peers: make(map[string]*peer), active: make(map[string]*peer),
inactive: make(map[string]*peer),
} }
} }
@ -820,8 +850,8 @@ func newPeerSet() *peerSet {
func (ps *peerSet) notify(n peerSetNotify) { func (ps *peerSet) notify(n peerSetNotify) {
ps.lock.Lock() ps.lock.Lock()
ps.notifyList = append(ps.notifyList, n) ps.notifyList = append(ps.notifyList, n)
peers := make([]*peer, 0, len(ps.peers)) peers := make([]*peer, 0, len(ps.active))
for _, p := range ps.peers { for _, p := range ps.active {
peers = append(peers, p) peers = append(peers, p)
} }
ps.lock.Unlock() ps.lock.Unlock()
@ -839,11 +869,13 @@ func (ps *peerSet) Register(p *peer) error {
ps.lock.Unlock() ps.lock.Unlock()
return errClosed return errClosed
} }
if _, ok := ps.peers[p.id]; ok { if _, ok := ps.active[p.id]; ok {
ps.lock.Unlock() ps.lock.Unlock()
return errAlreadyRegistered return errAlreadyRegistered
} }
ps.peers[p.id] = p ps.active[p.id] = p
delete(ps.inactive, p.id)
p.sendQueue = newExecQueue(100) p.sendQueue = newExecQueue(100)
peers := make([]peerSetNotify, len(ps.notifyList)) peers := make([]peerSetNotify, len(ps.notifyList))
copy(peers, ps.notifyList) copy(peers, ps.notifyList)
@ -856,14 +888,15 @@ func (ps *peerSet) Register(p *peer) error {
} }
// Unregister removes a remote peer from the active set, disabling any further // Unregister removes a remote peer from the active set, disabling any further
// actions to/from that particular entity. It also initiates disconnection at the networking layer. // actions to/from that particular entity.
func (ps *peerSet) Unregister(id string) error { func (ps *peerSet) Unregister(p *peer) error {
ps.lock.Lock() ps.lock.Lock()
if p, ok := ps.peers[id]; !ok { if _, ok := ps.active[p.id]; !ok {
ps.lock.Unlock() ps.lock.Unlock()
return errNotRegistered return errNotRegistered
} else { } else {
delete(ps.peers, id) delete(ps.active, p.id)
ps.inactive[p.id] = p
peers := make([]peerSetNotify, len(ps.notifyList)) peers := make([]peerSetNotify, len(ps.notifyList))
copy(peers, ps.notifyList) copy(peers, ps.notifyList)
ps.lock.Unlock() ps.lock.Unlock()
@ -871,22 +904,38 @@ func (ps *peerSet) Unregister(id string) error {
for _, n := range peers { for _, n := range peers {
n.unregisterPeer(p) n.unregisterPeer(p)
} }
p.sendQueue.quit() p.sendQueue.quit()
p.Peer.Disconnect(p2p.DiscUselessPeer)
return nil return nil
} }
} }
// AllPeerIDs returns a list of all registered peer IDs // Disconnect removes a remote peer from either the active or inactive set and
// initiates disconnection at the networking layer.
func (ps *peerSet) Disconnect(id string) error {
ps.lock.Lock()
p, ok := ps.active[id]
if ok {
delete(ps.active, id)
} else {
if p, ok = ps.inactive[id]; !ok {
ps.lock.Unlock()
return errNotRegistered
}
delete(ps.inactive, id)
}
ps.lock.Unlock()
p.Peer.Disconnect(p2p.DiscUselessPeer)
return nil
}
// AllPeerIDs returns a list of all active peer IDs
func (ps *peerSet) AllPeerIDs() []string { func (ps *peerSet) AllPeerIDs() []string {
ps.lock.RLock() ps.lock.RLock()
defer ps.lock.RUnlock() defer ps.lock.RUnlock()
res := make([]string, len(ps.peers)) res := make([]string, len(ps.active))
idx := 0 idx := 0
for id := range ps.peers { for id := range ps.active {
res[idx] = id res[idx] = id
idx++ idx++
} }
@ -898,15 +947,18 @@ func (ps *peerSet) Peer(id string) *peer {
ps.lock.RLock() ps.lock.RLock()
defer ps.lock.RUnlock() defer ps.lock.RUnlock()
return ps.peers[id] if p, ok := ps.active[id]; ok {
return p
}
return ps.inactive[id]
} }
// Len returns if the current number of peers in the set. // Len returns if the current number of peers in the active set.
func (ps *peerSet) Len() int { func (ps *peerSet) Len() int {
ps.lock.RLock() ps.lock.RLock()
defer ps.lock.RUnlock() defer ps.lock.RUnlock()
return len(ps.peers) return len(ps.active)
} }
// BestPeer retrieves the known peer with the currently highest total difficulty. // BestPeer retrieves the known peer with the currently highest total difficulty.
@ -918,7 +970,7 @@ func (ps *peerSet) BestPeer() *peer {
bestPeer *peer bestPeer *peer
bestTd *big.Int bestTd *big.Int
) )
for _, p := range ps.peers { for _, p := range ps.active {
if td := p.Td(); bestPeer == nil || td.Cmp(bestTd) > 0 { if td := p.Td(); bestPeer == nil || td.Cmp(bestTd) > 0 {
bestPeer, bestTd = p, td bestPeer, bestTd = p, td
} }
@ -926,14 +978,14 @@ func (ps *peerSet) BestPeer() *peer {
return bestPeer return bestPeer
} }
// AllPeers returns all peers in a list // AllPeers returns all active peers in a list
func (ps *peerSet) AllPeers() []*peer { func (ps *peerSet) AllPeers() []*peer {
ps.lock.RLock() ps.lock.RLock()
defer ps.lock.RUnlock() defer ps.lock.RUnlock()
list := make([]*peer, len(ps.peers)) list := make([]*peer, len(ps.active))
i := 0 i := 0
for _, peer := range ps.peers { for _, peer := range ps.active {
list[i] = peer list[i] = peer
i++ i++
} }
@ -946,7 +998,10 @@ func (ps *peerSet) Close() {
ps.lock.Lock() ps.lock.Lock()
defer ps.lock.Unlock() defer ps.lock.Unlock()
for _, p := range ps.peers { for _, p := range ps.active {
p.Disconnect(p2p.DiscQuitting)
}
for _, p := range ps.inactive {
p.Disconnect(p2p.DiscQuitting) p.Disconnect(p2p.DiscQuitting)
} }
ps.closed = true ps.closed = true

View file

@ -345,7 +345,7 @@ func (r *sentReq) tryRequest() {
if hrto { if hrto {
pp.Log().Debug("Request timed out hard") pp.Log().Debug("Request timed out hard")
if r.rm.peers != nil { if r.rm.peers != nil {
r.rm.peers.Unregister(pp.id) r.rm.peers.Disconnect(pp.id)
} }
} }

View file

@ -116,7 +116,7 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
srv.maxCapacity = totalRecharge srv.maxCapacity = totalRecharge
} }
srv.fcManager.SetCapacityLimits(srv.freeCapacity, srv.maxCapacity, srv.freeCapacity*2) srv.fcManager.SetCapacityLimits(srv.freeCapacity, srv.maxCapacity, srv.freeCapacity*2)
srv.clientPool = newClientPool(srv.chainDb, srv.minCapacity, srv.freeCapacity, mclock.System{}, func(id enode.ID) { go srv.peers.Unregister(peerIdToString(id)) }) srv.clientPool = newClientPool(srv.chainDb, srv.minCapacity, srv.freeCapacity, mclock.System{}, func(id enode.ID) { go srv.peers.Disconnect(peerIdToString(id)) })
srv.clientPool.setDefaultFactors(priceFactors{0, 1, 1}, priceFactors{0, 1, 1}) srv.clientPool.setDefaultFactors(priceFactors{0, 1, 1}, priceFactors{0, 1, 1})
srv.tokenSale = newTokenSale(srv.clientPool, 0.1) srv.tokenSale = newTokenSale(srv.clientPool, 0.1)

View file

@ -58,10 +58,7 @@ const (
MaxTxStatus = 256 // Amount of transactions to queried per request MaxTxStatus = 256 // Amount of transactions to queried per request
) )
var ( var errTooManyInvalidRequest = errors.New("too many invalid requests made")
errTooManyInvalidRequest = errors.New("too many invalid requests made")
errFullClientPool = errors.New("client pool is full")
)
// serverHandler is responsible for serving light client and process // serverHandler is responsible for serving light client and process
// all incoming light requests. // all incoming light requests.
@ -139,28 +136,58 @@ func (h *serverHandler) handle(p *peer) error {
} }
defer p.fcClient.Disconnect() defer p.fcClient.Disconnect()
// Disconnect the inbound peer if it's rejected by clientPool var (
if !h.server.clientPool.connect(p, 0) { connectedAt mclock.AbsTime
p.Log().Debug("Light Ethereum peer registration failed", "err", errFullClientPool) wg *sync.WaitGroup // Wait group used to track all in-flight task routines.
return errFullClientPool )
} p.activate = func() {
// Register the peer locally // Register the peer locally
if err := h.server.peers.Register(p); err != nil { if err := h.server.peers.Register(p); err != nil {
h.server.clientPool.disconnect(p) h.server.clientPool.disconnect(p)
p.Log().Error("Light Ethereum peer registration failed", "err", err) p.Log().Error("Light Ethereum peer registration failed", "err", err)
return err return
} }
clientConnectionGauge.Update(int64(h.server.peers.Len())) clientConnectionGauge.Update(int64(h.server.peers.Len()))
connectedAt = mclock.Now()
var wg sync.WaitGroup // Wait group used to track all in-flight task routines. wg = new(sync.WaitGroup)
p.active = true
connectedAt := mclock.Now() }
defer func() { p.deactivate = func() {
wg.Wait() // Ensure all background task routines have exited. h.server.peers.Unregister(p)
h.server.peers.Unregister(p.id) if p.version < lpv4 {
h.server.clientPool.disconnect(p) h.server.peers.Disconnect(p.id)
}
clientConnectionGauge.Update(int64(h.server.peers.Len())) clientConnectionGauge.Update(int64(h.server.peers.Len()))
connectionTimer.Update(time.Duration(mclock.Now() - connectedAt)) connectionTimer.Update(time.Duration(mclock.Now() - connectedAt))
p.active = false
}
if p.active {
p.activate()
}
if capacity, err := h.server.clientPool.connect(p, 0); err != nil {
// Disconnect the inbound peer if it's rejected by clientPool
p.Log().Debug("Light Ethereum peer registration failed", "err", err)
return err
} else if capacity != p.fcParams.MinRecharge {
if p.version < lpv4 {
h.server.peers.Disconnect(p.id)
} else {
p.updateCapacity(capacity)
}
}
defer func() {
wg.Wait() // Ensure all background task routines have exited.
h.server.clientPool.disconnect(p)
p.responseLock.Lock()
if p.active {
p.deactivate()
}
p.activate = nil
p.deactivate = nil
p.responseLock.Unlock()
h.server.peers.Disconnect(p.id)
}() }()
// Spawn a main loop to handle all incoming messages. // Spawn a main loop to handle all incoming messages.
@ -171,7 +198,7 @@ func (h *serverHandler) handle(p *peer) error {
return err return err
default: default:
} }
if err := h.handleMsg(p, &wg); err != nil { if err := h.handleMsg(p, wg); err != nil {
p.Log().Debug("Light Ethereum message handling failed", "err", err) p.Log().Debug("Light Ethereum message handling failed", "err", err)
return err return err
} }

View file

@ -77,10 +77,10 @@ var (
processConfirms = big.NewInt(1) processConfirms = big.NewInt(1)
// The token bucket buffer limit for testing purpose. // The token bucket buffer limit for testing purpose.
testBufLimit = uint64(1000000) testBufLimit = uint64(6000)
// The buffer recharging speed for testing purpose. // The buffer recharging speed for testing purpose.
testBufRecharge = uint64(1000) testBufRecharge = uint64(1)
) )
/* /*
@ -393,8 +393,13 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu
expList = expList.add("serveStateSince", uint64(0)) expList = expList.add("serveStateSince", uint64(0))
expList = expList.add("serveRecentState", uint64(core.TriesInMemory-4)) expList = expList.add("serveRecentState", uint64(core.TriesInMemory-4))
expList = expList.add("txRelay", nil) expList = expList.add("txRelay", nil)
if p.peer.version >= lpv4 {
expList = expList.add("flowControl/BL", uint64(0))
expList = expList.add("flowControl/MRR", uint64(0))
} else {
expList = expList.add("flowControl/BL", testBufLimit) expList = expList.add("flowControl/BL", testBufLimit)
expList = expList.add("flowControl/MRR", testBufRecharge) expList = expList.add("flowControl/MRR", testBufRecharge)
}
expList = expList.add("flowControl/MRC", costList) expList = expList.add("flowControl/MRC", costList)
if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil { if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil {
@ -403,9 +408,16 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu
if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { if err := p2p.Send(p.app, StatusMsg, sendList); err != nil {
t.Fatalf("status send: %v", err) t.Fatalf("status send: %v", err)
} }
p.peer.fcParams = flowcontrol.ServerParams{ }
BufLimit: testBufLimit,
MinRecharge: testBufRecharge, func (p *testPeer) expectCapUpdate(t *testing.T) {
if p.peer.version >= lpv4 {
var expList keyValueList
expList = expList.add("flowControl/BL", testBufLimit)
expList = expList.add("flowControl/MRR", testBufRecharge)
if err := p2p.ExpectMsg(p.app, AnnounceMsg, announceData{Update: expList}); err != nil {
t.Fatalf("status recv: %v", err)
}
} }
} }
@ -436,7 +448,7 @@ type testServer struct {
bloomTrieIndexer *core.ChainIndexer bloomTrieIndexer *core.ChainIndexer
} }
func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, simClock bool, newPeer bool, testCost uint64) (*testServer, func()) { func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, simClock bool, newPeer bool, testCost uint64, expectCapUpdate bool) (*testServer, func()) {
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
indexers := testIndexers(db, nil, light.TestServerIndexerConfig) indexers := testIndexers(db, nil, light.TestServerIndexerConfig)
@ -477,6 +489,9 @@ func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallba
cIndexer.Close() cIndexer.Close()
bIndexer.Close() bIndexer.Close()
} }
if expectCapUpdate {
server.peer.expectCapUpdate(t)
}
return server, teardown return server, teardown
} }

View file

@ -126,7 +126,7 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr
// newServerPeer creates server peer. // newServerPeer creates server peer.
func newServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) { func newServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) {
s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0) s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0, false)
key, err := crypto.GenerateKey() key, err := crypto.GenerateKey()
if err != nil { if err != nil {
t.Fatal("generate key err:", err) t.Fatal("generate key err:", err)