@optimystic/db-p2p 0.3.0 → 0.7.0
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/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +4 -1
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +2 -16
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +68 -71
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +9 -1
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +39 -4
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +3 -5
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +2 -0
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +4 -1
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-node-base.js +1 -1
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +2 -2
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +5 -5
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/package.json +2 -2
- package/src/cluster/block-transfer-service.ts +4 -1
- package/src/cluster/block-transfer.ts +80 -99
- package/src/cluster/cluster-repo.ts +55 -4
- package/src/cluster/rebalance-monitor.ts +3 -5
- package/src/dispute/dispute-service.ts +4 -1
- package/src/index.ts +2 -0
- package/src/libp2p-node-base.ts +1 -1
- package/src/repo/coordinator-repo.ts +7 -7
|
@@ -1,28 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { PeerId, IPeerNetwork } from '@optimystic/db-core';
|
|
1
|
+
import type { IRepo, IPeerNetwork } from '@optimystic/db-core';
|
|
3
2
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
4
3
|
import type { PartitionDetector } from './partition-detector.js';
|
|
5
4
|
import type { RestorationCoordinator } from '../storage/restoration-coordinator-v2.js';
|
|
6
|
-
import { BlockTransferClient
|
|
5
|
+
import { BlockTransferClient } from './block-transfer-service.js';
|
|
6
|
+
import type { RebalanceEvent } from './rebalance-monitor.js';
|
|
7
7
|
import { createLogger } from '../logger.js';
|
|
8
8
|
|
|
9
9
|
const log = createLogger('block-transfer');
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* Rebalance event describing gained/lost block responsibilities.
|
|
13
|
-
* Matches the RebalanceMonitor spec from the sibling ticket.
|
|
14
|
-
*/
|
|
15
|
-
export interface RebalanceEvent {
|
|
16
|
-
/** Block IDs this node has gained responsibility for */
|
|
17
|
-
gained: string[];
|
|
18
|
-
/** Block IDs this node has lost responsibility for */
|
|
19
|
-
lost: string[];
|
|
20
|
-
/** Peers that are now closer for the lost blocks: blockId → peerId[] */
|
|
21
|
-
newOwners: Map<string, string[]>;
|
|
22
|
-
/** Timestamp of the topology change that triggered this */
|
|
23
|
-
triggeredAt: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
11
|
export interface BlockTransferConfig {
|
|
27
12
|
/** Max concurrent transfers. Default: 4 */
|
|
28
13
|
maxConcurrency?: number;
|
|
@@ -34,11 +19,6 @@ export interface BlockTransferConfig {
|
|
|
34
19
|
enablePush?: boolean;
|
|
35
20
|
}
|
|
36
21
|
|
|
37
|
-
interface TransferTask {
|
|
38
|
-
blockId: string;
|
|
39
|
-
attempt: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
22
|
/**
|
|
43
23
|
* Coordinates block transfers in response to rebalance events.
|
|
44
24
|
*
|
|
@@ -84,11 +64,9 @@ export class BlockTransferCoordinator {
|
|
|
84
64
|
const succeeded: string[] = [];
|
|
85
65
|
const failed: string[] = [];
|
|
86
66
|
|
|
87
|
-
const
|
|
88
|
-
.filter(id => !this.inFlight.has(`pull:${id}`))
|
|
89
|
-
.map(id => ({ blockId: id, attempt: 0 }));
|
|
67
|
+
const ids = blockIds.filter(id => !this.inFlight.has(`pull:${id}`));
|
|
90
68
|
|
|
91
|
-
await Promise.all(
|
|
69
|
+
await Promise.all(ids.map(id => this.executePull(id, succeeded, failed)));
|
|
92
70
|
|
|
93
71
|
return { succeeded, failed };
|
|
94
72
|
}
|
|
@@ -111,11 +89,9 @@ export class BlockTransferCoordinator {
|
|
|
111
89
|
const succeeded: string[] = [];
|
|
112
90
|
const failed: string[] = [];
|
|
113
91
|
|
|
114
|
-
const
|
|
115
|
-
.filter(id => !this.inFlight.has(`push:${id}`) && newOwners.has(id))
|
|
116
|
-
.map(id => ({ blockId: id, attempt: 0 }));
|
|
92
|
+
const ids = blockIds.filter(id => !this.inFlight.has(`push:${id}`) && newOwners.has(id));
|
|
117
93
|
|
|
118
|
-
await Promise.all(
|
|
94
|
+
await Promise.all(ids.map(id => this.executePush(id, newOwners, succeeded, failed)));
|
|
119
95
|
|
|
120
96
|
return { succeeded, failed };
|
|
121
97
|
}
|
|
@@ -139,37 +115,40 @@ export class BlockTransferCoordinator {
|
|
|
139
115
|
}
|
|
140
116
|
|
|
141
117
|
private async executePull(
|
|
142
|
-
|
|
118
|
+
blockId: string,
|
|
143
119
|
succeeded: string[],
|
|
144
120
|
failed: string[]
|
|
145
121
|
): Promise<void> {
|
|
146
|
-
const key = `pull:${
|
|
122
|
+
const key = `pull:${blockId}`;
|
|
147
123
|
if (this.inFlight.has(key)) return;
|
|
148
124
|
this.inFlight.add(key);
|
|
149
125
|
|
|
150
126
|
try {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.
|
|
156
|
-
|
|
127
|
+
for (let attempt = 0; ; attempt++) {
|
|
128
|
+
await this.acquireSemaphore();
|
|
129
|
+
let archive: Awaited<ReturnType<RestorationCoordinator['restore']>>;
|
|
130
|
+
try {
|
|
131
|
+
archive = await this.withTimeout(
|
|
132
|
+
this.restorationCoordinator.restore(blockId),
|
|
133
|
+
this.transferTimeoutMs
|
|
134
|
+
);
|
|
135
|
+
} finally {
|
|
136
|
+
this.releaseSemaphore();
|
|
137
|
+
}
|
|
157
138
|
|
|
158
139
|
if (archive) {
|
|
159
|
-
log('pull:ok block=%s',
|
|
160
|
-
succeeded.push(
|
|
161
|
-
} else if (task.attempt < this.maxRetries) {
|
|
162
|
-
log('pull:retry block=%s attempt=%d', task.blockId, task.attempt + 1);
|
|
163
|
-
this.inFlight.delete(key);
|
|
164
|
-
await this.delay(this.backoffMs(task.attempt));
|
|
165
|
-
await this.executePull({ ...task, attempt: task.attempt + 1 }, succeeded, failed);
|
|
140
|
+
log('pull:ok block=%s', blockId);
|
|
141
|
+
succeeded.push(blockId);
|
|
166
142
|
return;
|
|
167
|
-
} else {
|
|
168
|
-
log('pull:failed block=%s', task.blockId);
|
|
169
|
-
failed.push(task.blockId);
|
|
170
143
|
}
|
|
171
|
-
|
|
172
|
-
|
|
144
|
+
if (attempt < this.maxRetries) {
|
|
145
|
+
log('pull:retry block=%s attempt=%d', blockId, attempt + 1);
|
|
146
|
+
await this.delay(this.backoffMs(attempt));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
log('pull:failed block=%s', blockId);
|
|
150
|
+
failed.push(blockId);
|
|
151
|
+
return;
|
|
173
152
|
}
|
|
174
153
|
} finally {
|
|
175
154
|
this.inFlight.delete(key);
|
|
@@ -177,71 +156,73 @@ export class BlockTransferCoordinator {
|
|
|
177
156
|
}
|
|
178
157
|
|
|
179
158
|
private async executePush(
|
|
180
|
-
|
|
159
|
+
blockId: string,
|
|
181
160
|
newOwners: Map<string, string[]>,
|
|
182
161
|
succeeded: string[],
|
|
183
162
|
failed: string[]
|
|
184
163
|
): Promise<void> {
|
|
185
|
-
const key = `push:${
|
|
164
|
+
const key = `push:${blockId}`;
|
|
186
165
|
if (this.inFlight.has(key)) return;
|
|
187
166
|
this.inFlight.add(key);
|
|
188
167
|
|
|
189
168
|
try {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const result = await this.repo.get({ blockIds: [task.blockId] });
|
|
200
|
-
const blockResult = result[task.blockId];
|
|
201
|
-
if (!blockResult?.block) {
|
|
202
|
-
log('push:no-local-data block=%s', task.blockId);
|
|
203
|
-
failed.push(task.blockId);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
169
|
+
for (let attempt = 0; ; attempt++) {
|
|
170
|
+
await this.acquireSemaphore();
|
|
171
|
+
let pushed = false;
|
|
172
|
+
try {
|
|
173
|
+
const owners = newOwners.get(blockId);
|
|
174
|
+
if (!owners || owners.length === 0) {
|
|
175
|
+
failed.push(blockId);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
206
178
|
|
|
207
|
-
|
|
179
|
+
// Read block data from local storage
|
|
180
|
+
const result = await this.repo.get({ blockIds: [blockId] });
|
|
181
|
+
const blockResult = result[blockId];
|
|
182
|
+
if (!blockResult?.block) {
|
|
183
|
+
log('push:no-local-data block=%s', blockId);
|
|
184
|
+
failed.push(blockId);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
208
187
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
const blockData = new TextEncoder().encode(JSON.stringify(blockResult.block));
|
|
189
|
+
|
|
190
|
+
// Push to at least one new owner
|
|
191
|
+
for (const ownerPeerIdStr of owners) {
|
|
192
|
+
try {
|
|
193
|
+
const peerId = peerIdFromString(ownerPeerIdStr);
|
|
194
|
+
const client = new BlockTransferClient(peerId, this.peerNetwork, this.protocolPrefix);
|
|
195
|
+
const response = await this.withTimeout(
|
|
196
|
+
client.pushBlocks([blockId], [blockData]),
|
|
197
|
+
this.transferTimeoutMs
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (response && !response.missing.includes(blockId)) {
|
|
201
|
+
pushed = true;
|
|
202
|
+
log('push:ok block=%s peer=%s', blockId, ownerPeerIdStr);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
} catch (err) {
|
|
206
|
+
log('push:peer-error block=%s peer=%s err=%s',
|
|
207
|
+
blockId, ownerPeerIdStr, (err as Error).message);
|
|
224
208
|
}
|
|
225
|
-
} catch (err) {
|
|
226
|
-
log('push:peer-error block=%s peer=%s err=%s',
|
|
227
|
-
task.blockId, ownerPeerIdStr, (err as Error).message);
|
|
228
209
|
}
|
|
210
|
+
} finally {
|
|
211
|
+
this.releaseSemaphore();
|
|
229
212
|
}
|
|
230
213
|
|
|
231
214
|
if (pushed) {
|
|
232
|
-
succeeded.push(
|
|
233
|
-
} else if (task.attempt < this.maxRetries) {
|
|
234
|
-
log('push:retry block=%s attempt=%d', task.blockId, task.attempt + 1);
|
|
235
|
-
this.inFlight.delete(key);
|
|
236
|
-
await this.delay(this.backoffMs(task.attempt));
|
|
237
|
-
await this.executePush({ ...task, attempt: task.attempt + 1 }, newOwners, succeeded, failed);
|
|
215
|
+
succeeded.push(blockId);
|
|
238
216
|
return;
|
|
239
|
-
} else {
|
|
240
|
-
log('push:failed block=%s', task.blockId);
|
|
241
|
-
failed.push(task.blockId);
|
|
242
217
|
}
|
|
243
|
-
|
|
244
|
-
|
|
218
|
+
if (attempt < this.maxRetries) {
|
|
219
|
+
log('push:retry block=%s attempt=%d', blockId, attempt + 1);
|
|
220
|
+
await this.delay(this.backoffMs(attempt));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
log('push:failed block=%s', blockId);
|
|
224
|
+
failed.push(blockId);
|
|
225
|
+
return;
|
|
245
226
|
}
|
|
246
227
|
} finally {
|
|
247
228
|
this.inFlight.delete(key);
|
|
@@ -306,7 +306,8 @@ export class ClusterMember implements ICluster {
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
/**
|
|
309
|
-
* Merges two records, validating that non-signature fields match
|
|
309
|
+
* Merges two records, validating that non-signature fields match.
|
|
310
|
+
* Detects equivocation (same peer changing vote type) and applies penalties.
|
|
310
311
|
*/
|
|
311
312
|
private async mergeRecords(existing: ClusterRecord, incoming: ClusterRecord): Promise<ClusterRecord> {
|
|
312
313
|
log('cluster-member:merge-records', {
|
|
@@ -327,14 +328,64 @@ export class ClusterMember implements ICluster {
|
|
|
327
328
|
throw new Error('Peers mismatch');
|
|
328
329
|
}
|
|
329
330
|
|
|
330
|
-
// Merge signatures
|
|
331
|
+
// Merge signatures with equivocation detection
|
|
332
|
+
const mergedPromises = this.detectEquivocation(
|
|
333
|
+
existing.promises, incoming.promises, 'promise', existing.messageHash
|
|
334
|
+
);
|
|
335
|
+
const mergedCommits = this.detectEquivocation(
|
|
336
|
+
existing.commits, incoming.commits, 'commit', existing.messageHash
|
|
337
|
+
);
|
|
338
|
+
|
|
331
339
|
return {
|
|
332
340
|
...existing,
|
|
333
|
-
promises:
|
|
334
|
-
commits:
|
|
341
|
+
promises: mergedPromises,
|
|
342
|
+
commits: mergedCommits
|
|
335
343
|
};
|
|
336
344
|
}
|
|
337
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Compares existing vs incoming signatures for the same peers.
|
|
348
|
+
* If a peer's vote type changed (approve↔reject), that's equivocation:
|
|
349
|
+
* report a penalty and keep the first-seen signature.
|
|
350
|
+
* New peers are accepted normally.
|
|
351
|
+
*/
|
|
352
|
+
private detectEquivocation(
|
|
353
|
+
existing: Record<string, Signature>,
|
|
354
|
+
incoming: Record<string, Signature>,
|
|
355
|
+
phase: 'promise' | 'commit',
|
|
356
|
+
messageHash: string
|
|
357
|
+
): Record<string, Signature> {
|
|
358
|
+
const merged = { ...existing };
|
|
359
|
+
|
|
360
|
+
for (const [peerId, incomingSig] of Object.entries(incoming)) {
|
|
361
|
+
const existingSig = existing[peerId];
|
|
362
|
+
if (existingSig) {
|
|
363
|
+
if (existingSig.type !== incomingSig.type) {
|
|
364
|
+
// Equivocation detected: peer changed their vote type
|
|
365
|
+
log('cluster-member:equivocation-detected', {
|
|
366
|
+
peerId,
|
|
367
|
+
phase,
|
|
368
|
+
messageHash,
|
|
369
|
+
existingType: existingSig.type,
|
|
370
|
+
incomingType: incomingSig.type
|
|
371
|
+
});
|
|
372
|
+
this.reputation?.reportPeer(
|
|
373
|
+
peerId,
|
|
374
|
+
PenaltyReason.Equivocation,
|
|
375
|
+
`${phase}:${messageHash}:${existingSig.type}->${incomingSig.type}`
|
|
376
|
+
);
|
|
377
|
+
// Keep first-seen signature — do not let the peer flip their vote
|
|
378
|
+
}
|
|
379
|
+
// Same type: keep existing (no-op, already in merged)
|
|
380
|
+
} else {
|
|
381
|
+
// New peer — accept normally
|
|
382
|
+
merged[peerId] = incomingSig;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return merged;
|
|
387
|
+
}
|
|
388
|
+
|
|
338
389
|
private async validateRecord(record: ClusterRecord): Promise<void> {
|
|
339
390
|
// Validate message hash matches the message content
|
|
340
391
|
const expectedHash = await this.computeMessageHash(record.message);
|
|
@@ -6,6 +6,7 @@ import type { ArachnodeFretAdapter, ArachnodeInfo } from '../storage/arachnode-f
|
|
|
6
6
|
import { createLogger } from '../logger.js'
|
|
7
7
|
|
|
8
8
|
const log = createLogger('rebalance-monitor')
|
|
9
|
+
const textEncoder = new TextEncoder()
|
|
9
10
|
|
|
10
11
|
export interface RebalanceEvent {
|
|
11
12
|
/** Block IDs this node has gained responsibility for */
|
|
@@ -164,7 +165,7 @@ export class RebalanceMonitor implements Startable {
|
|
|
164
165
|
const newOwners = new Map<string, string[]>()
|
|
165
166
|
|
|
166
167
|
for (const blockId of this.trackedBlocks) {
|
|
167
|
-
const key =
|
|
168
|
+
const key = textEncoder.encode(blockId)
|
|
168
169
|
const coord = await hashKey(key)
|
|
169
170
|
|
|
170
171
|
// Get the current cohort — assembleCohort returns peer IDs sorted by distance
|
|
@@ -217,9 +218,6 @@ export class RebalanceMonitor implements Startable {
|
|
|
217
218
|
* Update ArachnodeInfo status through the fret adapter.
|
|
218
219
|
*/
|
|
219
220
|
setStatus(status: ArachnodeInfo['status']): void {
|
|
220
|
-
|
|
221
|
-
if (current) {
|
|
222
|
-
this.deps.fretAdapter.setArachnodeInfo({ ...current, status })
|
|
223
|
-
}
|
|
221
|
+
this.deps.fretAdapter.setStatus(status)
|
|
224
222
|
}
|
|
225
223
|
}
|
|
@@ -63,6 +63,8 @@ export class DisputeService {
|
|
|
63
63
|
private activeDisputes: Map<string, DisputeChallenge> = new Map();
|
|
64
64
|
/** Resolved disputes (disputeId -> resolution) */
|
|
65
65
|
private resolvedDisputes: Map<string, DisputeResolution> = new Map();
|
|
66
|
+
/** Challenges retained after resolution for status lookups */
|
|
67
|
+
private resolvedChallenges: Map<string, DisputeChallenge> = new Map();
|
|
66
68
|
/** Track which transactions we've already disputed (prevent spam) */
|
|
67
69
|
private disputedTransactions: Set<string> = new Set();
|
|
68
70
|
|
|
@@ -179,6 +181,7 @@ export class DisputeService {
|
|
|
179
181
|
const votes = await this.collectVotes(challenge, arbitrators);
|
|
180
182
|
const resolution = this.resolveDispute(challenge, votes);
|
|
181
183
|
|
|
184
|
+
this.resolvedChallenges.set(disputeId, challenge);
|
|
182
185
|
this.activeDisputes.delete(disputeId);
|
|
183
186
|
this.resolvedDisputes.set(disputeId, resolution);
|
|
184
187
|
|
|
@@ -448,6 +451,6 @@ export class DisputeService {
|
|
|
448
451
|
}
|
|
449
452
|
|
|
450
453
|
private findChallengeForDispute(disputeId: string): DisputeChallenge | undefined {
|
|
451
|
-
return this.activeDisputes.get(disputeId);
|
|
454
|
+
return this.activeDisputes.get(disputeId) ?? this.resolvedChallenges.get(disputeId);
|
|
452
455
|
}
|
|
453
456
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ export * from "./cluster/client.js";
|
|
|
2
2
|
export * from "./cluster/cluster-repo.js";
|
|
3
3
|
export * from "./cluster/service.js";
|
|
4
4
|
export * from "./cluster/rebalance-monitor.js";
|
|
5
|
+
export * from "./cluster/block-transfer.js";
|
|
6
|
+
export * from "./cluster/block-transfer-service.js";
|
|
5
7
|
export * from "./protocol-client.js";
|
|
6
8
|
export * from "./repo/client.js";
|
|
7
9
|
export * from "./repo/cluster-coordinator.js";
|
package/src/libp2p-node-base.ts
CHANGED
|
@@ -315,7 +315,7 @@ export async function createLibp2pNodeBase(
|
|
|
315
315
|
);
|
|
316
316
|
|
|
317
317
|
// Create callback for querying cluster peers for their latest block revision
|
|
318
|
-
const clusterLatestCallback: ClusterLatestCallback = async (peerId, blockId) => {
|
|
318
|
+
const clusterLatestCallback: ClusterLatestCallback = async (peerId, blockId, _context?) => {
|
|
319
319
|
const syncClient = new SyncClient(peerId, keyNetwork, protocolPrefix);
|
|
320
320
|
try {
|
|
321
321
|
const response = await syncClient.requestBlock({ blockId, rev: undefined });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PendRequest, ActionBlocks, IRepo, MessageOptions, CommitResult, GetBlockResults, PendResult, BlockGets, CommitRequest, RepoMessage, IKeyNetwork, ICluster, ClusterConsensusConfig, BlockId, ActionRev } from "@optimystic/db-core";
|
|
1
|
+
import type { PendRequest, ActionBlocks, IRepo, MessageOptions, CommitResult, GetBlockResults, PendResult, BlockGets, CommitRequest, RepoMessage, IKeyNetwork, ICluster, ClusterConsensusConfig, BlockId, ActionRev, ActionContext } from "@optimystic/db-core";
|
|
2
2
|
import { LruMap } from "@optimystic/db-core";
|
|
3
3
|
import { ClusterCoordinator } from "./cluster-coordinator.js";
|
|
4
4
|
import type { ClusterClient } from "../cluster/client.js";
|
|
@@ -22,7 +22,7 @@ interface LocalClusterWithExecutionTracking extends ICluster {
|
|
|
22
22
|
* Callback to query a cluster peer for their latest revision of a block.
|
|
23
23
|
* Returns the peer's latest ActionRev if they have the block, undefined otherwise.
|
|
24
24
|
*/
|
|
25
|
-
export type ClusterLatestCallback = (peerId: PeerId, blockId: BlockId) => Promise<ActionRev | undefined>;
|
|
25
|
+
export type ClusterLatestCallback = (peerId: PeerId, blockId: BlockId, context?: ActionContext) => Promise<ActionRev | undefined>;
|
|
26
26
|
|
|
27
27
|
interface CoordinatorRepoComponents {
|
|
28
28
|
storageRepo: IRepo;
|
|
@@ -159,7 +159,7 @@ export class CoordinatorRepo implements IRepo {
|
|
|
159
159
|
// If block not found locally (no state), try cluster peers
|
|
160
160
|
if (!localEntry?.state?.latest) {
|
|
161
161
|
try {
|
|
162
|
-
await this.fetchBlockFromCluster(blockId);
|
|
162
|
+
await this.fetchBlockFromCluster(blockId, blockGets.context);
|
|
163
163
|
// Re-fetch after sync
|
|
164
164
|
const refreshed = await this.storageRepo.get({ blockIds: [blockId], context: blockGets.context }, options);
|
|
165
165
|
if (refreshed[blockId]) {
|
|
@@ -175,11 +175,11 @@ export class CoordinatorRepo implements IRepo {
|
|
|
175
175
|
return localResult;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
private async fetchBlockFromCluster(blockId: BlockId): Promise<void> {
|
|
178
|
+
private async fetchBlockFromCluster(blockId: BlockId, context?: ActionContext): Promise<void> {
|
|
179
179
|
if (!this.clusterLatestCallback) return;
|
|
180
180
|
|
|
181
181
|
// Query cluster for the block
|
|
182
|
-
const clusterLatest = await this.queryClusterForLatest(blockId);
|
|
182
|
+
const clusterLatest = await this.queryClusterForLatest(blockId, context);
|
|
183
183
|
if (clusterLatest) {
|
|
184
184
|
// Found on cluster - trigger restoration to sync the block
|
|
185
185
|
await this.storageRepo.get({ blockIds: [blockId], context: { committed: [clusterLatest], rev: clusterLatest.rev } });
|
|
@@ -190,7 +190,7 @@ export class CoordinatorRepo implements IRepo {
|
|
|
190
190
|
/**
|
|
191
191
|
* Query cluster peers to find the maximum latest revision for a block.
|
|
192
192
|
*/
|
|
193
|
-
private async queryClusterForLatest(blockId: BlockId): Promise<ActionRev | undefined> {
|
|
193
|
+
private async queryClusterForLatest(blockId: BlockId, context?: ActionContext): Promise<ActionRev | undefined> {
|
|
194
194
|
const blockIdBytes = new TextEncoder().encode(blockId);
|
|
195
195
|
const peers = await this.keyNetwork.findCluster(blockIdBytes);
|
|
196
196
|
if (!peers || Object.keys(peers).length === 0) {
|
|
@@ -211,7 +211,7 @@ export class CoordinatorRepo implements IRepo {
|
|
|
211
211
|
const latestResults = await Promise.allSettled(
|
|
212
212
|
peerIds.map(peerIdStr => {
|
|
213
213
|
const peerId = peerIdFromString(peerIdStr);
|
|
214
|
-
return withTimeout(this.clusterLatestCallback!(peerId, blockId), 3000);
|
|
214
|
+
return withTimeout(this.clusterLatestCallback!(peerId, blockId, context), 3000);
|
|
215
215
|
})
|
|
216
216
|
);
|
|
217
217
|
|