Skip to content

Commit 6142183

Browse files
authored
Fluffy: Limit concurrent offers that can be received from each peer (#2885)
* Limit offer transfers per peer. * Remove pending transfers in prune. * Limit content lookups. * Improve performance of canAddPendingTransfer and addPendingTransfer.
1 parent c0199e8 commit 6142183

File tree

2 files changed

+105
-20
lines changed

2 files changed

+105
-20
lines changed

fluffy/network/wire/portal_protocol.nim

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ proc handleFindContent(
408408
)
409409

410410
# Check first if content is in range, as this is a cheaper operation
411-
if p.inRange(contentId):
411+
if p.inRange(contentId) and p.stream.canAddPendingTransfer(srcId, contentId):
412412
let contentResult = p.dbGet(fc.contentKey, contentId)
413413
if contentResult.isOk():
414414
let content = contentResult.get()
@@ -419,7 +419,8 @@ proc handleFindContent(
419419
)
420420
)
421421
else:
422-
let connectionId = p.stream.addContentRequest(srcId, content)
422+
p.stream.addPendingTransfer(srcId, contentId)
423+
let connectionId = p.stream.addContentRequest(srcId, contentId, content)
423424

424425
return encodeMessage(
425426
ContentMessage(
@@ -448,8 +449,10 @@ proc handleOffer(p: PortalProtocol, o: OfferMessage, srcId: NodeId): seq[byte] =
448449
)
449450
)
450451

451-
var contentKeysBitList = ContentKeysBitList.init(o.contentKeys.len)
452-
var contentKeys = ContentKeysList.init(@[])
452+
var
453+
contentKeysBitList = ContentKeysBitList.init(o.contentKeys.len)
454+
contentKeys = ContentKeysList.init(@[])
455+
contentIds = newSeq[ContentId]()
453456
# TODO: Do we need some protection against a peer offering lots (64x) of
454457
# content that fits our Radius but is actually bogus?
455458
# Additional TODO, but more of a specification clarification: What if we don't
@@ -465,17 +468,19 @@ proc handleOffer(p: PortalProtocol, o: OfferMessage, srcId: NodeId): seq[byte] =
465468
int64(logDistance), labelValues = [$p.protocolId]
466469
)
467470

468-
if p.inRange(contentId):
469-
if not p.dbContains(contentKey, contentId):
470-
contentKeysBitList.setBit(i)
471-
discard contentKeys.add(contentKey)
471+
if p.inRange(contentId) and p.stream.canAddPendingTransfer(srcId, contentId) and
472+
not p.dbContains(contentKey, contentId):
473+
p.stream.addPendingTransfer(srcId, contentId)
474+
contentKeysBitList.setBit(i)
475+
discard contentKeys.add(contentKey)
476+
contentIds.add(contentId)
472477
else:
473478
# Return empty response when content key validation fails
474479
return @[]
475480

476481
let connectionId =
477482
if contentKeysBitList.countOnes() != 0:
478-
p.stream.addContentOffer(srcId, contentKeys)
483+
p.stream.addContentOffer(srcId, contentKeys, contentIds)
479484
else:
480485
# When the node does not accept any of the content offered, reply with an
481486
# all zeroes bitlist and connectionId.

fluffy/network/wire/portal_stream.nim

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{.push raises: [].}
99

1010
import
11-
std/sequtils,
11+
std/[sequtils, sets],
1212
chronos,
1313
stew/[byteutils, leb128, endians2],
1414
chronicles,
@@ -35,17 +35,20 @@ const
3535
talkReqOverhead = getTalkReqOverhead(utpProtocolId)
3636
utpHeaderOverhead = 20
3737
maxUtpPayloadSize = maxDiscv5PacketSize - talkReqOverhead - utpHeaderOverhead
38+
maxPendingTransfersPerPeer = 128
3839

3940
type
4041
ContentRequest = object
4142
connectionId: uint16
4243
nodeId: NodeId
44+
contentId: ContentId
4345
content: seq[byte]
4446
timeout: Moment
4547

4648
ContentOffer = object
4749
connectionId: uint16
4850
nodeId: NodeId
51+
contentIds: seq[ContentId]
4952
contentKeys: ContentKeysList
5053
timeout: Moment
5154

@@ -69,28 +72,97 @@ type
6972
connectionTimeout: Duration
7073
contentReadTimeout*: Duration
7174
rng: ref HmacDrbgContext
75+
pendingTransfers: TableRef[NodeId, HashSet[ContentId]]
7276
contentQueue*: AsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])]
7377

7478
StreamManager* = ref object
7579
transport: UtpDiscv5Protocol
7680
streams: seq[PortalStream]
7781
rng: ref HmacDrbgContext
7882

83+
proc canAddPendingTransfer(
84+
transfers: TableRef[NodeId, HashSet[ContentId]],
85+
nodeId: NodeId,
86+
contentId: ContentId,
87+
limit: int,
88+
): bool =
89+
if not transfers.contains(nodeId):
90+
return true
91+
92+
try:
93+
let contentIds = transfers[nodeId]
94+
(contentIds.len() < limit) and not contentIds.contains(contentId)
95+
except KeyError as e:
96+
raiseAssert(e.msg)
97+
98+
proc addPendingTransfer(
99+
transfers: TableRef[NodeId, HashSet[ContentId]],
100+
nodeId: NodeId,
101+
contentId: ContentId,
102+
) =
103+
if transfers.contains(nodeId):
104+
try:
105+
transfers[nodeId].incl(contentId)
106+
except KeyError as e:
107+
raiseAssert(e.msg)
108+
else:
109+
var contentIds = initHashSet[ContentId]()
110+
contentIds.incl(contentId)
111+
transfers[nodeId] = contentIds
112+
113+
proc removePendingTransfer(
114+
transfers: TableRef[NodeId, HashSet[ContentId]],
115+
nodeId: NodeId,
116+
contentId: ContentId,
117+
) =
118+
doAssert transfers.contains(nodeId)
119+
120+
try:
121+
transfers[nodeId].excl(contentId)
122+
123+
if transfers[nodeId].len() == 0:
124+
transfers.del(nodeId)
125+
except KeyError as e:
126+
raiseAssert(e.msg)
127+
128+
template canAddPendingTransfer*(
129+
stream: PortalStream, nodeId: NodeId, contentId: ContentId
130+
): bool =
131+
stream.pendingTransfers.canAddPendingTransfer(
132+
srcId, contentId, maxPendingTransfersPerPeer
133+
)
134+
135+
template addPendingTransfer*(
136+
stream: PortalStream, nodeId: NodeId, contentId: ContentId
137+
) =
138+
addPendingTransfer(stream.pendingTransfers, nodeId, contentId)
139+
140+
template removePendingTransfer*(
141+
stream: PortalStream, nodeId: NodeId, contentId: ContentId
142+
) =
143+
removePendingTransfer(stream.pendingTransfers, nodeId, contentId)
144+
79145
proc pruneAllowedConnections(stream: PortalStream) =
80146
# Prune requests and offers that didn't receive a connection request
81147
# before `connectionTimeout`.
82148
let now = Moment.now()
83-
stream.contentRequests.keepIf(
84-
proc(x: ContentRequest): bool =
85-
x.timeout > now
86-
)
87-
stream.contentOffers.keepIf(
88-
proc(x: ContentOffer): bool =
89-
x.timeout > now
90-
)
149+
150+
for i, request in stream.contentRequests:
151+
if request.timeout <= now:
152+
stream.removePendingTransfer(request.nodeId, request.contentId)
153+
stream.contentRequests.del(i)
154+
155+
for i, offer in stream.contentOffers:
156+
if offer.timeout <= now:
157+
for contentId in offer.contentIds:
158+
stream.removePendingTransfer(offer.nodeId, contentId)
159+
stream.contentOffers.del(i)
91160

92161
proc addContentOffer*(
93-
stream: PortalStream, nodeId: NodeId, contentKeys: ContentKeysList
162+
stream: PortalStream,
163+
nodeId: NodeId,
164+
contentKeys: ContentKeysList,
165+
contentIds: seq[ContentId],
94166
): Bytes2 =
95167
stream.pruneAllowedConnections()
96168

@@ -107,6 +179,7 @@ proc addContentOffer*(
107179
let contentOffer = ContentOffer(
108180
connectionId: id,
109181
nodeId: nodeId,
182+
contentIds: contentIds,
110183
contentKeys: contentKeys,
111184
timeout: Moment.now() + stream.connectionTimeout,
112185
)
@@ -115,7 +188,7 @@ proc addContentOffer*(
115188
return connectionId
116189

117190
proc addContentRequest*(
118-
stream: PortalStream, nodeId: NodeId, content: seq[byte]
191+
stream: PortalStream, nodeId: NodeId, contentId: ContentId, content: seq[byte]
119192
): Bytes2 =
120193
stream.pruneAllowedConnections()
121194

@@ -129,6 +202,7 @@ proc addContentRequest*(
129202
let contentRequest = ContentRequest(
130203
connectionId: id,
131204
nodeId: nodeId,
205+
contentId: contentId,
132206
content: content,
133207
timeout: Moment.now() + stream.connectionTimeout,
134208
)
@@ -285,6 +359,7 @@ proc new(
285359
transport: transport,
286360
connectionTimeout: connectionTimeout,
287361
contentReadTimeout: contentReadTimeout,
362+
pendingTransfers: newTable[NodeId, HashSet[ContentId]](),
288363
contentQueue: contentQueue,
289364
rng: rng,
290365
)
@@ -317,13 +392,18 @@ proc handleIncomingConnection(
317392
if request.connectionId == socket.connectionId and
318393
request.nodeId == socket.remoteAddress.nodeId:
319394
let fut = socket.writeContentRequest(stream, request)
395+
396+
stream.removePendingTransfer(request.nodeId, request.contentId)
320397
stream.contentRequests.del(i)
321398
return noCancel(fut)
322399

323400
for i, offer in stream.contentOffers:
324401
if offer.connectionId == socket.connectionId and
325402
offer.nodeId == socket.remoteAddress.nodeId:
326403
let fut = socket.readContentOffer(stream, offer)
404+
405+
for contentId in offer.contentIds:
406+
stream.removePendingTransfer(offer.nodeId, contentId)
327407
stream.contentOffers.del(i)
328408
return noCancel(fut)
329409

0 commit comments

Comments
 (0)