@@ -133,6 +133,7 @@ proc wrapOutgoingMessage*(
133133 channelId: channelId,
134134 content: message,
135135 bloomFilter: bfResult.get (),
136+ senderId: rm.participantId,
136137 repairRequest: repairReqs,
137138 )
138139
@@ -144,7 +145,16 @@ proc wrapOutgoingMessage*(
144145 channel.bloomFilter.add (msg.messageId)
145146 rm.addToHistory (msg.messageId, channelId)
146147
147- return serializeMessage (msg)
148+ # SDS-R: record sender for future causal-history entries
149+ if rm.participantId.len > 0 :
150+ channel.messageSenders[msg.messageId] = rm.participantId
151+
152+ let serialized = serializeMessage (msg)
153+ if serialized.isOk ():
154+ # SDS-R: cache serialized bytes so we can serve our own message on repair
155+ if channel.messageCache.len < rm.config.maxMessageHistory:
156+ channel.messageCache[msg.messageId] = serialized.get ()
157+ return serialized
148158 except Exception :
149159 error " Failed to wrap message" ,
150160 channelId = channelId, msg = getCurrentExceptionMsg ()
@@ -213,6 +223,11 @@ proc unwrapReceivedMessage*(
213223
214224 let channel = rm.getOrCreateChannel (channelId)
215225
226+ # SDS-R: opportunistic repair-buffer cleanup — applies to duplicates too,
227+ # so rebroadcasts cancel redundant responses on peers that already have the message.
228+ channel.outgoingRepairBuffer.del (msg.messageId)
229+ channel.incomingRepairBuffer.del (msg.messageId)
230+
216231 if msg.messageId in channel.messageHistory:
217232 return ok ((msg.content, @ [], channelId))
218233
@@ -222,14 +237,14 @@ proc unwrapReceivedMessage*(
222237 # Review ACK status for outgoing messages
223238 rm.reviewAckStatus (msg)
224239
225- # SDS-R: remove this message from repair buffers (dependency now met)
226- channel.outgoingRepairBuffer.del (msg.messageId)
227- channel.incomingRepairBuffer.del (msg.messageId)
228-
229240 # SDS-R: cache the raw message for potential repair responses
230241 if channel.messageCache.len < rm.config.maxMessageHistory:
231242 channel.messageCache[msg.messageId] = message
232243
244+ # SDS-R: record sender so our future causal-history entries carry it
245+ if msg.senderId.len > 0 :
246+ channel.messageSenders[msg.messageId] = msg.senderId
247+
233248 # SDS-R: process incoming repair requests from this message
234249 let now = getTime ()
235250 for repairEntry in msg.repairRequest:
@@ -267,6 +282,10 @@ proc unwrapReceivedMessage*(
267282 else :
268283 # All dependencies met, add to history
269284 rm.addToHistory (msg.messageId, channelId)
285+ # Unblock any buffered messages that were waiting on this one.
286+ for pendingId, entry in channel.incomingBuffer:
287+ if msg.messageId in entry.missingDeps:
288+ channel.incomingBuffer[pendingId].missingDeps.excl (msg.messageId)
270289 rm.processIncomingBuffer (channelId)
271290 if not rm.onMessageReady.isNil ():
272291 rm.onMessageReady (msg.messageId, channelId)
@@ -415,43 +434,47 @@ proc periodicSyncMessage(
415434 error " Error in periodic sync" , msg = getCurrentExceptionMsg ()
416435 await sleepAsync (chronos.seconds (rm.config.syncMessageInterval.inSeconds))
417436
437+ proc runRepairSweep * (rm: ReliabilityManager ) {.gcsafe , raises : [].} =
438+ # # SDS-R: Runs a single pass of the repair sweep.
439+ # # - Incoming: fires onRepairReady for expired T_resp entries and removes them
440+ # # - Outgoing: drops entries past T_max window
441+ # # Exposed so it can be driven directly in tests; also invoked by periodicRepairSweep.
442+ try :
443+ let now = getTime ()
444+ for channelId, channel in rm.channels:
445+ try :
446+ # Check incoming repair buffer for expired T_resp (time to rebroadcast)
447+ var toRebroadcast: seq [SdsMessageID ] = @ []
448+ for msgId, entry in channel.incomingRepairBuffer:
449+ if now >= entry.tResp:
450+ toRebroadcast.add (msgId)
451+
452+ for msgId in toRebroadcast:
453+ let entry = channel.incomingRepairBuffer[msgId]
454+ channel.incomingRepairBuffer.del (msgId)
455+ if not rm.onRepairReady.isNil ():
456+ rm.onRepairReady (entry.cachedMessage, channelId)
457+
458+ # Drop expired outgoing repair entries past T_max
459+ var toRemove: seq [SdsMessageID ] = @ []
460+ let tMaxDuration = rm.config.repairTMax
461+ for msgId, entry in channel.outgoingRepairBuffer:
462+ if now - entry.tReq > tMaxDuration:
463+ toRemove.add (msgId)
464+ for msgId in toRemove:
465+ channel.outgoingRepairBuffer.del (msgId)
466+ except Exception :
467+ error " Error in repair sweep for channel" ,
468+ channelId = channelId, msg = getCurrentExceptionMsg ()
469+ except Exception :
470+ error " Error in repair sweep" , msg = getCurrentExceptionMsg ()
471+
418472proc periodicRepairSweep (
419473 rm: ReliabilityManager
420474) {.async : (raises: [CancelledError ]), gcsafe .} =
421475 # # SDS-R: Periodically checks repair buffers for expired entries.
422- # # - Incoming: fires onRepairReady for expired T_resp entries
423- # # - Outgoing: drops entries past T_max
424476 while true :
425- try :
426- let now = getTime ()
427- for channelId, channel in rm.channels:
428- try :
429- # Check incoming repair buffer for expired T_resp (time to rebroadcast)
430- var toRebroadcast: seq [SdsMessageID ] = @ []
431- for msgId, entry in channel.incomingRepairBuffer:
432- if now >= entry.tResp:
433- toRebroadcast.add (msgId)
434-
435- for msgId in toRebroadcast:
436- let entry = channel.incomingRepairBuffer[msgId]
437- channel.incomingRepairBuffer.del (msgId)
438- if not rm.onRepairReady.isNil ():
439- rm.onRepairReady (entry.cachedMessage, channelId)
440-
441- # Drop expired outgoing repair entries past T_max
442- var toRemove: seq [SdsMessageID ] = @ []
443- let tMaxDuration = rm.config.repairTMax
444- for msgId, entry in channel.outgoingRepairBuffer:
445- if now - entry.tReq > tMaxDuration:
446- toRemove.add (msgId)
447- for msgId in toRemove:
448- channel.outgoingRepairBuffer.del (msgId)
449- except Exception :
450- error " Error in repair sweep for channel" ,
451- channelId = channelId, msg = getCurrentExceptionMsg ()
452- except Exception :
453- error " Error in periodic repair sweep" , msg = getCurrentExceptionMsg ()
454-
477+ rm.runRepairSweep ()
455478 await sleepAsync (chronos.milliseconds (rm.config.repairSweepInterval.inMilliseconds))
456479
457480proc startPeriodicTasks * (rm: ReliabilityManager ) =
@@ -476,6 +499,7 @@ proc resetReliabilityManager*(rm: ReliabilityManager): Result[void, ReliabilityE
476499 channel.outgoingRepairBuffer.clear ()
477500 channel.incomingRepairBuffer.clear ()
478501 channel.messageCache.clear ()
502+ channel.messageSenders.clear ()
479503 channel.bloomFilter = newRollingBloomFilter (
480504 rm.config.bloomFilterCapacity, rm.config.bloomFilterErrorRate
481505 )
0 commit comments