@peerbit/shared-log 16.0.23 → 16.0.25
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/exchange-heads.d.ts +8 -0
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +8 -0
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +292 -26
- package/dist/src/index.js.map +1 -1
- package/dist/src/replication-info-v2-receive.d.ts +23 -0
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +91 -0
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +6 -0
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +13 -0
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/dist/src/sync/index.d.ts +3 -1
- package/dist/src/sync/index.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/exchange-heads.ts +9 -0
- package/src/index.ts +386 -35
- package/src/replication-info-v2-receive.ts +122 -0
- package/src/replication-info-v2-send.ts +16 -0
- package/src/sync/index.ts +3 -1
package/src/index.ts
CHANGED
|
@@ -169,6 +169,7 @@ import {
|
|
|
169
169
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY,
|
|
170
170
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM,
|
|
171
171
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE,
|
|
172
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM,
|
|
172
173
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND,
|
|
173
174
|
StashBackedRawExchangeHeadsMessage,
|
|
174
175
|
SyncCapabilitiesMessage,
|
|
@@ -322,6 +323,86 @@ type SharedLogServicesWithFanout = {
|
|
|
322
323
|
const getSharedLogFanoutService = (services: unknown): FanoutTree | undefined =>
|
|
323
324
|
(services as SharedLogServicesWithFanout).fanout;
|
|
324
325
|
|
|
326
|
+
const FANOUT_OPEN_METRICS = [
|
|
327
|
+
["joinReqSent", "joinReqSent"],
|
|
328
|
+
["joinAcceptReceived", "joinAcceptReceived"],
|
|
329
|
+
["joinRejectReceived", "joinRejectReceived"],
|
|
330
|
+
["bootstrapDialAttempts", "joinBootstrapDialAttempts"],
|
|
331
|
+
["bootstrapDialFailures", "joinBootstrapDialFailures"],
|
|
332
|
+
["candidateDialAttempts", "joinCandidateDialAttempts"],
|
|
333
|
+
["candidateDialFailures", "joinCandidateDialFailures"],
|
|
334
|
+
["connectedCandidateAttempts", "joinConnectedCandidateAttempts"],
|
|
335
|
+
["unconnectedCandidateAttempts", "joinUnconnectedCandidateAttempts"],
|
|
336
|
+
["joinReqTimeouts", "joinReqTimeouts"],
|
|
337
|
+
["deadlineExpirations", "joinDeadlineExpirations"],
|
|
338
|
+
] as const;
|
|
339
|
+
|
|
340
|
+
type FanoutOpenMetric = (typeof FANOUT_OPEN_METRICS)[number][0];
|
|
341
|
+
type FanoutOpenMetricSnapshot = Record<FanoutOpenMetric, number>;
|
|
342
|
+
|
|
343
|
+
const snapshotFanoutOpenMetrics = (
|
|
344
|
+
service: FanoutTree,
|
|
345
|
+
topic: string,
|
|
346
|
+
root: string,
|
|
347
|
+
): FanoutOpenMetricSnapshot | undefined => {
|
|
348
|
+
try {
|
|
349
|
+
const metrics = (
|
|
350
|
+
service as FanoutTree & {
|
|
351
|
+
getChannelMetrics: (
|
|
352
|
+
topic: string,
|
|
353
|
+
root: string,
|
|
354
|
+
) => Record<string, number>;
|
|
355
|
+
}
|
|
356
|
+
).getChannelMetrics(topic, root);
|
|
357
|
+
return Object.fromEntries(
|
|
358
|
+
FANOUT_OPEN_METRICS.map(([name, source]) => [name, metrics[source] ?? 0]),
|
|
359
|
+
) as FanoutOpenMetricSnapshot;
|
|
360
|
+
} catch {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const emitFanoutOpenProfile = (properties: {
|
|
366
|
+
profile?: SyncProfileFn;
|
|
367
|
+
startedAt: number;
|
|
368
|
+
service: FanoutTree;
|
|
369
|
+
topic: string;
|
|
370
|
+
root: string;
|
|
371
|
+
mode: "root" | "node";
|
|
372
|
+
outcome: "error" | "opened" | "joined";
|
|
373
|
+
timeoutMs?: number;
|
|
374
|
+
before?: FanoutOpenMetricSnapshot;
|
|
375
|
+
}) => {
|
|
376
|
+
if (!properties.profile) return;
|
|
377
|
+
try {
|
|
378
|
+
const after = snapshotFanoutOpenMetrics(
|
|
379
|
+
properties.service,
|
|
380
|
+
properties.topic,
|
|
381
|
+
properties.root,
|
|
382
|
+
);
|
|
383
|
+
const deltas = Object.fromEntries(
|
|
384
|
+
FANOUT_OPEN_METRICS.map(([name]) => [
|
|
385
|
+
name,
|
|
386
|
+
(after?.[name] ?? 0) - (properties.before?.[name] ?? 0),
|
|
387
|
+
]),
|
|
388
|
+
) as FanoutOpenMetricSnapshot;
|
|
389
|
+
emitSyncProfileDuration(properties.profile, properties.startedAt, {
|
|
390
|
+
name: "sharedLog.open.fanout",
|
|
391
|
+
component: "shared-log",
|
|
392
|
+
messages: deltas.joinReqSent,
|
|
393
|
+
details: {
|
|
394
|
+
configured: true,
|
|
395
|
+
mode: properties.mode,
|
|
396
|
+
outcome: properties.outcome,
|
|
397
|
+
configuredTimeoutMs: properties.timeoutMs,
|
|
398
|
+
...deltas,
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
} catch {
|
|
402
|
+
// Diagnostics must not affect open correctness.
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
325
406
|
type PendingIHave<T> = {
|
|
326
407
|
resetTimeout: () => void;
|
|
327
408
|
requesting: Map<string, Uint8Array>;
|
|
@@ -471,6 +552,18 @@ export const logger = loggerFn("peerbit:shared-log");
|
|
|
471
552
|
const warn = logger.newScope("warn");
|
|
472
553
|
const traceLogger = logger.trace as typeof logger.trace & { enabled?: boolean };
|
|
473
554
|
|
|
555
|
+
const emitAdvisorySyncProfileDuration = (
|
|
556
|
+
profile: SyncProfileFn | undefined,
|
|
557
|
+
startedAt: number,
|
|
558
|
+
event: Parameters<typeof emitSyncProfileDuration>[2],
|
|
559
|
+
): void => {
|
|
560
|
+
try {
|
|
561
|
+
emitSyncProfileDuration(profile, startedAt, event);
|
|
562
|
+
} catch {
|
|
563
|
+
// Diagnostics must not change open or provider-resolution correctness.
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
474
567
|
const canUseOptionalNativeModuleImports = (): boolean => {
|
|
475
568
|
const scope = globalThis as {
|
|
476
569
|
ServiceWorkerGlobalScope?: unknown;
|
|
@@ -3979,6 +4072,7 @@ export class SharedLog<
|
|
|
3979
4072
|
peerSession: PeerSession;
|
|
3980
4073
|
receiveEpoch: object | null;
|
|
3981
4074
|
signal: AbortSignal;
|
|
4075
|
+
requestRemoteFullRearm?: boolean;
|
|
3982
4076
|
}): Promise<
|
|
3983
4077
|
{ receiverTransportSession: bigint; requestNotBeforeMs: number } | undefined
|
|
3984
4078
|
> {
|
|
@@ -3986,7 +4080,11 @@ export class SharedLog<
|
|
|
3986
4080
|
const receiverTransportSession = this.ownTransportSession();
|
|
3987
4081
|
await this.rpc.send(
|
|
3988
4082
|
new SyncCapabilitiesMessage({
|
|
3989
|
-
capabilities:
|
|
4083
|
+
capabilities:
|
|
4084
|
+
this.replicationInfoV2ReceiveCapabilities() |
|
|
4085
|
+
(properties.requestRemoteFullRearm
|
|
4086
|
+
? SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM
|
|
4087
|
+
: 0),
|
|
3990
4088
|
}),
|
|
3991
4089
|
{
|
|
3992
4090
|
mode: new AcknowledgeDelivery({
|
|
@@ -4053,6 +4151,7 @@ export class SharedLog<
|
|
|
4053
4151
|
peerSession: properties.peerSession as PeerSession,
|
|
4054
4152
|
receiveEpoch: properties.receiveEpoch,
|
|
4055
4153
|
signal: properties.signal,
|
|
4154
|
+
requestRemoteFullRearm: properties.requestRemoteFullRearm,
|
|
4056
4155
|
}),
|
|
4057
4156
|
onRequestError: (error) => {
|
|
4058
4157
|
if (
|
|
@@ -4341,16 +4440,38 @@ export class SharedLog<
|
|
|
4341
4440
|
});
|
|
4342
4441
|
channel.addEventListener("unicast", this._onFanoutUnicastFn);
|
|
4343
4442
|
|
|
4443
|
+
const profile = this._logProperties?.sync?.profile;
|
|
4444
|
+
const startedAt = syncProfileStart(profile);
|
|
4445
|
+
const mode =
|
|
4446
|
+
resolvedRoot === fanoutService.publicKeyHash ? "root" : "node";
|
|
4447
|
+
const before = profile
|
|
4448
|
+
? snapshotFanoutOpenMetrics(fanoutService, this.topic, resolvedRoot)
|
|
4449
|
+
: undefined;
|
|
4450
|
+
let outcome: "error" | "opened" | "joined" = "error";
|
|
4344
4451
|
try {
|
|
4345
4452
|
const channelOptions = this.getFanoutChannelOptions(options);
|
|
4346
|
-
if (
|
|
4453
|
+
if (mode === "root") {
|
|
4347
4454
|
await channel.openAsRoot(channelOptions);
|
|
4455
|
+
outcome = "opened";
|
|
4348
4456
|
return;
|
|
4349
4457
|
}
|
|
4350
4458
|
await channel.join(channelOptions, options.join);
|
|
4459
|
+
outcome = "joined";
|
|
4351
4460
|
} catch (error) {
|
|
4352
4461
|
this._closeFanoutChannel();
|
|
4353
4462
|
throw error;
|
|
4463
|
+
} finally {
|
|
4464
|
+
emitFanoutOpenProfile({
|
|
4465
|
+
profile,
|
|
4466
|
+
startedAt,
|
|
4467
|
+
service: fanoutService,
|
|
4468
|
+
topic: this.topic,
|
|
4469
|
+
root: resolvedRoot,
|
|
4470
|
+
mode,
|
|
4471
|
+
outcome,
|
|
4472
|
+
timeoutMs: options.join?.timeoutMs,
|
|
4473
|
+
before,
|
|
4474
|
+
});
|
|
4354
4475
|
}
|
|
4355
4476
|
}
|
|
4356
4477
|
|
|
@@ -5737,7 +5858,13 @@ export class SharedLog<
|
|
|
5737
5858
|
|
|
5738
5859
|
private persistedReceiptPeerSession(
|
|
5739
5860
|
peerHash: string,
|
|
5740
|
-
):
|
|
5861
|
+
):
|
|
5862
|
+
| {
|
|
5863
|
+
capabilitySession: bigint;
|
|
5864
|
+
peerSession: PeerSession;
|
|
5865
|
+
receiveEpoch: object | null;
|
|
5866
|
+
}
|
|
5867
|
+
| undefined {
|
|
5741
5868
|
// This is a hot receipt/transfer-loop predicate. Keep it allocation-light,
|
|
5742
5869
|
// while mirroring every exact-session gate in
|
|
5743
5870
|
// persistedReceiptReadinessCandidate (which additionally creates public
|
|
@@ -5770,7 +5897,7 @@ export class SharedLog<
|
|
|
5770
5897
|
) {
|
|
5771
5898
|
return undefined;
|
|
5772
5899
|
}
|
|
5773
|
-
return { capabilitySession, peerSession };
|
|
5900
|
+
return { capabilitySession, peerSession, receiveEpoch };
|
|
5774
5901
|
}
|
|
5775
5902
|
|
|
5776
5903
|
private async waitPersistedReceiptRetry(
|
|
@@ -17088,6 +17215,8 @@ export class SharedLog<
|
|
|
17088
17215
|
(this.node as unknown as NodeWithSharedLogNativeDefaults)
|
|
17089
17216
|
.sharedLogNativeDefaults,
|
|
17090
17217
|
);
|
|
17218
|
+
const openProfile = options?.sync?.profile;
|
|
17219
|
+
const openStartedAt = syncProfileStart(openProfile);
|
|
17091
17220
|
this.replicas = {
|
|
17092
17221
|
min:
|
|
17093
17222
|
options?.replicas?.min != null
|
|
@@ -17318,6 +17447,7 @@ export class SharedLog<
|
|
|
17318
17447
|
this.keep = options?.keep;
|
|
17319
17448
|
this.pendingMaturity = new Map();
|
|
17320
17449
|
|
|
17450
|
+
const localStateStartedAt = syncProfileStart(openProfile);
|
|
17321
17451
|
const id = sha256Base64Sync(this.log.id);
|
|
17322
17452
|
const [storage, logScope] = await Promise.all([
|
|
17323
17453
|
this.node.storage.sublevel(id),
|
|
@@ -17373,6 +17503,11 @@ export class SharedLog<
|
|
|
17373
17503
|
this._entryCoordinatesIndex = await replicationIndex.init({
|
|
17374
17504
|
schema: this.indexableDomain.constructorEntry,
|
|
17375
17505
|
});
|
|
17506
|
+
emitAdvisorySyncProfileDuration(openProfile, localStateStartedAt, {
|
|
17507
|
+
name: "sharedLog.open.localState",
|
|
17508
|
+
component: "shared-log",
|
|
17509
|
+
});
|
|
17510
|
+
const blockStoreStartedAt = syncProfileStart(openProfile);
|
|
17376
17511
|
const deferStandaloneNativeRangePlanner =
|
|
17377
17512
|
!!options?.nativeBackbone && options.nativeRangePlanner == null;
|
|
17378
17513
|
await this.openNativeRangePlanner(
|
|
@@ -17423,6 +17558,14 @@ export class SharedLog<
|
|
|
17423
17558
|
storage as unknown as DurableBlockSublevelStore,
|
|
17424
17559
|
);
|
|
17425
17560
|
}
|
|
17561
|
+
emitAdvisorySyncProfileDuration(openProfile, blockStoreStartedAt, {
|
|
17562
|
+
name: "sharedLog.open.blockStore",
|
|
17563
|
+
component: "shared-log",
|
|
17564
|
+
details: {
|
|
17565
|
+
nativeBackbone: this._nativeBackbone != null,
|
|
17566
|
+
directoryConfigured: this.node.directory != null,
|
|
17567
|
+
},
|
|
17568
|
+
});
|
|
17426
17569
|
this.remoteBlocks = new RemoteBlocks({
|
|
17427
17570
|
local: localBlocks,
|
|
17428
17571
|
publish: (message, options) =>
|
|
@@ -17433,38 +17576,76 @@ export class SharedLog<
|
|
|
17433
17576
|
// compatible eager path with bounded validation and storage budgets.
|
|
17434
17577
|
eagerBlocks: options?.eagerBlocks ?? false,
|
|
17435
17578
|
resolveProviders: async (cid, opts) => {
|
|
17579
|
+
const profile = this._logProperties?.sync?.profile;
|
|
17436
17580
|
const maxPeers = 8;
|
|
17581
|
+
const excluded = new Set((opts?.exclude ?? []).slice(0, maxPeers));
|
|
17582
|
+
const lookupPeers = opts?.refresh
|
|
17583
|
+
? Math.min(maxPeers * 2, maxPeers + excluded.size)
|
|
17584
|
+
: maxPeers;
|
|
17585
|
+
const resolutionStartedAt = syncProfileStart(profile);
|
|
17437
17586
|
const localCandidates =
|
|
17438
17587
|
(await this.resolveCandidatePeersForHash(cid, {
|
|
17439
17588
|
signal: opts?.signal,
|
|
17440
|
-
maxPeers,
|
|
17589
|
+
maxPeers: lookupPeers,
|
|
17441
17590
|
})) ?? [];
|
|
17442
|
-
|
|
17591
|
+
const emitResolution = profile
|
|
17592
|
+
? (
|
|
17593
|
+
status: "aborted" | "local" | "directory",
|
|
17594
|
+
targets: number,
|
|
17595
|
+
directoryCandidates = 0,
|
|
17596
|
+
reachableCandidates = 0,
|
|
17597
|
+
) =>
|
|
17598
|
+
emitAdvisorySyncProfileDuration(profile, resolutionStartedAt, {
|
|
17599
|
+
name: "sharedLog.blocks.resolveProviders",
|
|
17600
|
+
component: "shared-log",
|
|
17601
|
+
count: targets,
|
|
17602
|
+
targets,
|
|
17603
|
+
details: {
|
|
17604
|
+
status,
|
|
17605
|
+
refresh: opts?.refresh === true,
|
|
17606
|
+
excluded: excluded.size,
|
|
17607
|
+
lookupPeers,
|
|
17608
|
+
localCandidates: localCandidates.length,
|
|
17609
|
+
directoryCandidates,
|
|
17610
|
+
reachableCandidates,
|
|
17611
|
+
},
|
|
17612
|
+
})
|
|
17613
|
+
: undefined;
|
|
17614
|
+
if (opts?.signal?.aborted) {
|
|
17615
|
+
emitResolution?.("aborted", 0);
|
|
17616
|
+
return [];
|
|
17617
|
+
}
|
|
17443
17618
|
const locallyReachable = new Set(
|
|
17444
17619
|
await this._getLocalReachablePeerHashes(this.topic),
|
|
17445
17620
|
);
|
|
17621
|
+
if (opts?.signal?.aborted) {
|
|
17622
|
+
emitResolution?.("aborted", 0, 0, locallyReachable.size);
|
|
17623
|
+
return [];
|
|
17624
|
+
}
|
|
17446
17625
|
const confirmed = this._checkedPrune.getConfirmedReplicators(cid);
|
|
17447
17626
|
const contacted = this._checkedPrune.getContactedReplicators(cid);
|
|
17627
|
+
const hasProviderEvidence = (peer: string) =>
|
|
17628
|
+
confirmed?.has(peer) === true ||
|
|
17629
|
+
contacted?.has(peer) ||
|
|
17630
|
+
this.uniqueReplicators.has(peer);
|
|
17448
17631
|
const hasLiveCandidate = localCandidates.some(
|
|
17449
|
-
(peer) =>
|
|
17450
|
-
locallyReachable.has(peer) &&
|
|
17451
|
-
(confirmed?.has(peer) ||
|
|
17452
|
-
contacted?.has(peer) ||
|
|
17453
|
-
this.uniqueReplicators.has(peer)),
|
|
17632
|
+
(peer) => locallyReachable.has(peer) && hasProviderEvidence(peer),
|
|
17454
17633
|
);
|
|
17455
17634
|
|
|
17456
17635
|
// Only reachability corroborated by provider/replicator evidence may
|
|
17457
17636
|
// bypass the initial CID lookup. Arbitrary bootstrap connections are
|
|
17458
17637
|
// useful fallbacks, but are not evidence that they hold this block.
|
|
17459
17638
|
if (hasLiveCandidate && !opts?.refresh) {
|
|
17460
|
-
|
|
17639
|
+
const selected = localCandidates.slice(0, maxPeers);
|
|
17640
|
+
emitResolution?.("local", selected.length, 0, locallyReachable.size);
|
|
17641
|
+
return selected;
|
|
17461
17642
|
}
|
|
17462
17643
|
|
|
17463
17644
|
let directoryProviders: string[] = [];
|
|
17464
17645
|
try {
|
|
17465
17646
|
const query = (namespace: string) =>
|
|
17466
17647
|
fanoutService?.queryProviders(namespace, {
|
|
17467
|
-
want:
|
|
17648
|
+
want: lookupPeers,
|
|
17468
17649
|
timeoutMs: 2_000,
|
|
17469
17650
|
queryTimeoutMs: 500,
|
|
17470
17651
|
bootstrapMaxPeers: 2,
|
|
@@ -17476,12 +17657,21 @@ export class SharedLog<
|
|
|
17476
17657
|
]);
|
|
17477
17658
|
for (const result of results) {
|
|
17478
17659
|
if (result.status === "fulfilled") {
|
|
17479
|
-
directoryProviders.push(...result.value);
|
|
17660
|
+
directoryProviders.push(...result.value.slice(0, lookupPeers));
|
|
17480
17661
|
}
|
|
17481
17662
|
}
|
|
17482
17663
|
} catch {
|
|
17483
17664
|
// Ignore discovery failures; local evidence remains usable.
|
|
17484
17665
|
}
|
|
17666
|
+
if (opts?.signal?.aborted) {
|
|
17667
|
+
emitResolution?.(
|
|
17668
|
+
"aborted",
|
|
17669
|
+
0,
|
|
17670
|
+
directoryProviders.length,
|
|
17671
|
+
locallyReachable.size,
|
|
17672
|
+
);
|
|
17673
|
+
return [];
|
|
17674
|
+
}
|
|
17485
17675
|
|
|
17486
17676
|
const selected: string[] = [];
|
|
17487
17677
|
const selectedSet = new Set<string>();
|
|
@@ -17492,15 +17682,62 @@ export class SharedLog<
|
|
|
17492
17682
|
selectedSet.add(peer);
|
|
17493
17683
|
selected.push(peer);
|
|
17494
17684
|
};
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
(
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
|
|
17502
|
-
|
|
17503
|
-
|
|
17685
|
+
const append = (
|
|
17686
|
+
providers: readonly string[],
|
|
17687
|
+
includeExcluded: boolean,
|
|
17688
|
+
predicate?: (provider: string) => boolean,
|
|
17689
|
+
) => {
|
|
17690
|
+
for (const provider of providers) {
|
|
17691
|
+
if (selected.length >= maxPeers) return;
|
|
17692
|
+
if (
|
|
17693
|
+
excluded.has(provider) === includeExcluded &&
|
|
17694
|
+
(!predicate || predicate(provider))
|
|
17695
|
+
) {
|
|
17696
|
+
add(provider);
|
|
17697
|
+
}
|
|
17698
|
+
}
|
|
17699
|
+
};
|
|
17700
|
+
const appendInterleaved = (includeExcluded: boolean) => {
|
|
17701
|
+
for (
|
|
17702
|
+
let index = 0;
|
|
17703
|
+
selected.length < maxPeers &&
|
|
17704
|
+
(index < localCandidates.length ||
|
|
17705
|
+
index < directoryProviders.length);
|
|
17706
|
+
index++
|
|
17707
|
+
) {
|
|
17708
|
+
const local = localCandidates[index];
|
|
17709
|
+
if (local && excluded.has(local) === includeExcluded) add(local);
|
|
17710
|
+
const directory = directoryProviders[index];
|
|
17711
|
+
if (directory && excluded.has(directory) === includeExcluded) {
|
|
17712
|
+
add(directory);
|
|
17713
|
+
}
|
|
17714
|
+
}
|
|
17715
|
+
};
|
|
17716
|
+
if (opts?.refresh) {
|
|
17717
|
+
// Retry results are wider than the regular eight-peer window. Prefer
|
|
17718
|
+
// untried reachable holders, then the remaining fresh directory
|
|
17719
|
+
// evidence, without discarding attempted peers as bounded transient-
|
|
17720
|
+
// failure fallbacks.
|
|
17721
|
+
append(directoryProviders, false, (peer) =>
|
|
17722
|
+
locallyReachable.has(peer),
|
|
17723
|
+
);
|
|
17724
|
+
append(
|
|
17725
|
+
localCandidates,
|
|
17726
|
+
false,
|
|
17727
|
+
(peer) => locallyReachable.has(peer) && hasProviderEvidence(peer),
|
|
17728
|
+
);
|
|
17729
|
+
append(directoryProviders, false);
|
|
17730
|
+
append(localCandidates, false);
|
|
17731
|
+
} else {
|
|
17732
|
+
appendInterleaved(false);
|
|
17733
|
+
}
|
|
17734
|
+
appendInterleaved(true);
|
|
17735
|
+
emitResolution?.(
|
|
17736
|
+
"directory",
|
|
17737
|
+
selected.length,
|
|
17738
|
+
directoryProviders.length,
|
|
17739
|
+
locallyReachable.size,
|
|
17740
|
+
);
|
|
17504
17741
|
return selected;
|
|
17505
17742
|
},
|
|
17506
17743
|
watchProviders: fanoutService
|
|
@@ -17548,7 +17785,13 @@ export class SharedLog<
|
|
|
17548
17785
|
: undefined,
|
|
17549
17786
|
});
|
|
17550
17787
|
|
|
17551
|
-
const
|
|
17788
|
+
const remoteBlocksStartedAt = syncProfileStart(openProfile);
|
|
17789
|
+
const remoteBlocksStartPromise = this.remoteBlocks.start().then(() => {
|
|
17790
|
+
emitAdvisorySyncProfileDuration(openProfile, remoteBlocksStartedAt, {
|
|
17791
|
+
name: "sharedLog.open.remoteBlocks",
|
|
17792
|
+
component: "shared-log",
|
|
17793
|
+
});
|
|
17794
|
+
});
|
|
17552
17795
|
const hasIndexedReplicationInfo =
|
|
17553
17796
|
(await this.replicationIndex.count({
|
|
17554
17797
|
query: [
|
|
@@ -17764,6 +18007,7 @@ export class SharedLog<
|
|
|
17764
18007
|
// joins rely on: a replicate:false observer syncing a head whose parents
|
|
17765
18008
|
// are not local would fail block resolution, and Log.join treats that as
|
|
17766
18009
|
// recoverable and skips the entry without persisting anything.
|
|
18010
|
+
const lowerLogStartedAt = syncProfileStart(openProfile);
|
|
17767
18011
|
await this.log.open(this.remoteBlocks, this.node.identity, {
|
|
17768
18012
|
keychain: this.node.services.keychain,
|
|
17769
18013
|
resolveRemotePeers: (hash, options) =>
|
|
@@ -17795,6 +18039,10 @@ export class SharedLog<
|
|
|
17795
18039
|
},
|
|
17796
18040
|
indexer: logIndex,
|
|
17797
18041
|
});
|
|
18042
|
+
emitAdvisorySyncProfileDuration(openProfile, lowerLogStartedAt, {
|
|
18043
|
+
name: "sharedLog.open.lowerLog",
|
|
18044
|
+
component: "shared-log",
|
|
18045
|
+
});
|
|
17798
18046
|
this._persistedReceiptStorage = this.resolvePersistedReceiptStorage();
|
|
17799
18047
|
try {
|
|
17800
18048
|
const recovered =
|
|
@@ -17859,6 +18107,7 @@ export class SharedLog<
|
|
|
17859
18107
|
this._onUnsubscription(event),
|
|
17860
18108
|
);
|
|
17861
18109
|
});
|
|
18110
|
+
const communicationStartedAt = syncProfileStart(openProfile);
|
|
17862
18111
|
await Promise.all([
|
|
17863
18112
|
this.rpc.open({
|
|
17864
18113
|
queryType: TransportMessage,
|
|
@@ -17877,7 +18126,12 @@ export class SharedLog<
|
|
|
17877
18126
|
this._onUnsubscriptionFn,
|
|
17878
18127
|
),
|
|
17879
18128
|
]);
|
|
18129
|
+
emitAdvisorySyncProfileDuration(openProfile, communicationStartedAt, {
|
|
18130
|
+
name: "sharedLog.open.rpcSubscriptions",
|
|
18131
|
+
component: "shared-log",
|
|
18132
|
+
});
|
|
17880
18133
|
|
|
18134
|
+
const providerChannelStartedAt = syncProfileStart(openProfile);
|
|
17881
18135
|
const fanoutOpenPromise = this._openFanoutChannel(options?.fanout);
|
|
17882
18136
|
// Mark previously-owned replication ranges as "new" only when they already exist.
|
|
17883
18137
|
// Fresh opens have nothing to touch here, so skip the extra scan/write entirely.
|
|
@@ -17885,6 +18139,11 @@ export class SharedLog<
|
|
|
17885
18139
|
? this.updateTimestampOfOwnedReplicationRanges()
|
|
17886
18140
|
: Promise.resolve();
|
|
17887
18141
|
await Promise.all([fanoutOpenPromise, updateOwnedReplicationPromise]);
|
|
18142
|
+
emitAdvisorySyncProfileDuration(openProfile, providerChannelStartedAt, {
|
|
18143
|
+
name: "sharedLog.open.providerAndOwnership",
|
|
18144
|
+
component: "shared-log",
|
|
18145
|
+
details: { indexedReplicationInfo: hasIndexedReplicationInfo },
|
|
18146
|
+
});
|
|
17888
18147
|
|
|
17889
18148
|
// if we had a previous session with replication info, and new replication info dictates that we unreplicate
|
|
17890
18149
|
// we should do that. Otherwise if options is a unreplication we dont need to do anything because
|
|
@@ -17902,17 +18161,35 @@ export class SharedLog<
|
|
|
17902
18161
|
this.node.identity.publicKey,
|
|
17903
18162
|
));
|
|
17904
18163
|
|
|
18164
|
+
const replicationStartedAt = syncProfileStart(openProfile);
|
|
18165
|
+
let replicationAction: "replace" | "resume" | "reset";
|
|
17905
18166
|
if (hasIndexedReplicationInfo && isUnreplicationOptionsDefined) {
|
|
18167
|
+
replicationAction = "replace";
|
|
17906
18168
|
await this.replicate(options?.replicate, { checkDuplicates: true });
|
|
17907
18169
|
} else if (canResumeReplication) {
|
|
18170
|
+
replicationAction = "resume";
|
|
17908
18171
|
// dont do anthing since we are alread replicating stuff
|
|
17909
18172
|
} else {
|
|
18173
|
+
replicationAction = "reset";
|
|
17910
18174
|
await this.replicate(options?.replicate, {
|
|
17911
18175
|
checkDuplicates: true,
|
|
17912
18176
|
reset: true,
|
|
17913
18177
|
});
|
|
17914
18178
|
}
|
|
18179
|
+
emitAdvisorySyncProfileDuration(openProfile, replicationStartedAt, {
|
|
18180
|
+
name: "sharedLog.open.replication",
|
|
18181
|
+
component: "shared-log",
|
|
18182
|
+
details: {
|
|
18183
|
+
hadIndexedState: hasIndexedReplicationInfo,
|
|
18184
|
+
action: replicationAction,
|
|
18185
|
+
},
|
|
18186
|
+
});
|
|
18187
|
+
const synchronizerStartedAt = syncProfileStart(openProfile);
|
|
17915
18188
|
await this.syncronizer.open();
|
|
18189
|
+
emitAdvisorySyncProfileDuration(openProfile, synchronizerStartedAt, {
|
|
18190
|
+
name: "sharedLog.open.synchronizer",
|
|
18191
|
+
component: "shared-log",
|
|
18192
|
+
});
|
|
17916
18193
|
|
|
17917
18194
|
this.interval = setInterval(() => {
|
|
17918
18195
|
void this.rebalanceParticipationDebounced?.call();
|
|
@@ -17920,6 +18197,10 @@ export class SharedLog<
|
|
|
17920
18197
|
|
|
17921
18198
|
this._instanceLifecycle!.markOpenComplete();
|
|
17922
18199
|
this.scheduleReplicationStatusRefresh();
|
|
18200
|
+
emitAdvisorySyncProfileDuration(openProfile, openStartedAt, {
|
|
18201
|
+
name: "sharedLog.open.total",
|
|
18202
|
+
component: "shared-log",
|
|
18203
|
+
});
|
|
17923
18204
|
}
|
|
17924
18205
|
|
|
17925
18206
|
private toNativeReplicationRange(
|
|
@@ -22900,6 +23181,17 @@ export class SharedLog<
|
|
|
22900
23181
|
if (!context.from.equals(this.node.identity.publicKey)) {
|
|
22901
23182
|
const capabilityTransportSession = context.message?.header?.session;
|
|
22902
23183
|
const capabilityTimestamp = context.message?.header?.timestamp;
|
|
23184
|
+
const previousCapabilitySession =
|
|
23185
|
+
this._peerSyncCapabilitySessions.get(receiveFromHash);
|
|
23186
|
+
const previousCapabilityTimestamp =
|
|
23187
|
+
this._peerSyncCapabilityTimestamps.get(receiveFromHash);
|
|
23188
|
+
const requestsRemoteFullRearm =
|
|
23189
|
+
(msg.capabilities & SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM) !==
|
|
23190
|
+
0;
|
|
23191
|
+
// The rearm bit is a one-shot authenticated command, not a sticky
|
|
23192
|
+
// negotiated capability. Store and promote only the steady bits.
|
|
23193
|
+
const steadyCapabilities =
|
|
23194
|
+
msg.capabilities & ~SYNC_CAPABILITY_REPLICATION_INFO_V2_REARM;
|
|
22903
23195
|
// No await separates this from the capture above, so the captured
|
|
22904
23196
|
// window state is exact: the legacy re-read of the opening map here
|
|
22905
23197
|
// could never observe a different value.
|
|
@@ -22912,7 +23204,7 @@ export class SharedLog<
|
|
|
22912
23204
|
// cleanup cannot erase it before the opening transition commits.
|
|
22913
23205
|
this.observePeerSyncCapabilities({
|
|
22914
23206
|
peerHash: receiveFromHash,
|
|
22915
|
-
capabilities:
|
|
23207
|
+
capabilities: steadyCapabilities,
|
|
22916
23208
|
transportSession: capabilityTransportSession,
|
|
22917
23209
|
timestamp: capabilityTimestamp,
|
|
22918
23210
|
openingSession: receiveSession!,
|
|
@@ -22920,7 +23212,7 @@ export class SharedLog<
|
|
|
22920
23212
|
} else {
|
|
22921
23213
|
const observed = this.observePeerSyncCapabilities({
|
|
22922
23214
|
peerHash: receiveFromHash,
|
|
22923
|
-
capabilities:
|
|
23215
|
+
capabilities: steadyCapabilities,
|
|
22924
23216
|
transportSession: capabilityTransportSession,
|
|
22925
23217
|
timestamp: capabilityTimestamp,
|
|
22926
23218
|
});
|
|
@@ -22929,6 +23221,33 @@ export class SharedLog<
|
|
|
22929
23221
|
context.from,
|
|
22930
23222
|
receiveSession,
|
|
22931
23223
|
);
|
|
23224
|
+
const freshExactRearm =
|
|
23225
|
+
requestsRemoteFullRearm &&
|
|
23226
|
+
capabilityTransportSession !== undefined &&
|
|
23227
|
+
capabilityTimestamp !== undefined &&
|
|
23228
|
+
previousCapabilitySession === capabilityTransportSession &&
|
|
23229
|
+
previousCapabilityTimestamp !== undefined &&
|
|
23230
|
+
capabilityTimestamp > previousCapabilityTimestamp;
|
|
23231
|
+
if (
|
|
23232
|
+
freshExactRearm &&
|
|
23233
|
+
this._v2Receive.isCurrentActive({
|
|
23234
|
+
peerHash: receiveFromHash,
|
|
23235
|
+
peerSession: receiveSession,
|
|
23236
|
+
receiveEpoch:
|
|
23237
|
+
this._peerSessions.receiveEpoch(receiveFromHash),
|
|
23238
|
+
senderTransportSession: capabilityTransportSession,
|
|
23239
|
+
})
|
|
23240
|
+
) {
|
|
23241
|
+
// Rotate the receiver grant/challenge before requesting Full. A
|
|
23242
|
+
// sender rebuilt from no state starts at sequence one, which an
|
|
23243
|
+
// active receiver's old sequence fence must otherwise reject.
|
|
23244
|
+
this._v2Receive.advanceRecovery({
|
|
23245
|
+
peerHash: receiveFromHash,
|
|
23246
|
+
peerSession: receiveSession,
|
|
23247
|
+
receiveEpoch:
|
|
23248
|
+
this._peerSessions.receiveEpoch(receiveFromHash),
|
|
23249
|
+
});
|
|
23250
|
+
}
|
|
22932
23251
|
} else if (observed && receiveSession === null) {
|
|
22933
23252
|
// A capability can arrive before the sender's topic Subscribe after
|
|
22934
23253
|
// reconnect. Ask that authenticated peer for its authoritative
|
|
@@ -24059,8 +24378,11 @@ export class SharedLog<
|
|
|
24059
24378
|
throwIfInactive();
|
|
24060
24379
|
}
|
|
24061
24380
|
|
|
24062
|
-
private nudgePersistedReceiptPeerReadiness(
|
|
24063
|
-
|
|
24381
|
+
private nudgePersistedReceiptPeerReadiness(
|
|
24382
|
+
publicKey: PublicSignKey,
|
|
24383
|
+
signal: AbortSignal,
|
|
24384
|
+
): void {
|
|
24385
|
+
if (this.closed || signal.aborted) return;
|
|
24064
24386
|
const peerHash = publicKey.hashcode();
|
|
24065
24387
|
const peerSession = this._peerSessions.current(peerHash);
|
|
24066
24388
|
if (
|
|
@@ -24091,6 +24413,22 @@ export class SharedLog<
|
|
|
24091
24413
|
peerSession,
|
|
24092
24414
|
receiveEpoch,
|
|
24093
24415
|
});
|
|
24416
|
+
const receiptTarget = this.persistedReceiptPeerSession(peerHash);
|
|
24417
|
+
if (
|
|
24418
|
+
receiptTarget &&
|
|
24419
|
+
!this._v2Send.hasCurrentStateForPeer({
|
|
24420
|
+
peerHash,
|
|
24421
|
+
peerSession: receiptTarget.peerSession,
|
|
24422
|
+
receiverTransportSession: receiptTarget.capabilitySession,
|
|
24423
|
+
})
|
|
24424
|
+
) {
|
|
24425
|
+
this._v2Receive.reAdvertiseLocalCapabilityForRemoteFull({
|
|
24426
|
+
peerHash,
|
|
24427
|
+
peerSession: receiptTarget.peerSession,
|
|
24428
|
+
receiveEpoch: receiptTarget.receiveEpoch,
|
|
24429
|
+
signal,
|
|
24430
|
+
});
|
|
24431
|
+
}
|
|
24094
24432
|
this.scheduleReplicationInfoV2Recovery(publicKey);
|
|
24095
24433
|
}
|
|
24096
24434
|
|
|
@@ -24359,18 +24697,26 @@ export class SharedLog<
|
|
|
24359
24697
|
throw new AbortError("Persisted-receipt readiness wait settled");
|
|
24360
24698
|
}
|
|
24361
24699
|
};
|
|
24700
|
+
const recoveryDelayMs = Math.max(
|
|
24701
|
+
50,
|
|
24702
|
+
Math.min(1_000, this.waitForReplicatorRequestIntervalMs),
|
|
24703
|
+
);
|
|
24362
24704
|
const armRecoveryTick = () => {
|
|
24363
24705
|
if (settled || recoveryTimer) return;
|
|
24364
|
-
const delayMs = Math.max(
|
|
24365
|
-
50,
|
|
24366
|
-
Math.min(1_000, this.waitForReplicatorRequestIntervalMs),
|
|
24367
|
-
);
|
|
24368
24706
|
recoveryTimer = setTimeout(() => {
|
|
24369
24707
|
recoveryTimer = undefined;
|
|
24370
24708
|
if (!continueWait()) return;
|
|
24371
|
-
this.nudgePersistedReceiptPeerReadiness(key);
|
|
24709
|
+
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24710
|
+
if (checkInFlight) {
|
|
24711
|
+
// Confirmation can legitimately span several recovery intervals.
|
|
24712
|
+
// Keep repairing the exact current generation without aborting its
|
|
24713
|
+
// one application-confirmation waiter on every tick.
|
|
24714
|
+
rerun = true;
|
|
24715
|
+
armRecoveryTick();
|
|
24716
|
+
return;
|
|
24717
|
+
}
|
|
24372
24718
|
scheduleCheck();
|
|
24373
|
-
},
|
|
24719
|
+
}, recoveryDelayMs);
|
|
24374
24720
|
recoveryTimer.unref?.();
|
|
24375
24721
|
};
|
|
24376
24722
|
const runCheck = async () => {
|
|
@@ -24403,6 +24749,11 @@ export class SharedLog<
|
|
|
24403
24749
|
) {
|
|
24404
24750
|
const target = this.persistedReceiptPeerSession(peerHash);
|
|
24405
24751
|
if (target) {
|
|
24752
|
+
// A confirmation waiter cannot create an outbound V2 stream when
|
|
24753
|
+
// that exact session state is missing. Nudge first, then keep the
|
|
24754
|
+
// recovery tick alive while the single confirmation wait remains.
|
|
24755
|
+
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24756
|
+
armRecoveryTick();
|
|
24406
24757
|
const currentConfirmationController = new AbortController();
|
|
24407
24758
|
confirmationController = currentConfirmationController;
|
|
24408
24759
|
try {
|
|
@@ -24447,7 +24798,7 @@ export class SharedLog<
|
|
|
24447
24798
|
}
|
|
24448
24799
|
}
|
|
24449
24800
|
if (!continueWait()) return;
|
|
24450
|
-
this.nudgePersistedReceiptPeerReadiness(key);
|
|
24801
|
+
this.nudgePersistedReceiptPeerReadiness(key, operationSignal);
|
|
24451
24802
|
} catch (error) {
|
|
24452
24803
|
if (!settled) reject(error);
|
|
24453
24804
|
} finally {
|