Skip to content

Commit 405405e

Browse files
feat: upgrade testbench and claude.md file for better context
1 parent f732ef6 commit 405405e

6 files changed

Lines changed: 698 additions & 46 deletions

File tree

sds.nim

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
418472
proc 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

457480
proc 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
)

sds/message.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type
1717
channelId*: SdsChannelID
1818
content*: seq[byte]
1919
bloomFilter*: seq[byte]
20+
senderId*: SdsParticipantID ## SDS-R: original sender's participant ID
2021
repairRequest*: seq[HistoryEntry] ## Capped list of missing entries requesting repair (SDS-R)
2122

2223
UnacknowledgedMessage* = object

sds/protobuf.nim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ proc encode*(msg: SdsMessage): ProtoBuffer =
3434
pb.write(5, msg.content)
3535
pb.write(6, msg.bloomFilter)
3636

37+
if msg.senderId.len > 0:
38+
pb.write(7, msg.senderId)
39+
3740
for entry in msg.repairRequest:
3841
let entryPb = encodeHistoryEntry(entry)
3942
pb.write(13, entryPb.buffer)
@@ -78,6 +81,9 @@ proc decode*(T: type SdsMessage, buffer: seq[byte]): ProtobufResult[T] =
7881
if not ?pb.getField(6, msg.bloomFilter):
7982
msg.bloomFilter = @[] # Empty if not present
8083

84+
# SDS-R: decode senderId (field 7, optional)
85+
discard pb.getField(7, msg.senderId)
86+
8187
# SDS-R: decode repair request (field 13, optional)
8288
var repairBuffers: seq[seq[byte]]
8389
if pb.getRepeatedField(13, repairBuffers).isOk():

sds/sds_utils.nim

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type
5454
outgoingRepairBuffer*: Table[SdsMessageID, OutgoingRepairEntry]
5555
incomingRepairBuffer*: Table[SdsMessageID, IncomingRepairEntry]
5656
messageCache*: Table[SdsMessageID, seq[byte]] ## Cached serialized messages for repair responses
57+
messageSenders*: Table[SdsMessageID, SdsParticipantID] ## SDS-R: msgId -> original sender
5758

5859
ReliabilityManager* = ref object
5960
channels*: Table[SdsChannelID, ChannelContext]
@@ -109,6 +110,7 @@ proc cleanup*(rm: ReliabilityManager) {.raises: [].} =
109110
channel.outgoingRepairBuffer.clear()
110111
channel.incomingRepairBuffer.clear()
111112
channel.messageCache.clear()
113+
channel.messageSenders.clear()
112114
rm.channels.clear()
113115
except Exception:
114116
error "Error during cleanup", error = getCurrentExceptionMsg()
@@ -217,19 +219,20 @@ proc getRecentHistoryEntries*(
217219
rm: ReliabilityManager, n: int, channelId: SdsChannelID
218220
): seq[HistoryEntry] =
219221
## Get recent history entries for sending in causal history.
220-
## Populates retrieval hints for our own messages using the provider callback.
222+
## Populates retrieval hints and senderId (SDS-R) for each entry.
221223
try:
222224
if channelId in rm.channels:
223225
let channel = rm.channels[channelId]
224226
let recentMessageIds = channel.messageHistory[max(0, channel.messageHistory.len - n) .. ^1]
225-
if rm.onRetrievalHint.isNil():
226-
return toCausalHistory(recentMessageIds)
227-
else:
228-
var entries: seq[HistoryEntry] = @[]
229-
for msgId in recentMessageIds:
230-
let hint = rm.onRetrievalHint(msgId)
231-
entries.add(newHistoryEntry(msgId, hint))
232-
return entries
227+
var entries: seq[HistoryEntry] = @[]
228+
for msgId in recentMessageIds:
229+
var entry = HistoryEntry(messageId: msgId)
230+
if not rm.onRetrievalHint.isNil():
231+
entry.retrievalHint = rm.onRetrievalHint(msgId)
232+
if msgId in channel.messageSenders:
233+
entry.senderId = channel.messageSenders[msgId]
234+
entries.add(entry)
235+
return entries
233236
else:
234237
return @[]
235238
except Exception:
@@ -315,6 +318,7 @@ proc getOrCreateChannel*(
315318
outgoingRepairBuffer: initTable[SdsMessageID, OutgoingRepairEntry](),
316319
incomingRepairBuffer: initTable[SdsMessageID, IncomingRepairEntry](),
317320
messageCache: initTable[SdsMessageID, seq[byte]](),
321+
messageSenders: initTable[SdsMessageID, SdsParticipantID](),
318322
)
319323
result = rm.channels[channelId]
320324
except Exception:
@@ -347,6 +351,7 @@ proc removeChannel*(
347351
channel.outgoingRepairBuffer.clear()
348352
channel.incomingRepairBuffer.clear()
349353
channel.messageCache.clear()
354+
channel.messageSenders.clear()
350355
rm.channels.del(channelId)
351356
return ok()
352357
except Exception:

tests/test_bloom

-32 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)