finishup with score and ihave budget

This commit is contained in:
Giovanni Petrantoni 2020-08-17 01:20:50 +09:00
parent 1661344e17
commit afaa7f2a84
4 changed files with 85 additions and 22 deletions

View File

@ -11,7 +11,7 @@
{.push raises: [Defect].} {.push raises: [Defect].}
import nativesockets import nativesockets, hashes
import tables, strutils, stew/shims/net import tables, strutils, stew/shims/net
import chronos import chronos
import multicodec, multihash, multibase, transcoder, vbuffer, peerid, import multicodec, multihash, multibase, transcoder, vbuffer, peerid,
@ -56,6 +56,13 @@ const
IPPROTO_TCP = Protocol.IPPROTO_TCP IPPROTO_TCP = Protocol.IPPROTO_TCP
IPPROTO_UDP = Protocol.IPPROTO_UDP IPPROTO_UDP = Protocol.IPPROTO_UDP
proc hash*(a: MultiAddress): Hash =
var h: Hash = 0
h = h !& hash(a.data.buffer)
h = h !& hash(a.data.offset)
h = h !& hash(a.data.length)
!$h
proc ip4StB(s: string, vb: var VBuffer): bool = proc ip4StB(s: string, vb: var VBuffer): bool =
## IPv4 stringToBuffer() implementation. ## IPv4 stringToBuffer() implementation.
try: try:

View File

@ -56,6 +56,7 @@ const
const const
BackoffSlackTime = 2 # seconds BackoffSlackTime = 2 # seconds
IWantPeerBudget = 25 # 25 messages per second ( reset every heartbeat ) IWantPeerBudget = 25 # 25 messages per second ( reset every heartbeat )
IHavePeerBudget = 10
type type
TopicInfo* = object TopicInfo* = object
@ -146,6 +147,7 @@ type
parameters*: GossipSubParams parameters*: GossipSubParams
topicParams*: Table[string, TopicParams] topicParams*: Table[string, TopicParams]
directPeersLoop: Future[void] directPeersLoop: Future[void]
peersInIP: Table[MultiAddress, HashSet[PubSubPeer]]
heartbeatEvents*: seq[AsyncEvent] heartbeatEvents*: seq[AsyncEvent]
@ -282,6 +284,7 @@ method onNewPeer(g: GossipSub, peer: PubSubPeer) =
# new peer # new peer
g.peerStats[peer] = PeerStats() g.peerStats[peer] = PeerStats()
peer.iWantBudget = IWantPeerBudget peer.iWantBudget = IWantPeerBudget
peer.iHaveBudget = IHavePeerBudget
return return
else: else:
# we knew this peer # we knew this peer
@ -618,6 +621,24 @@ func `/`(a, b: Duration): float64 =
fb = float64(b.nanoseconds) / 1000000000 fb = float64(b.nanoseconds) / 1000000000
fa / fb fa / fb
proc colocationFactor(g: GossipSub, peer: PubSubPeer): float64 =
if peer.sendConn == nil:
0.0
else:
let
address = peer.sendConn.observedAddr
ipPeers = g.peersInIP.getOrDefault(address)
len = ipPeers.len.float64
if len > g.parameters.ipColocationFactorThreshold:
let over = len - g.parameters.ipColocationFactorThreshold
over * over
else:
# lazy update peersInIP
if address notin g.peersInIP:
g.peersInIP[address] = initHashSet[PubSubPeer]()
g.peersInIP[address].incl(peer)
0.0
proc updateScores(g: GossipSub) = # avoid async proc updateScores(g: GossipSub) = # avoid async
trace "updating scores", peers = g.peers.len trace "updating scores", peers = g.peers.len
@ -694,7 +715,18 @@ proc updateScores(g: GossipSub) = # avoid async
# Wrap up # Wrap up
# commit our changes, mgetOrPut does NOT work as wanted with value types (lent?) # commit our changes, mgetOrPut does NOT work as wanted with value types (lent?)
stats.topicInfos[topic] = info stats.topicInfos[topic] = info
peer.score += peer.appScore * g.parameters.appSpecificWeight
peer.score += peer.behaviourPenalty * peer.behaviourPenalty * g.parameters.behaviourPenaltyWeight
peer.score += g.colocationFactor(peer) * g.parameters.ipColocationFactorWeight
# decay behaviourPenalty
peer.behaviourPenalty *= g.parameters.behaviourPenaltyDecay
if peer.behaviourPenalty < g.parameters.decayToZero:
peer.behaviourPenalty = 0
trace "updated peer's score", peer, score = peer.score trace "updated peer's score", peer, score = peer.score
for peer in evicting: for peer in evicting:
@ -715,9 +747,11 @@ proc heartbeat(g: GossipSub) {.async.} =
g.backingOff.del(peer) g.backingOff.del(peer)
# reset IWANT budget # reset IWANT budget
# reset IHAVE cap
block: block:
for peer in g.peers.values: for peer in g.peers.values:
peer.iWantBudget = IWantPeerBudget peer.iWantBudget = IWantPeerBudget
peer.iHaveBudget = IHavePeerBudget
g.updateScores() g.updateScores()
@ -779,6 +813,11 @@ method unsubscribePeer*(g: GossipSub, peer: PeerID) =
if pubSubPeer.isNil: if pubSubPeer.isNil:
return return
# remove from peer IPs collection too
if pubSubPeer.sendConn != nil:
g.peersInIP.withValue(pubSubPeer.sendConn.observedAddr, s) do:
s[].excl(pubSubPeer)
for t in toSeq(g.gossipsub.keys): for t in toSeq(g.gossipsub.keys):
g.gossipsub.removePeer(t, pubSubPeer) g.gossipsub.removePeer(t, pubSubPeer)
# also try to remove from explicit table here # also try to remove from explicit table here
@ -947,29 +986,38 @@ proc handlePrune(g: GossipSub, peer: PubSubPeer, prunes: seq[ControlPrune]) =
proc handleIHave(g: GossipSub, proc handleIHave(g: GossipSub,
peer: PubSubPeer, peer: PubSubPeer,
ihaves: seq[ControlIHave]): ControlIWant = ihaves: seq[ControlIHave]): ControlIWant =
for ihave in ihaves: if peer.score < g.parameters.gossipThreshold:
trace "peer sent ihave", trace "ihave: ignoring low score peer", peer = $peer, score = peer.score
peer = peer.id, topic = ihave.topicID, msgs = ihave.messageIDs elif peer.iHaveBudget == 0:
trace "ihave: ignoring out of budget peer", peer = $peer, score = peer.score
else:
dec peer.iHaveBudget
for ihave in ihaves:
trace "peer sent ihave",
peer = peer.id, topic = ihave.topicID, msgs = ihave.messageIDs
if ihave.topicID in g.mesh: if ihave.topicID in g.mesh:
for m in ihave.messageIDs: for m in ihave.messageIDs:
if m notin g.seen: if m notin g.seen:
result.messageIDs.add(m) result.messageIDs.add(m)
proc handleIWant(g: GossipSub, proc handleIWant(g: GossipSub,
peer: PubSubPeer, peer: PubSubPeer,
iwants: seq[ControlIWant]): seq[Message] = iwants: seq[ControlIWant]): seq[Message] =
for iwant in iwants: if peer.score < g.parameters.gossipThreshold:
for mid in iwant.messageIDs: trace "iwant: ignoring low score peer", peer = $peer, score = peer.score
trace "peer sent iwant", peer = peer.id, messageID = mid else:
let msg = g.mcache.get(mid) for iwant in iwants:
if msg.isSome: for mid in iwant.messageIDs:
# avoid spam trace "peer sent iwant", peer = peer.id, messageID = mid
if peer.iWantBudget > 0: let msg = g.mcache.get(mid)
result.add(msg.get()) if msg.isSome:
dec peer.iWantBudget # avoid spam
else: if peer.iWantBudget > 0:
return result.add(msg.get())
dec peer.iWantBudget
else:
return
proc punishPeer(g: GossipSub, peer: PubSubPeer, msg: Message) = proc punishPeer(g: GossipSub, peer: PubSubPeer, msg: Message) =
for t in msg.topicIDs: for t in msg.topicIDs:

View File

@ -39,7 +39,7 @@ type
PubSubPeer* = ref object of RootObj PubSubPeer* = ref object of RootObj
switch*: Switch # switch instance to dial peers switch*: Switch # switch instance to dial peers
codec*: string # the protocol that this peer joined from codec*: string # the protocol that this peer joined from
sendConn: Connection sendConn*: Connection
peerId*: PeerID peerId*: PeerID
handler*: RPCHandler handler*: RPCHandler
sentRpcCache: TimedCache[string] # cache for already sent messages sentRpcCache: TimedCache[string] # cache for already sent messages
@ -50,7 +50,10 @@ type
score*: float64 score*: float64
iWantBudget*: int iWantBudget*: int
outbound*: bool iHaveBudget*: int
outbound*: bool # if this is an outbound connection
appScore*: float64 # application specific score
behaviourPenalty*: float64 # the eventual penalty score
RPCHandler* = proc(peer: PubSubPeer, msg: seq[RPCMsg]): Future[void] {.gcsafe.} RPCHandler* = proc(peer: PubSubPeer, msg: seq[RPCMsg]): Future[void] {.gcsafe.}

View File

@ -251,6 +251,11 @@ proc upgradeIncoming(s: Switch, conn: Connection) {.async, gcsafe.} =
for muxer in s.muxers.values: for muxer in s.muxers.values:
ms.addHandler(muxer.codecs, muxer) ms.addHandler(muxer.codecs, muxer)
# add the mounted protocols
# notice this should be kept in sync ...
for handler in s.ms.handlers:
ms.handlers &= handler
# handle subsequent secure requests # handle subsequent secure requests
await ms.handle(sconn) await ms.handle(sconn)