From c25278d7e6b7cd47694034d58ca91595a3763f2a Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Wed, 1 Oct 2025 12:56:05 +1000 Subject: [PATCH] fix!: avoid SDS lamport timestamp overflow The SDS timestamp is initialized to the current time in milliseconds, which is a 13 digits value (e.g. 1,759,223,090,052). The maximum value for int32 is 2,147,483,647 (10 digits), which is clearly less than the timestamp. Maximum value for uint32 is 4,294,967,295 (10 digits), which does not help with ms timestamp. uint64 is BigInt in JavaScript, so best to be avoided unless strictly necessary as it creates complexity. max uint64 is 18,446,744,073,709,551,615 (20 digits). Using seconds instead of milliseconds would enable usage of uint32 valid until the year 2106. The lamport timestamp is only initialized to current time for a new channel. The only scenario is when a user comes in a channel, and thinks it's new (did not get previous messages), and then starts sending messages. Meaning that there may be an initial timestamp conflict until the logs are consolidated, which is already handled by the protocol. --- packages/proto/src/generated/sds_message.ts | 4 ++-- packages/proto/src/lib/sds_message.proto | 2 +- packages/sds/src/message_channel/message_channel.ts | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/proto/src/generated/sds_message.ts b/packages/proto/src/generated/sds_message.ts index 20ef8746ae..f91ef23cfa 100644 --- a/packages/proto/src/generated/sds_message.ts +++ b/packages/proto/src/generated/sds_message.ts @@ -117,7 +117,7 @@ export namespace SdsMessage { if (obj.lamportTimestamp != null) { w.uint32(80) - w.int32(obj.lamportTimestamp) + w.uint32(obj.lamportTimestamp) } if (obj.causalHistory != null) { @@ -167,7 +167,7 @@ export namespace SdsMessage { break } case 10: { - obj.lamportTimestamp = reader.int32() + obj.lamportTimestamp = reader.uint32() break } case 11: { diff --git a/packages/proto/src/lib/sds_message.proto b/packages/proto/src/lib/sds_message.proto index 5344a0d33a..626ae12525 100644 --- a/packages/proto/src/lib/sds_message.proto +++ b/packages/proto/src/lib/sds_message.proto @@ -9,7 +9,7 @@ message SdsMessage { string sender_id = 1; // Participant ID of the message sender string message_id = 2; // Unique identifier of the message string channel_id = 3; // Identifier of the channel to which the message belongs - optional int32 lamport_timestamp = 10; // Logical timestamp for causal ordering in channel + optional uint32 lamport_timestamp = 10; // Logical timestamp for causal ordering in channel repeated HistoryEntry causal_history = 11; // List of preceding message IDs that this message causally depends on. Generally 2 or 3 message IDs are included. optional bytes bloom_filter = 12; // Bloom filter representing received message IDs in channel optional bytes content = 20; // Actual content of the message diff --git a/packages/sds/src/message_channel/message_channel.ts b/packages/sds/src/message_channel/message_channel.ts index 6375e819f7..9b1ab0e885 100644 --- a/packages/sds/src/message_channel/message_channel.ts +++ b/packages/sds/src/message_channel/message_channel.ts @@ -95,9 +95,8 @@ export class MessageChannel extends TypedEventEmitter { super(); this.channelId = channelId; this.senderId = senderId; - // SDS RFC says to use nanoseconds, but current time in nanosecond is > Number.MAX_SAFE_INTEGER - // So instead we are using milliseconds and proposing a spec change (TODO) - this.lamportTimestamp = Date.now(); + // Initialize channel lamport timestamp to current time in seconds. + this.lamportTimestamp = Date.now() / 1000; this.filter = new DefaultBloomFilter(DEFAULT_BLOOM_FILTER_OPTIONS); this.outgoingBuffer = []; this.possibleAcks = new Map();