@waku/sds 0.0.8-d637425.0 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/bundle/index.js +614 -25
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js.map +1 -1
- package/dist/message_channel/events.d.ts +27 -3
- package/dist/message_channel/events.js +6 -0
- package/dist/message_channel/events.js.map +1 -1
- package/dist/message_channel/index.d.ts +1 -1
- package/dist/message_channel/message.d.ts +17 -13
- package/dist/message_channel/message.js +19 -11
- package/dist/message_channel/message.js.map +1 -1
- package/dist/message_channel/message_channel.d.ts +31 -3
- package/dist/message_channel/message_channel.js +99 -10
- package/dist/message_channel/message_channel.js.map +1 -1
- package/dist/message_channel/repair/buffers.d.ts +106 -0
- package/dist/message_channel/repair/buffers.js +206 -0
- package/dist/message_channel/repair/buffers.js.map +1 -0
- package/dist/message_channel/repair/repair.d.ts +92 -0
- package/dist/message_channel/repair/repair.js +209 -0
- package/dist/message_channel/repair/repair.js.map +1 -0
- package/dist/message_channel/repair/utils.d.ts +40 -0
- package/dist/message_channel/repair/utils.js +61 -0
- package/dist/message_channel/repair/utils.js.map +1 -0
- package/package.json +93 -1
- package/src/index.ts +7 -1
- package/src/message_channel/events.ts +28 -3
- package/src/message_channel/index.ts +1 -1
- package/src/message_channel/message.ts +24 -13
- package/src/message_channel/message_channel.ts +151 -17
- package/src/message_channel/repair/buffers.ts +277 -0
- package/src/message_channel/repair/repair.ts +331 -0
- package/src/message_channel/repair/utils.ts +80 -0
package/package.json
CHANGED
|
@@ -1 +1,93 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@waku/sds",
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"description": "Scalable Data Sync implementation for the browser. Based on https://github.com/vacp2p/rfc-index/blob/main/vac/raw/sds.md",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"typesVersions": {
|
|
14
|
+
"*": {
|
|
15
|
+
"*": [
|
|
16
|
+
"*",
|
|
17
|
+
"dist/*",
|
|
18
|
+
"dist/*/index"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"author": "Waku Team",
|
|
24
|
+
"homepage": "https://github.com/waku-org/js-waku/tree/master/packages/scalable-data-sync#readme",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/waku-org/js-waku.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/waku-org/js-waku/issues"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT OR Apache-2.0",
|
|
33
|
+
"keywords": [
|
|
34
|
+
"waku",
|
|
35
|
+
"decentralized",
|
|
36
|
+
"secure",
|
|
37
|
+
"communication",
|
|
38
|
+
"web3",
|
|
39
|
+
"ethereum",
|
|
40
|
+
"dapps",
|
|
41
|
+
"privacy"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "run-s build:**",
|
|
45
|
+
"build:esm": "tsc",
|
|
46
|
+
"build:bundle": "rollup --config rollup.config.js",
|
|
47
|
+
"fix": "run-s fix:*",
|
|
48
|
+
"fix:lint": "eslint src *.js --fix",
|
|
49
|
+
"check": "run-s check:*",
|
|
50
|
+
"check:lint": "eslint src *.js",
|
|
51
|
+
"check:spelling": "cspell \"{README.md,src/**/*.ts}\"",
|
|
52
|
+
"check:tsc": "tsc -p tsconfig.dev.json",
|
|
53
|
+
"prepublish": "npm run build",
|
|
54
|
+
"reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build",
|
|
55
|
+
"test": "NODE_ENV=test run-s test:*",
|
|
56
|
+
"test:node": "NODE_ENV=test TS_NODE_PROJECT=./tsconfig.dev.json mocha",
|
|
57
|
+
"test:browser": "NODE_ENV=test karma start karma.conf.cjs"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@libp2p/interface": "2.10.4",
|
|
64
|
+
"@noble/hashes": "^1.7.1",
|
|
65
|
+
"@waku/proto": "^0.0.15",
|
|
66
|
+
"@waku/utils": "^0.0.27",
|
|
67
|
+
"chai": "^5.1.2",
|
|
68
|
+
"lodash": "^4.17.21"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
72
|
+
"@rollup/plugin-json": "^6.0.0",
|
|
73
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
74
|
+
"@waku/build-utils": "*",
|
|
75
|
+
"allure-commandline": "^2.27.0",
|
|
76
|
+
"allure-mocha": "^2.9.2",
|
|
77
|
+
"cspell": "^8.6.1",
|
|
78
|
+
"fast-check": "^3.19.0",
|
|
79
|
+
"mocha-multi-reporters": "^1.5.1",
|
|
80
|
+
"npm-run-all": "^4.1.5",
|
|
81
|
+
"rollup": "^4.12.0"
|
|
82
|
+
},
|
|
83
|
+
"files": [
|
|
84
|
+
"dist",
|
|
85
|
+
"bundle",
|
|
86
|
+
"src/**/*.ts",
|
|
87
|
+
"!**/*.spec.*",
|
|
88
|
+
"!**/*.json",
|
|
89
|
+
"CHANGELOG.md",
|
|
90
|
+
"LICENSE",
|
|
91
|
+
"README.md"
|
|
92
|
+
]
|
|
93
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,8 +14,14 @@ export {
|
|
|
14
14
|
type HistoryEntry,
|
|
15
15
|
type ChannelId,
|
|
16
16
|
type MessageChannelEvents,
|
|
17
|
-
type
|
|
17
|
+
type ParticipantId,
|
|
18
18
|
type MessageId
|
|
19
19
|
} from "./message_channel/index.js";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use ParticipantId instead. SenderId has been renamed to ParticipantId
|
|
23
|
+
* to better reflect that it represents a channel participant, not just a message sender.
|
|
24
|
+
*/
|
|
25
|
+
export type { ParticipantId as SenderId } from "./message_channel/index.js";
|
|
26
|
+
|
|
21
27
|
export { BloomFilter };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HistoryEntry, Message, MessageId } from "./message.js";
|
|
1
|
+
import { HistoryEntry, Message, MessageId, ParticipantId } from "./message.js";
|
|
2
2
|
|
|
3
3
|
export enum MessageChannelEvent {
|
|
4
4
|
OutMessageSent = "sds:out:message-sent",
|
|
@@ -10,7 +10,13 @@ export enum MessageChannelEvent {
|
|
|
10
10
|
OutSyncSent = "sds:out:sync-sent",
|
|
11
11
|
InSyncReceived = "sds:in:sync-received",
|
|
12
12
|
InMessageLost = "sds:in:message-irretrievably-lost",
|
|
13
|
-
ErrorTask = "sds:error-task"
|
|
13
|
+
ErrorTask = "sds:error-task",
|
|
14
|
+
// SDS-R Repair Events
|
|
15
|
+
RepairRequestQueued = "sds:repair:request-queued",
|
|
16
|
+
RepairRequestSent = "sds:repair:request-sent",
|
|
17
|
+
RepairRequestReceived = "sds:repair:request-received",
|
|
18
|
+
RepairResponseQueued = "sds:repair:response-queued",
|
|
19
|
+
RepairResponseSent = "sds:repair:response-sent"
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export type MessageChannelEvents = {
|
|
@@ -26,5 +32,24 @@ export type MessageChannelEvents = {
|
|
|
26
32
|
[MessageChannelEvent.InMessageLost]: CustomEvent<HistoryEntry[]>;
|
|
27
33
|
[MessageChannelEvent.OutSyncSent]: CustomEvent<Message>;
|
|
28
34
|
[MessageChannelEvent.InSyncReceived]: CustomEvent<Message>;
|
|
29
|
-
[MessageChannelEvent.ErrorTask]: CustomEvent<
|
|
35
|
+
[MessageChannelEvent.ErrorTask]: CustomEvent<unknown>;
|
|
36
|
+
[MessageChannelEvent.RepairRequestQueued]: CustomEvent<{
|
|
37
|
+
messageId: MessageId;
|
|
38
|
+
tReq: number;
|
|
39
|
+
}>;
|
|
40
|
+
[MessageChannelEvent.RepairRequestSent]: CustomEvent<{
|
|
41
|
+
messageIds: MessageId[];
|
|
42
|
+
carrierMessageId: MessageId;
|
|
43
|
+
}>;
|
|
44
|
+
[MessageChannelEvent.RepairRequestReceived]: CustomEvent<{
|
|
45
|
+
messageIds: MessageId[];
|
|
46
|
+
fromSenderId?: ParticipantId;
|
|
47
|
+
}>;
|
|
48
|
+
[MessageChannelEvent.RepairResponseQueued]: CustomEvent<{
|
|
49
|
+
messageId: MessageId;
|
|
50
|
+
tResp: number;
|
|
51
|
+
}>;
|
|
52
|
+
[MessageChannelEvent.RepairResponseSent]: CustomEvent<{
|
|
53
|
+
messageId: MessageId;
|
|
54
|
+
}>;
|
|
30
55
|
};
|
|
@@ -4,19 +4,20 @@ import { Logger } from "@waku/utils";
|
|
|
4
4
|
export type MessageId = string;
|
|
5
5
|
export type HistoryEntry = proto_sds_message.HistoryEntry;
|
|
6
6
|
export type ChannelId = string;
|
|
7
|
-
export type
|
|
7
|
+
export type ParticipantId = string;
|
|
8
8
|
|
|
9
9
|
const log = new Logger("sds:message");
|
|
10
10
|
|
|
11
11
|
export class Message implements proto_sds_message.SdsMessage {
|
|
12
12
|
public constructor(
|
|
13
|
-
public messageId:
|
|
13
|
+
public messageId: MessageId,
|
|
14
14
|
public channelId: string,
|
|
15
|
-
public senderId:
|
|
15
|
+
public senderId: ParticipantId,
|
|
16
16
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
17
17
|
public lamportTimestamp?: bigint | undefined,
|
|
18
18
|
public bloomFilter?: Uint8Array<ArrayBufferLike> | undefined,
|
|
19
19
|
public content?: Uint8Array<ArrayBufferLike> | undefined,
|
|
20
|
+
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
20
21
|
/**
|
|
21
22
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
22
23
|
*/
|
|
@@ -38,7 +39,8 @@ export class Message implements proto_sds_message.SdsMessage {
|
|
|
38
39
|
causalHistory,
|
|
39
40
|
lamportTimestamp,
|
|
40
41
|
bloomFilter,
|
|
41
|
-
content
|
|
42
|
+
content,
|
|
43
|
+
repairRequest
|
|
42
44
|
} = proto_sds_message.SdsMessage.decode(data);
|
|
43
45
|
|
|
44
46
|
if (testContentMessage({ lamportTimestamp, content })) {
|
|
@@ -49,7 +51,8 @@ export class Message implements proto_sds_message.SdsMessage {
|
|
|
49
51
|
causalHistory,
|
|
50
52
|
lamportTimestamp!,
|
|
51
53
|
bloomFilter,
|
|
52
|
-
content
|
|
54
|
+
content!,
|
|
55
|
+
repairRequest
|
|
53
56
|
);
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -61,7 +64,8 @@ export class Message implements proto_sds_message.SdsMessage {
|
|
|
61
64
|
causalHistory,
|
|
62
65
|
undefined,
|
|
63
66
|
bloomFilter,
|
|
64
|
-
content
|
|
67
|
+
content!,
|
|
68
|
+
repairRequest
|
|
65
69
|
);
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -73,7 +77,8 @@ export class Message implements proto_sds_message.SdsMessage {
|
|
|
73
77
|
causalHistory,
|
|
74
78
|
lamportTimestamp!,
|
|
75
79
|
bloomFilter,
|
|
76
|
-
undefined
|
|
80
|
+
undefined,
|
|
81
|
+
repairRequest
|
|
77
82
|
);
|
|
78
83
|
}
|
|
79
84
|
log.error(
|
|
@@ -90,13 +95,14 @@ export class Message implements proto_sds_message.SdsMessage {
|
|
|
90
95
|
|
|
91
96
|
export class SyncMessage extends Message {
|
|
92
97
|
public constructor(
|
|
93
|
-
public messageId:
|
|
98
|
+
public messageId: MessageId,
|
|
94
99
|
public channelId: string,
|
|
95
|
-
public senderId:
|
|
100
|
+
public senderId: ParticipantId,
|
|
96
101
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
97
102
|
public lamportTimestamp: bigint,
|
|
98
103
|
public bloomFilter: Uint8Array<ArrayBufferLike> | undefined,
|
|
99
104
|
public content: undefined,
|
|
105
|
+
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
100
106
|
/**
|
|
101
107
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
102
108
|
*/
|
|
@@ -110,6 +116,7 @@ export class SyncMessage extends Message {
|
|
|
110
116
|
lamportTimestamp,
|
|
111
117
|
bloomFilter,
|
|
112
118
|
content,
|
|
119
|
+
repairRequest,
|
|
113
120
|
retrievalHint
|
|
114
121
|
);
|
|
115
122
|
}
|
|
@@ -134,13 +141,14 @@ export function isSyncMessage(
|
|
|
134
141
|
|
|
135
142
|
export class EphemeralMessage extends Message {
|
|
136
143
|
public constructor(
|
|
137
|
-
public messageId:
|
|
144
|
+
public messageId: MessageId,
|
|
138
145
|
public channelId: string,
|
|
139
|
-
public senderId:
|
|
146
|
+
public senderId: ParticipantId,
|
|
140
147
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
141
148
|
public lamportTimestamp: undefined,
|
|
142
149
|
public bloomFilter: Uint8Array<ArrayBufferLike> | undefined,
|
|
143
150
|
public content: Uint8Array<ArrayBufferLike>,
|
|
151
|
+
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
144
152
|
/**
|
|
145
153
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
146
154
|
*/
|
|
@@ -157,6 +165,7 @@ export class EphemeralMessage extends Message {
|
|
|
157
165
|
lamportTimestamp,
|
|
158
166
|
bloomFilter,
|
|
159
167
|
content,
|
|
168
|
+
repairRequest,
|
|
160
169
|
retrievalHint
|
|
161
170
|
);
|
|
162
171
|
}
|
|
@@ -182,13 +191,14 @@ function testEphemeralMessage(message: {
|
|
|
182
191
|
|
|
183
192
|
export class ContentMessage extends Message {
|
|
184
193
|
public constructor(
|
|
185
|
-
public messageId:
|
|
194
|
+
public messageId: MessageId,
|
|
186
195
|
public channelId: string,
|
|
187
|
-
public senderId:
|
|
196
|
+
public senderId: ParticipantId,
|
|
188
197
|
public causalHistory: proto_sds_message.HistoryEntry[],
|
|
189
198
|
public lamportTimestamp: bigint,
|
|
190
199
|
public bloomFilter: Uint8Array<ArrayBufferLike> | undefined,
|
|
191
200
|
public content: Uint8Array<ArrayBufferLike>,
|
|
201
|
+
public repairRequest: proto_sds_message.HistoryEntry[] = [],
|
|
192
202
|
/**
|
|
193
203
|
* Not encoded, set after it is sent, used to include in follow-up messages
|
|
194
204
|
*/
|
|
@@ -205,6 +215,7 @@ export class ContentMessage extends Message {
|
|
|
205
215
|
lamportTimestamp,
|
|
206
216
|
bloomFilter,
|
|
207
217
|
content,
|
|
218
|
+
repairRequest,
|
|
208
219
|
retrievalHint
|
|
209
220
|
);
|
|
210
221
|
}
|
|
@@ -18,15 +18,21 @@ import {
|
|
|
18
18
|
isSyncMessage,
|
|
19
19
|
Message,
|
|
20
20
|
MessageId,
|
|
21
|
-
|
|
21
|
+
ParticipantId,
|
|
22
22
|
SyncMessage
|
|
23
23
|
} from "./message.js";
|
|
24
|
+
import { RepairConfig, RepairManager } from "./repair/repair.js";
|
|
24
25
|
|
|
25
26
|
export const DEFAULT_BLOOM_FILTER_OPTIONS = {
|
|
26
27
|
capacity: 10000,
|
|
27
28
|
errorRate: 0.001
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Maximum number of repair requests to include in a single message
|
|
33
|
+
*/
|
|
34
|
+
const MAX_REPAIR_REQUESTS_PER_MESSAGE = 3;
|
|
35
|
+
|
|
30
36
|
const DEFAULT_CAUSAL_HISTORY_SIZE = 200;
|
|
31
37
|
const DEFAULT_POSSIBLE_ACKS_THRESHOLD = 2;
|
|
32
38
|
|
|
@@ -46,6 +52,15 @@ export interface MessageChannelOptions {
|
|
|
46
52
|
* How many possible acks does it take to consider it a definitive ack.
|
|
47
53
|
*/
|
|
48
54
|
possibleAcksThreshold?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Whether to enable SDS-R repair protocol.
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
enableRepair?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* SDS-R repair configuration. Only used if enableRepair is true.
|
|
62
|
+
*/
|
|
63
|
+
repairConfig?: RepairConfig;
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
export type ILocalHistory = Pick<
|
|
@@ -55,7 +70,7 @@ export type ILocalHistory = Pick<
|
|
|
55
70
|
|
|
56
71
|
export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
57
72
|
public readonly channelId: ChannelId;
|
|
58
|
-
public readonly senderId:
|
|
73
|
+
public readonly senderId: ParticipantId;
|
|
59
74
|
private lamportTimestamp: bigint;
|
|
60
75
|
private filter: DefaultBloomFilter;
|
|
61
76
|
private outgoingBuffer: ContentMessage[];
|
|
@@ -66,6 +81,7 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
66
81
|
private readonly causalHistorySize: number;
|
|
67
82
|
private readonly possibleAcksThreshold: number;
|
|
68
83
|
private readonly timeoutForLostMessagesMs?: number;
|
|
84
|
+
private readonly repairManager?: RepairManager;
|
|
69
85
|
|
|
70
86
|
private tasks: Task[] = [];
|
|
71
87
|
private handlers: Handlers = {
|
|
@@ -88,7 +104,7 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
88
104
|
|
|
89
105
|
public constructor(
|
|
90
106
|
channelId: ChannelId,
|
|
91
|
-
senderId:
|
|
107
|
+
senderId: ParticipantId,
|
|
92
108
|
options: MessageChannelOptions = {},
|
|
93
109
|
localHistory: ILocalHistory = new MemLocalHistory()
|
|
94
110
|
) {
|
|
@@ -109,6 +125,17 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
109
125
|
options.possibleAcksThreshold ?? DEFAULT_POSSIBLE_ACKS_THRESHOLD;
|
|
110
126
|
this.timeReceived = new Map();
|
|
111
127
|
this.timeoutForLostMessagesMs = options.timeoutForLostMessagesMs;
|
|
128
|
+
|
|
129
|
+
// Only construct RepairManager if repair is enabled (default: true)
|
|
130
|
+
if (options.enableRepair ?? true) {
|
|
131
|
+
this.repairManager = new RepairManager(
|
|
132
|
+
senderId,
|
|
133
|
+
options.repairConfig,
|
|
134
|
+
(event: string, detail: unknown) => {
|
|
135
|
+
this.safeSendEvent(event as MessageChannelEvent, { detail });
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
112
139
|
}
|
|
113
140
|
|
|
114
141
|
public static getMessageId(payload: Uint8Array): MessageId {
|
|
@@ -272,9 +299,7 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
272
299
|
);
|
|
273
300
|
const missingDependencies = message.causalHistory.filter(
|
|
274
301
|
(messageHistoryEntry) =>
|
|
275
|
-
!this.
|
|
276
|
-
({ messageId }) => messageId === messageHistoryEntry.messageId
|
|
277
|
-
)
|
|
302
|
+
!this.isMessageAvailable(messageHistoryEntry.messageId)
|
|
278
303
|
);
|
|
279
304
|
if (missingDependencies.length === 0) {
|
|
280
305
|
if (isContentMessage(message) && this.deliverMessage(message)) {
|
|
@@ -355,6 +380,44 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
355
380
|
);
|
|
356
381
|
}
|
|
357
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Sweep repair incoming buffer and rebroadcast messages ready for repair.
|
|
385
|
+
* Per SDS-R spec: periodically check for repair responses that are due.
|
|
386
|
+
*
|
|
387
|
+
* @param callback - callback to rebroadcast the message
|
|
388
|
+
* @returns Promise that resolves when all ready repairs have been sent
|
|
389
|
+
*/
|
|
390
|
+
public async sweepRepairIncomingBuffer(
|
|
391
|
+
callback?: (message: Message) => Promise<boolean>
|
|
392
|
+
): Promise<Message[]> {
|
|
393
|
+
const repairsToSend =
|
|
394
|
+
this.repairManager?.sweepIncomingBuffer(this.localHistory) ?? [];
|
|
395
|
+
|
|
396
|
+
if (callback) {
|
|
397
|
+
for (const message of repairsToSend) {
|
|
398
|
+
try {
|
|
399
|
+
await callback(message);
|
|
400
|
+
log.info(
|
|
401
|
+
this.senderId,
|
|
402
|
+
"repair message rebroadcast",
|
|
403
|
+
message.messageId
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
// Emit RepairResponseSent event
|
|
407
|
+
this.safeSendEvent(MessageChannelEvent.RepairResponseSent, {
|
|
408
|
+
detail: {
|
|
409
|
+
messageId: message.messageId
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
} catch (error) {
|
|
413
|
+
log.error("Failed to rebroadcast repair message:", error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return repairsToSend;
|
|
419
|
+
}
|
|
420
|
+
|
|
358
421
|
/**
|
|
359
422
|
* Send a sync message to the SDS channel.
|
|
360
423
|
*
|
|
@@ -369,6 +432,12 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
369
432
|
callback?: (message: SyncMessage) => Promise<boolean>
|
|
370
433
|
): Promise<boolean> {
|
|
371
434
|
this.lamportTimestamp = lamportTimestampIncrement(this.lamportTimestamp);
|
|
435
|
+
|
|
436
|
+
// Get repair requests to include in sync message (SDS-R)
|
|
437
|
+
const repairRequests =
|
|
438
|
+
this.repairManager?.getRepairRequests(MAX_REPAIR_REQUESTS_PER_MESSAGE) ??
|
|
439
|
+
[];
|
|
440
|
+
|
|
372
441
|
const message = new SyncMessage(
|
|
373
442
|
// does not need to be secure randomness
|
|
374
443
|
`sync-${Math.random().toString(36).substring(2)}`,
|
|
@@ -376,18 +445,22 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
376
445
|
this.senderId,
|
|
377
446
|
this.localHistory
|
|
378
447
|
.slice(-this.causalHistorySize)
|
|
379
|
-
.map(({ messageId, retrievalHint }) => {
|
|
380
|
-
return { messageId, retrievalHint };
|
|
448
|
+
.map(({ messageId, retrievalHint, senderId }) => {
|
|
449
|
+
return { messageId, retrievalHint, senderId };
|
|
381
450
|
}),
|
|
382
451
|
this.lamportTimestamp,
|
|
383
452
|
this.filter.toBytes(),
|
|
384
|
-
undefined
|
|
453
|
+
undefined,
|
|
454
|
+
repairRequests
|
|
385
455
|
);
|
|
386
456
|
|
|
387
|
-
if (
|
|
457
|
+
if (
|
|
458
|
+
(!message.causalHistory || message.causalHistory.length === 0) &&
|
|
459
|
+
repairRequests.length === 0
|
|
460
|
+
) {
|
|
388
461
|
log.info(
|
|
389
462
|
this.senderId,
|
|
390
|
-
"no causal history in sync message, aborting sending"
|
|
463
|
+
"no causal history and no repair requests in sync message, aborting sending"
|
|
391
464
|
);
|
|
392
465
|
return false;
|
|
393
466
|
}
|
|
@@ -399,6 +472,17 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
399
472
|
this.safeSendEvent(MessageChannelEvent.OutSyncSent, {
|
|
400
473
|
detail: message
|
|
401
474
|
});
|
|
475
|
+
|
|
476
|
+
// Emit RepairRequestSent event if repair requests were included
|
|
477
|
+
if (repairRequests.length > 0) {
|
|
478
|
+
this.safeSendEvent(MessageChannelEvent.RepairRequestSent, {
|
|
479
|
+
detail: {
|
|
480
|
+
messageIds: repairRequests.map((r) => r.messageId),
|
|
481
|
+
carrierMessageId: message.messageId
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
402
486
|
return true;
|
|
403
487
|
} catch (error) {
|
|
404
488
|
log.error(
|
|
@@ -464,6 +548,26 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
464
548
|
detail: message
|
|
465
549
|
});
|
|
466
550
|
}
|
|
551
|
+
|
|
552
|
+
// SDS-R: Handle received message in repair manager
|
|
553
|
+
this.repairManager?.markMessageReceived(message.messageId);
|
|
554
|
+
|
|
555
|
+
// SDS-R: Process incoming repair requests
|
|
556
|
+
if (message.repairRequest && message.repairRequest.length > 0) {
|
|
557
|
+
// Emit RepairRequestReceived event
|
|
558
|
+
this.safeSendEvent(MessageChannelEvent.RepairRequestReceived, {
|
|
559
|
+
detail: {
|
|
560
|
+
messageIds: message.repairRequest.map((r) => r.messageId),
|
|
561
|
+
fromSenderId: message.senderId
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
this.repairManager?.processIncomingRepairRequests(
|
|
566
|
+
message.repairRequest,
|
|
567
|
+
this.localHistory
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
467
571
|
this.reviewAckStatus(message);
|
|
468
572
|
if (isContentMessage(message)) {
|
|
469
573
|
this.filter.insert(message.messageId);
|
|
@@ -471,9 +575,7 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
471
575
|
|
|
472
576
|
const missingDependencies = message.causalHistory.filter(
|
|
473
577
|
(messageHistoryEntry) =>
|
|
474
|
-
!this.
|
|
475
|
-
({ messageId }) => messageId === messageHistoryEntry.messageId
|
|
476
|
-
)
|
|
578
|
+
!this.isMessageAvailable(messageHistoryEntry.messageId)
|
|
477
579
|
);
|
|
478
580
|
|
|
479
581
|
if (missingDependencies.length > 0) {
|
|
@@ -487,6 +589,9 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
487
589
|
missingDependencies.map((ch) => ch.messageId)
|
|
488
590
|
);
|
|
489
591
|
|
|
592
|
+
// SDS-R: Track missing dependencies in repair manager
|
|
593
|
+
this.repairManager?.markDependenciesMissing(missingDependencies);
|
|
594
|
+
|
|
490
595
|
this.safeSendEvent(MessageChannelEvent.InMessageMissing, {
|
|
491
596
|
detail: Array.from(missingDependencies)
|
|
492
597
|
});
|
|
@@ -549,18 +654,26 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
549
654
|
// It's a new message
|
|
550
655
|
if (!message) {
|
|
551
656
|
log.info(this.senderId, "sending new message", messageId);
|
|
657
|
+
|
|
658
|
+
// Get repair requests to include in the message (SDS-R)
|
|
659
|
+
const repairRequests =
|
|
660
|
+
this.repairManager?.getRepairRequests(
|
|
661
|
+
MAX_REPAIR_REQUESTS_PER_MESSAGE
|
|
662
|
+
) ?? [];
|
|
663
|
+
|
|
552
664
|
message = new ContentMessage(
|
|
553
665
|
messageId,
|
|
554
666
|
this.channelId,
|
|
555
667
|
this.senderId,
|
|
556
668
|
this.localHistory
|
|
557
669
|
.slice(-this.causalHistorySize)
|
|
558
|
-
.map(({ messageId, retrievalHint }) => {
|
|
559
|
-
return { messageId, retrievalHint };
|
|
670
|
+
.map(({ messageId, retrievalHint, senderId }) => {
|
|
671
|
+
return { messageId, retrievalHint, senderId };
|
|
560
672
|
}),
|
|
561
673
|
this.lamportTimestamp,
|
|
562
674
|
this.filter.toBytes(),
|
|
563
|
-
payload
|
|
675
|
+
payload,
|
|
676
|
+
repairRequests
|
|
564
677
|
);
|
|
565
678
|
|
|
566
679
|
this.outgoingBuffer.push(message);
|
|
@@ -616,6 +729,26 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
616
729
|
}
|
|
617
730
|
}
|
|
618
731
|
|
|
732
|
+
/**
|
|
733
|
+
* Check if a message is available (either in localHistory or incomingBuffer)
|
|
734
|
+
* This prevents treating messages as "missing" when they've already been received
|
|
735
|
+
* but are waiting in the incoming buffer for their dependencies.
|
|
736
|
+
*
|
|
737
|
+
* @param messageId - The ID of the message to check
|
|
738
|
+
* @private
|
|
739
|
+
*/
|
|
740
|
+
private isMessageAvailable(messageId: MessageId): boolean {
|
|
741
|
+
// Check if in local history
|
|
742
|
+
if (this.localHistory.some((m) => m.messageId === messageId)) {
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
// Check if in incoming buffer (already received, waiting for dependencies)
|
|
746
|
+
if (this.incomingBuffer.some((m) => m.messageId === messageId)) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
|
|
619
752
|
/**
|
|
620
753
|
* Return true if the message was "delivered"
|
|
621
754
|
*
|
|
@@ -657,6 +790,7 @@ export class MessageChannel extends TypedEventEmitter<MessageChannelEvents> {
|
|
|
657
790
|
}
|
|
658
791
|
|
|
659
792
|
this.localHistory.push(message);
|
|
793
|
+
|
|
660
794
|
return true;
|
|
661
795
|
}
|
|
662
796
|
|