@peerbit/pubsub 5.4.4 → 5.4.6
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/fanout-tree.d.ts +7 -2
- package/dist/src/fanout-tree.d.ts.map +1 -1
- package/dist/src/fanout-tree.js +339 -81
- package/dist/src/fanout-tree.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +303 -78
- package/dist/src/index.js.map +1 -1
- package/package.json +4 -4
- package/src/fanout-tree.ts +462 -111
- package/src/index.ts +381 -98
package/dist/src/index.d.ts
CHANGED
|
@@ -166,6 +166,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
166
166
|
private hostOwnedShardRootsInFlight?;
|
|
167
167
|
private hostOwnedShardRootsDirty;
|
|
168
168
|
private readonly signedTopicRootCandidateClaims;
|
|
169
|
+
private readonly suppressedDepartedTopicRootCandidateClaims;
|
|
169
170
|
private readonly topicRootCandidateClaimReplayFloors;
|
|
170
171
|
private localSignedTopicRootCandidateClaim?;
|
|
171
172
|
private topicRootCandidateClaimMaintenanceTimer?;
|
|
@@ -199,7 +200,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
199
200
|
start(): Promise<void>;
|
|
200
201
|
stop(): Promise<void>;
|
|
201
202
|
addPeer(peerId: Libp2pPeerId, publicKey: PublicSignKey, protocol: string, connId: string): PeerStreams;
|
|
202
|
-
protected _removePeer(publicKey: PublicSignKey): Promise<PeerStreams>;
|
|
203
|
+
protected _removePeer(publicKey: PublicSignKey): Promise<PeerStreams | undefined>;
|
|
203
204
|
private maybeDisableAutoTopicRootCandidatesIfExternallyConfigured;
|
|
204
205
|
private clearTopicRootCandidateClaimTimer;
|
|
205
206
|
private clearSignedTopicRootCandidateState;
|
|
@@ -211,6 +212,7 @@ export declare class TopicControlPlane extends DirectStream<PubSubEvents> implem
|
|
|
211
212
|
private pruneExpiredTopicRootCandidateClaims;
|
|
212
213
|
private rebuildAutoTopicRootCandidatesFromClaims;
|
|
213
214
|
private retainSignedTopicRootCandidateClaim;
|
|
215
|
+
private isDepartedTopicRootCandidateClaimSuppressed;
|
|
214
216
|
private canAdvanceTopicRootCandidateClaimReplayFloor;
|
|
215
217
|
private canRetainTopicRootCandidateClaimOrigin;
|
|
216
218
|
private consumeTopicRootCandidateClaimVerifyBudget;
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,IAAI,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACN,aAAa,EAGb,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAIN,KAAK,MAAM,EAEX,KAAK,YAAY,EAIjB,gBAAgB,EAehB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,YAAY,EACZ,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAEhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEN,mBAAmB,EAEnB,WAAW,EAEX,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAGd,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,SAAS,EACd,cAAc,EACd,KAAK,gBAAgB,EAOrB,MAAM,2BAA2B,CAAC;AAUnC,OAAO,KAAK,EACX,UAAU,EACV,wBAAwB,EAExB,qBAAqB,EACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,MAAM,+BAA+B,CAAC;AAEvC,cAAc,kBAAkB,CAAC;AAIjC,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,mBAAmB,MAAM,iCAAiC,CAAC;AACvE,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,MAAM,oCAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -134,9 +134,15 @@ const TOPIC_ROOT_CANDIDATE_CLAIM_VERIFY_REFILL_MS = 60_000;
|
|
|
134
134
|
const TOPIC_ROOT_CANDIDATE_CLAIM_VERIFY_PEERS_MAX = 4_096;
|
|
135
135
|
const sameCandidates = (left, right) => left.length === right.length &&
|
|
136
136
|
left.every((candidate, index) => candidate === right[index]);
|
|
137
|
-
//
|
|
138
|
-
//
|
|
137
|
+
// Retain the previous 12 second allowance as one deadline for the complete
|
|
138
|
+
// peer-query phase. It must not multiply by peer count or retry count.
|
|
139
139
|
const DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS = 12_000;
|
|
140
|
+
// A responder may need to open an outbound stream after an inbound-only dial.
|
|
141
|
+
// Bounded rounds leave enough time for that setup while still allowing a lost
|
|
142
|
+
// request or a newly-ready stream to be retried within the phase deadline.
|
|
143
|
+
const TOPIC_ROOT_QUERY_ROUND_TIMEOUT_MS = 3_000;
|
|
144
|
+
const TOPIC_ROOT_HOST_SCAN_TIMEOUT_MS = DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS;
|
|
145
|
+
const TOPIC_ROOT_HOST_SCAN_CONCURRENCY = 8;
|
|
140
146
|
const DIRECT_SHARD_ROOT_CONFIRM_TIMEOUT_MS = 2_000;
|
|
141
147
|
const DEFAULT_PUBSUB_FANOUT_CHANNEL_OPTIONS = {
|
|
142
148
|
msgRate: 30,
|
|
@@ -218,6 +224,12 @@ export class TopicControlPlane extends DirectStream {
|
|
|
218
224
|
hostOwnedShardRootsInFlight;
|
|
219
225
|
hostOwnedShardRootsDirty = false;
|
|
220
226
|
signedTopicRootCandidateClaims = new Map();
|
|
227
|
+
// A directly observed departure makes only that origin's current lease
|
|
228
|
+
// ineffective locally. This is not a component-wide withdrawal: claims learned
|
|
229
|
+
// only through relays keep their existing lease semantics. Keeping the verified
|
|
230
|
+
// bytes + replay floor prevents a relay from resurrecting the departed lease;
|
|
231
|
+
// the map is bounded by the same retained-origin cap as the claim map.
|
|
232
|
+
suppressedDepartedTopicRootCandidateClaims = new Map();
|
|
221
233
|
// Active claims expire, but their per-origin timestamp floors survive for the
|
|
222
234
|
// auto-mode lifecycle so a frozen wall clock cannot renew a replayed lease.
|
|
223
235
|
// Retaining only the deterministic lowest origins keeps this state hard-bounded.
|
|
@@ -481,7 +493,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
481
493
|
if (peer.isWritable) {
|
|
482
494
|
void this.sendAutoTopicRootCandidates([peer]).catch(() => { });
|
|
483
495
|
}
|
|
484
|
-
if (
|
|
496
|
+
if (existingPeer !== peer) {
|
|
485
497
|
peer.addEventListener("stream:outbound", sendWhenOutboundReady);
|
|
486
498
|
peer.addEventListener("close", () => peer.removeEventListener("stream:outbound", sendWhenOutboundReady), { once: true });
|
|
487
499
|
}
|
|
@@ -494,12 +506,31 @@ export class TopicControlPlane extends DirectStream {
|
|
|
494
506
|
const protocol = this.peers.get(hash)?.protocol;
|
|
495
507
|
const hadSignedPeer = [...this.peers.values()].some((peer) => peer.protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1);
|
|
496
508
|
const removed = await super._removePeer(publicKey);
|
|
509
|
+
// A replacement may arrive after base removal but before this continuation.
|
|
510
|
+
if (!removed || this.peers.has(hash))
|
|
511
|
+
return;
|
|
512
|
+
let suppressedDepartedOrigin = false;
|
|
513
|
+
if (removed &&
|
|
514
|
+
this.autoTopicRootCandidates &&
|
|
515
|
+
protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1 &&
|
|
516
|
+
this.signedTopicRootCandidateClaims.has(hash)) {
|
|
517
|
+
const claim = this.signedTopicRootCandidateClaims.get(hash);
|
|
518
|
+
const suppressed = this.suppressedDepartedTopicRootCandidateClaims.get(hash);
|
|
519
|
+
suppressedDepartedOrigin =
|
|
520
|
+
!suppressed ||
|
|
521
|
+
suppressed.timestamp !== claim.timestamp ||
|
|
522
|
+
!bytesEqual(suppressed.bytes, claim.bytes);
|
|
523
|
+
this.suppressedDepartedTopicRootCandidateClaims.set(hash, {
|
|
524
|
+
bytes: claim.bytes,
|
|
525
|
+
timestamp: claim.timestamp,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
497
528
|
if (this.autoTopicRootCandidates &&
|
|
498
529
|
(protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_0 ||
|
|
499
530
|
protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1)) {
|
|
500
531
|
const hasSignedPeer = [...this.peers.values()].some((peer) => peer.protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1);
|
|
501
532
|
this.rebuildAutoTopicRootCandidatesFromClaims(BigInt(Date.now()), {
|
|
502
|
-
immediate: hadSignedPeer !== hasSignedPeer,
|
|
533
|
+
immediate: suppressedDepartedOrigin || hadSignedPeer !== hasSignedPeer,
|
|
503
534
|
});
|
|
504
535
|
}
|
|
505
536
|
return removed;
|
|
@@ -538,6 +569,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
538
569
|
clearSignedTopicRootCandidateState() {
|
|
539
570
|
this.clearTopicRootCandidateClaimTimer();
|
|
540
571
|
this.signedTopicRootCandidateClaims.clear();
|
|
572
|
+
this.suppressedDepartedTopicRootCandidateClaims.clear();
|
|
541
573
|
this.topicRootCandidateClaimReplayFloors.clear();
|
|
542
574
|
this.localSignedTopicRootCandidateClaim = undefined;
|
|
543
575
|
this.topicRootCandidateClaimRefreshNotBefore = undefined;
|
|
@@ -749,6 +781,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
749
781
|
for (const [origin, claim] of this.signedTopicRootCandidateClaims) {
|
|
750
782
|
if (claim.expires <= now || claim.acceptUntil <= monotonicNow) {
|
|
751
783
|
this.signedTopicRootCandidateClaims.delete(origin);
|
|
784
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(origin);
|
|
752
785
|
changed = true;
|
|
753
786
|
}
|
|
754
787
|
}
|
|
@@ -768,7 +801,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
768
801
|
? []
|
|
769
802
|
: [this.publicKeyHash]),
|
|
770
803
|
...legacyDirectCandidates,
|
|
771
|
-
...this.signedTopicRootCandidateClaims.
|
|
804
|
+
...[...this.signedTopicRootCandidateClaims.entries()]
|
|
805
|
+
.filter(([origin, claim]) => !this.isDepartedTopicRootCandidateClaimSuppressed(origin, claim))
|
|
806
|
+
.map(([origin]) => origin),
|
|
772
807
|
]),
|
|
773
808
|
]
|
|
774
809
|
.filter(isCanonicalTopicRootCandidate)
|
|
@@ -802,6 +837,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
802
837
|
// because the retained maximum only decreases, it cannot re-enter.
|
|
803
838
|
this.topicRootCandidateClaimReplayFloors.delete(highest);
|
|
804
839
|
this.signedTopicRootCandidateClaims.delete(highest);
|
|
840
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(highest);
|
|
805
841
|
if (highest === this.publicKeyHash) {
|
|
806
842
|
this.localSignedTopicRootCandidateClaim = undefined;
|
|
807
843
|
}
|
|
@@ -809,8 +845,14 @@ export class TopicControlPlane extends DirectStream {
|
|
|
809
845
|
}
|
|
810
846
|
this.topicRootCandidateClaimReplayFloors.set(origin, claim.timestamp);
|
|
811
847
|
this.signedTopicRootCandidateClaims.set(origin, claim);
|
|
848
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(origin);
|
|
812
849
|
return true;
|
|
813
850
|
}
|
|
851
|
+
isDepartedTopicRootCandidateClaimSuppressed(origin, claim) {
|
|
852
|
+
const suppressed = this.suppressedDepartedTopicRootCandidateClaims.get(origin);
|
|
853
|
+
return (suppressed?.timestamp === claim.timestamp &&
|
|
854
|
+
bytesEqual(suppressed.bytes, claim.bytes));
|
|
855
|
+
}
|
|
814
856
|
canAdvanceTopicRootCandidateClaimReplayFloor(origin, timestamp) {
|
|
815
857
|
const floor = this.topicRootCandidateClaimReplayFloors.get(origin);
|
|
816
858
|
if (floor !== undefined) {
|
|
@@ -871,7 +913,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
871
913
|
this.topicRootCandidateClaimGlobalVerifyBudget.remaining -= 1;
|
|
872
914
|
return true;
|
|
873
915
|
}
|
|
874
|
-
async importSignedTopicRootCandidateClaim(rawClaim, outerPeerHash) {
|
|
916
|
+
async importSignedTopicRootCandidateClaim(rawClaim, outerPeerHash, expectedPeer) {
|
|
875
917
|
const lifecycleRevision = this.topicControlPlaneLifecycleRevision;
|
|
876
918
|
if (rawClaim.byteLength === 0 ||
|
|
877
919
|
rawClaim.byteLength > TOPIC_ROOT_CANDIDATE_CLAIM_MAX_BYTES) {
|
|
@@ -910,10 +952,30 @@ export class TopicControlPlane extends DirectStream {
|
|
|
910
952
|
if (acceptanceDeadline === undefined) {
|
|
911
953
|
return false;
|
|
912
954
|
}
|
|
955
|
+
this.rebuildAutoTopicRootCandidatesFromClaims(now);
|
|
956
|
+
const retained = this.signedTopicRootCandidateClaims.get(origin);
|
|
957
|
+
if (outerPeerHash === origin &&
|
|
958
|
+
expectedPeer !== undefined &&
|
|
959
|
+
expectedPeer.protocol === TOPIC_CONTROL_PLANE_PROTOCOL_V2_1 &&
|
|
960
|
+
this.peers.get(outerPeerHash) === expectedPeer &&
|
|
961
|
+
lifecycleRevision === this.topicControlPlaneLifecycleRevision &&
|
|
962
|
+
this.autoTopicRootCandidates &&
|
|
963
|
+
this.started &&
|
|
964
|
+
!this.stopping &&
|
|
965
|
+
!this.topicControlPlaneStopping &&
|
|
966
|
+
retained?.timestamp === timestamp &&
|
|
967
|
+
bytesEqual(retained.bytes, rawClaim) &&
|
|
968
|
+
this.isDepartedTopicRootCandidateClaimSuppressed(origin, retained)) {
|
|
969
|
+
// These exact bytes were already verified and remain under the original
|
|
970
|
+
// monotonic acceptance deadline. A current authenticated origin stream is
|
|
971
|
+
// sufficient liveness evidence; do not advance the floor or lease.
|
|
972
|
+
this.suppressedDepartedTopicRootCandidateClaims.delete(origin);
|
|
973
|
+
this.rebuildAutoTopicRootCandidatesFromClaims(now, { immediate: true });
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
913
976
|
if (!this.canAdvanceTopicRootCandidateClaimReplayFloor(origin, timestamp)) {
|
|
914
977
|
return false;
|
|
915
978
|
}
|
|
916
|
-
this.rebuildAutoTopicRootCandidatesFromClaims(now);
|
|
917
979
|
if (!this.consumeTopicRootCandidateClaimVerifyBudget(outerPeerHash)) {
|
|
918
980
|
return false;
|
|
919
981
|
}
|
|
@@ -933,7 +995,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
933
995
|
!this.autoTopicRootCandidates ||
|
|
934
996
|
!this.started ||
|
|
935
997
|
this.stopping ||
|
|
936
|
-
this.topicControlPlaneStopping
|
|
998
|
+
this.topicControlPlaneStopping ||
|
|
999
|
+
(expectedPeer !== undefined &&
|
|
1000
|
+
this.peers.get(outerPeerHash) !== expectedPeer)) {
|
|
937
1001
|
return false;
|
|
938
1002
|
}
|
|
939
1003
|
if (!this.retainSignedTopicRootCandidateClaim(origin, {
|
|
@@ -985,6 +1049,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
985
1049
|
}
|
|
986
1050
|
}
|
|
987
1051
|
const claims = [...this.signedTopicRootCandidateClaims.entries()]
|
|
1052
|
+
.filter(([origin, claim]) => !this.isDepartedTopicRootCandidateClaimSuppressed(origin, claim))
|
|
988
1053
|
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
989
1054
|
.map(([, claim]) => claim.bytes);
|
|
990
1055
|
await this.sendSignedTopicRootCandidateClaims(claims, signedStreams);
|
|
@@ -1483,27 +1548,68 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1483
1548
|
if (tracked) {
|
|
1484
1549
|
return { root: tracked, authoritative: true };
|
|
1485
1550
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
// above and remain authoritative. A leaf relying on a queried gateway for
|
|
1492
|
-
// shard roots must first disable auto mode with setTopicRootCandidates([]).
|
|
1493
|
-
return this.normalizePeerTopicRootState(topic, resolvedThroughPeers);
|
|
1551
|
+
if (options?.queryConnectedPeers === false) {
|
|
1552
|
+
return {
|
|
1553
|
+
root: this.topicRootControlPlane.resolveDeterministicTopicRoot(topic),
|
|
1554
|
+
authoritative: false,
|
|
1555
|
+
};
|
|
1494
1556
|
}
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1557
|
+
const deadlineController = new AbortController();
|
|
1558
|
+
const deadlineTimer = setTimeout(() => deadlineController.abort(new AbortError("topic root peer-query deadline reached")), DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS);
|
|
1559
|
+
deadlineTimer.unref?.();
|
|
1560
|
+
const querySignals = linkAbortSignals([
|
|
1561
|
+
options?.signal,
|
|
1562
|
+
deadlineController.signal,
|
|
1563
|
+
]);
|
|
1564
|
+
try {
|
|
1565
|
+
let attempt = 0;
|
|
1566
|
+
for (;;) {
|
|
1567
|
+
throwIfAborted(querySignals.signal);
|
|
1568
|
+
// Preserve the immediate deterministic fallback when no queryable stream
|
|
1569
|
+
// exists at entry. Once a peer was queryable, keep the bounded readiness
|
|
1570
|
+
// window alive: later rounds re-snapshot streams that can become writable
|
|
1571
|
+
// after an inbound-only dial.
|
|
1572
|
+
if (attempt === 0 &&
|
|
1573
|
+
this.getConnectedTopicRootTrackers().length === 0) {
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
if (attempt > 0) {
|
|
1577
|
+
await withAbort(delay(150 * Math.min(attempt, 2), {
|
|
1578
|
+
signal: querySignals.signal,
|
|
1579
|
+
}), querySignals.signal);
|
|
1580
|
+
}
|
|
1581
|
+
attempt += 1;
|
|
1582
|
+
const resolvedThroughPeers = await this.resolveTopicRootThroughPeers(topic, {
|
|
1583
|
+
signal: querySignals.signal,
|
|
1584
|
+
timeoutMs: TOPIC_ROOT_QUERY_ROUND_TIMEOUT_MS,
|
|
1585
|
+
});
|
|
1586
|
+
if (resolvedThroughPeers !== undefined) {
|
|
1587
|
+
// Unconfigured peer-query replies cannot override the locally
|
|
1588
|
+
// deterministic root for internal shards in auto mode. Roots,
|
|
1589
|
+
// resolvers, and trackers explicitly configured on this control
|
|
1590
|
+
// plane are resolved above and remain authoritative. A leaf relying
|
|
1591
|
+
// on a queried gateway for shard roots must first disable auto mode
|
|
1592
|
+
// with setTopicRootCandidates([]).
|
|
1593
|
+
return this.normalizePeerTopicRootState(topic, resolvedThroughPeers);
|
|
1504
1594
|
}
|
|
1505
1595
|
}
|
|
1506
1596
|
}
|
|
1597
|
+
catch (error) {
|
|
1598
|
+
// Candidate, lifecycle, and caller cancellation are terminal to this
|
|
1599
|
+
// invocation and must retain their exact reason. Only the private phase
|
|
1600
|
+
// deadline falls through to the same deterministic result as exhausted
|
|
1601
|
+
// peer queries.
|
|
1602
|
+
if (options?.signal?.aborted) {
|
|
1603
|
+
throw options.signal.reason ?? error;
|
|
1604
|
+
}
|
|
1605
|
+
if (!deadlineController.signal.aborted) {
|
|
1606
|
+
throw error;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
finally {
|
|
1610
|
+
clearTimeout(deadlineTimer);
|
|
1611
|
+
querySignals.clear();
|
|
1612
|
+
}
|
|
1507
1613
|
return {
|
|
1508
1614
|
root: this.topicRootControlPlane.resolveDeterministicTopicRoot(topic),
|
|
1509
1615
|
authoritative: false,
|
|
@@ -1527,8 +1633,13 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1527
1633
|
this.maybeDisableAutoTopicRootCandidatesIfExternallyConfigured();
|
|
1528
1634
|
}
|
|
1529
1635
|
const candidateGeneration = this.getTopicRootCandidateGeneration();
|
|
1530
|
-
const hasConnectedTrackers =
|
|
1531
|
-
|
|
1636
|
+
const hasConnectedTrackers = options?.queryConnectedPeers !== false &&
|
|
1637
|
+
this.getConnectedTopicRootTrackers().length > 0;
|
|
1638
|
+
// A proactive local scan must not inherit an authoritative cache entry that
|
|
1639
|
+
// originated from a connected-peer reply under the same candidate generation.
|
|
1640
|
+
const cached = options?.queryConnectedPeers === false
|
|
1641
|
+
? undefined
|
|
1642
|
+
: this.shardRootCache.get(shardTopic);
|
|
1532
1643
|
if (cached && (cached.authoritative || !hasConnectedTrackers)) {
|
|
1533
1644
|
return { root: cached.root, candidateGeneration };
|
|
1534
1645
|
}
|
|
@@ -1569,7 +1680,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1569
1680
|
requestId = this.nextTopicRootQueryId >>> 0;
|
|
1570
1681
|
} while (true);
|
|
1571
1682
|
}
|
|
1572
|
-
async sendDirectControlMessage(peer, pubsubMessage) {
|
|
1683
|
+
async sendDirectControlMessage(peer, pubsubMessage, signal) {
|
|
1573
1684
|
const embedded = await this.createMessage(this.encodePubSubMessage(pubsubMessage), {
|
|
1574
1685
|
mode: new SilentDelivery({
|
|
1575
1686
|
to: [peer.publicKey.hashcode()],
|
|
@@ -1578,7 +1689,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1578
1689
|
priority: 1,
|
|
1579
1690
|
skipRecipientValidation: true,
|
|
1580
1691
|
});
|
|
1581
|
-
await this.publishMessage(this.publicKey, embedded, [peer]);
|
|
1692
|
+
await this.publishMessage(this.publicKey, embedded, [peer], undefined, signal);
|
|
1582
1693
|
}
|
|
1583
1694
|
resolvePendingTopicRootQuery(message, responderPeerHash) {
|
|
1584
1695
|
const pending = this.pendingTopicRootQueries.get(message.requestId);
|
|
@@ -1598,6 +1709,9 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1598
1709
|
return undefined;
|
|
1599
1710
|
const expectedPeerHash = peer.publicKey.hashcode();
|
|
1600
1711
|
const requestId = this.nextTopicRootRequestIdValue();
|
|
1712
|
+
const queryController = new AbortController();
|
|
1713
|
+
const querySignals = linkAbortSignals([signal, queryController.signal]);
|
|
1714
|
+
let timedOut = false;
|
|
1601
1715
|
const responsePromise = new Promise((resolve) => {
|
|
1602
1716
|
let settled = false;
|
|
1603
1717
|
const settle = (root) => {
|
|
@@ -1609,13 +1723,15 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1609
1723
|
this.pendingTopicRootQueries.delete(requestId);
|
|
1610
1724
|
}
|
|
1611
1725
|
clearTimeout(timer);
|
|
1612
|
-
|
|
1613
|
-
signal.removeEventListener("abort", onAbort);
|
|
1614
|
-
}
|
|
1726
|
+
querySignals.signal.removeEventListener("abort", onAbort);
|
|
1615
1727
|
resolve(root);
|
|
1616
1728
|
};
|
|
1617
|
-
const onAbort =
|
|
1618
|
-
const timer = setTimeout(() =>
|
|
1729
|
+
const onAbort = () => settle(undefined);
|
|
1730
|
+
const timer = setTimeout(() => {
|
|
1731
|
+
timedOut = true;
|
|
1732
|
+
settle(undefined);
|
|
1733
|
+
queryController.abort(new AbortError("topic root query timed out"));
|
|
1734
|
+
}, Math.max(1, Math.floor(timeoutMs)));
|
|
1619
1735
|
timer.unref?.();
|
|
1620
1736
|
this.pendingTopicRootQueries.set(requestId, {
|
|
1621
1737
|
expectedPeerHash,
|
|
@@ -1623,26 +1739,39 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1623
1739
|
resolve: settle,
|
|
1624
1740
|
timer,
|
|
1625
1741
|
});
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
onAbort();
|
|
1630
|
-
}
|
|
1742
|
+
querySignals.signal.addEventListener("abort", onAbort, { once: true });
|
|
1743
|
+
if (querySignals.signal.aborted)
|
|
1744
|
+
onAbort();
|
|
1631
1745
|
});
|
|
1632
1746
|
try {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1747
|
+
try {
|
|
1748
|
+
const sending = withAbort(this.sendDirectControlMessage(peer, new TopicRootQuery({ requestId, topic }), querySignals.signal), querySignals.signal);
|
|
1749
|
+
// A write can finish after the request is already on the wire (or stall
|
|
1750
|
+
// while opening the reverse stream). Let either a response or the same
|
|
1751
|
+
// query timeout release this await instead of extending the timeout around
|
|
1752
|
+
// the outbound send.
|
|
1753
|
+
await Promise.race([
|
|
1754
|
+
sending,
|
|
1755
|
+
responsePromise.then(() => undefined),
|
|
1756
|
+
]);
|
|
1641
1757
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1758
|
+
catch (error) {
|
|
1759
|
+
const pending = this.pendingTopicRootQueries.get(requestId);
|
|
1760
|
+
if (pending && !timedOut) {
|
|
1761
|
+
this.pendingTopicRootQueries.delete(requestId);
|
|
1762
|
+
clearTimeout(pending.timer);
|
|
1763
|
+
pending.resolve(undefined);
|
|
1764
|
+
}
|
|
1765
|
+
if (signal?.aborted) {
|
|
1766
|
+
throw signal.reason ?? error;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return await withAbort(responsePromise, signal);
|
|
1770
|
+
}
|
|
1771
|
+
finally {
|
|
1772
|
+
queryController.abort(new AbortError("topic root query settled"));
|
|
1773
|
+
querySignals.clear();
|
|
1644
1774
|
}
|
|
1645
|
-
return withAbort(responsePromise, signal);
|
|
1646
1775
|
}
|
|
1647
1776
|
async confirmDirectShardRoot(shardTopic, root, candidateGeneration, lifecycleRevision, signal) {
|
|
1648
1777
|
this.assertTopicControlPlaneActive(lifecycleRevision);
|
|
@@ -1712,18 +1841,69 @@ export class TopicControlPlane extends DirectStream {
|
|
|
1712
1841
|
if (peers.length === 0) {
|
|
1713
1842
|
return undefined;
|
|
1714
1843
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1844
|
+
const roundController = new AbortController();
|
|
1845
|
+
const roundTimeoutMs = Math.max(1, Math.floor(options?.timeoutMs ?? DEFAULT_TOPIC_ROOT_QUERY_TIMEOUT_MS));
|
|
1846
|
+
let roundTimedOut = false;
|
|
1847
|
+
const roundTimer = setTimeout(() => {
|
|
1848
|
+
roundTimedOut = true;
|
|
1849
|
+
roundController.abort(new AbortError("topic root query round timed out"));
|
|
1850
|
+
}, roundTimeoutMs);
|
|
1851
|
+
roundTimer.unref?.();
|
|
1852
|
+
const roundSignals = linkAbortSignals([
|
|
1853
|
+
options?.signal,
|
|
1854
|
+
roundController.signal,
|
|
1855
|
+
]);
|
|
1856
|
+
const queries = peers.map((peer) => this.queryTopicRootFromPeer(peer, topic, roundTimeoutMs, roundSignals.signal));
|
|
1857
|
+
const unresolved = Symbol("unresolved topic root query");
|
|
1858
|
+
const results = peers.map(() => unresolved);
|
|
1859
|
+
// All requests start together, but the stable peer ordering remains the
|
|
1860
|
+
// conflict-resolution policy: a later answer is usable only after every
|
|
1861
|
+
// earlier peer has answered undefined or reached its round timeout.
|
|
1862
|
+
const prioritizedResult = new Promise((resolve, reject) => {
|
|
1863
|
+
let settled = false;
|
|
1864
|
+
const resolveInPeerOrder = () => {
|
|
1865
|
+
for (const result of results) {
|
|
1866
|
+
if (result === unresolved)
|
|
1867
|
+
return;
|
|
1868
|
+
if (result !== undefined) {
|
|
1869
|
+
settled = true;
|
|
1870
|
+
resolve(result);
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1720
1873
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1874
|
+
settled = true;
|
|
1875
|
+
resolve(undefined);
|
|
1876
|
+
};
|
|
1877
|
+
queries.forEach((query, index) => {
|
|
1878
|
+
query.then((root) => {
|
|
1879
|
+
if (settled)
|
|
1880
|
+
return;
|
|
1881
|
+
results[index] = root;
|
|
1882
|
+
resolveInPeerOrder();
|
|
1883
|
+
}, (error) => {
|
|
1884
|
+
if (settled)
|
|
1885
|
+
return;
|
|
1886
|
+
if (roundTimedOut && !options?.signal?.aborted) {
|
|
1887
|
+
results[index] = undefined;
|
|
1888
|
+
resolveInPeerOrder();
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
settled = true;
|
|
1892
|
+
reject(error);
|
|
1893
|
+
});
|
|
1894
|
+
});
|
|
1895
|
+
});
|
|
1896
|
+
try {
|
|
1897
|
+
return await prioritizedResult;
|
|
1898
|
+
}
|
|
1899
|
+
finally {
|
|
1900
|
+
clearTimeout(roundTimer);
|
|
1901
|
+
// Cancel response timers and in-flight sends for every losing query before
|
|
1902
|
+
// the winning root (or an abort) can leave this round.
|
|
1903
|
+
roundController.abort(new AbortError("topic root query round settled"));
|
|
1904
|
+
await Promise.allSettled(queries);
|
|
1905
|
+
roundSignals.clear();
|
|
1725
1906
|
}
|
|
1726
|
-
return undefined;
|
|
1727
1907
|
}
|
|
1728
1908
|
async ensureFanoutChannel(shardTopic, options) {
|
|
1729
1909
|
const topic = shardTopic.toString();
|
|
@@ -2107,26 +2287,71 @@ export class TopicControlPlane extends DirectStream {
|
|
|
2107
2287
|
if (!this.started)
|
|
2108
2288
|
throw new NotStartedError();
|
|
2109
2289
|
const lifecycleSignal = this.topicRootResolutionAbortController.signal;
|
|
2110
|
-
return this.withTopicRootCandidateResolution(async ({
|
|
2290
|
+
return this.withTopicRootCandidateResolution(async ({ signal }) => {
|
|
2111
2291
|
const joins = [];
|
|
2292
|
+
const scanController = new AbortController();
|
|
2293
|
+
const scanTimer = setTimeout(() => {
|
|
2294
|
+
scanController.abort(new AbortError(`topic root host scan timed out after ${TOPIC_ROOT_HOST_SCAN_TIMEOUT_MS}ms`));
|
|
2295
|
+
}, TOPIC_ROOT_HOST_SCAN_TIMEOUT_MS);
|
|
2296
|
+
scanTimer.unref?.();
|
|
2297
|
+
const scanSignals = linkAbortSignals([signal, scanController.signal]);
|
|
2298
|
+
let nextShard = 0;
|
|
2299
|
+
let scanFailure;
|
|
2300
|
+
const scanWorker = async () => {
|
|
2301
|
+
try {
|
|
2302
|
+
for (;;) {
|
|
2303
|
+
if (scanFailure)
|
|
2304
|
+
return;
|
|
2305
|
+
throwIfAborted(scanSignals.signal);
|
|
2306
|
+
const shard = nextShard++;
|
|
2307
|
+
if (shard >= this.shardCount)
|
|
2308
|
+
return;
|
|
2309
|
+
const shardTopic = `${this.shardTopicPrefix}${shard}`;
|
|
2310
|
+
const resolved = await this.resolveShardRootState(shardTopic, {
|
|
2311
|
+
signal: scanSignals.signal,
|
|
2312
|
+
// Proactive hosting follows the generation-bound local control
|
|
2313
|
+
// plane. Connected-peer queries remain on the lazy open path;
|
|
2314
|
+
// asking every peer for every shard multiplies one stale-peer
|
|
2315
|
+
// timeout by the complete shard count.
|
|
2316
|
+
queryConnectedPeers: false,
|
|
2317
|
+
});
|
|
2318
|
+
throwIfAborted(scanSignals.signal);
|
|
2319
|
+
if (scanFailure || resolved.root !== this.publicKeyHash)
|
|
2320
|
+
continue;
|
|
2321
|
+
const joining = this.ensureFanoutChannel(shardTopic, {
|
|
2322
|
+
pin: true,
|
|
2323
|
+
root: resolved.root,
|
|
2324
|
+
rootCandidateGeneration: resolved.candidateGeneration,
|
|
2325
|
+
// The shared deadline bounds root discovery only. Preserve the
|
|
2326
|
+
// existing contract that joins already started are drained.
|
|
2327
|
+
signal,
|
|
2328
|
+
});
|
|
2329
|
+
void joining.catch(() => { });
|
|
2330
|
+
joins.push(joining);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
catch (error) {
|
|
2334
|
+
if (!scanFailure) {
|
|
2335
|
+
scanFailure = { error };
|
|
2336
|
+
// Do not make a sibling resolver consume the rest of the shared
|
|
2337
|
+
// deadline after this scan already has a terminal failure. Joins use
|
|
2338
|
+
// the outer signal and are intentionally drained below.
|
|
2339
|
+
scanController.abort(error);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2112
2343
|
try {
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
const joining = this.ensureFanoutChannel(shardTopic, {
|
|
2122
|
-
pin: true,
|
|
2123
|
-
root: resolved.root,
|
|
2124
|
-
rootCandidateGeneration: resolved.candidateGeneration,
|
|
2125
|
-
signal,
|
|
2126
|
-
});
|
|
2127
|
-
void joining.catch(() => { });
|
|
2128
|
-
joins.push(joining);
|
|
2344
|
+
try {
|
|
2345
|
+
await Promise.all(Array.from({
|
|
2346
|
+
length: Math.min(TOPIC_ROOT_HOST_SCAN_CONCURRENCY, this.shardCount),
|
|
2347
|
+
}, () => scanWorker()));
|
|
2348
|
+
}
|
|
2349
|
+
finally {
|
|
2350
|
+
clearTimeout(scanTimer);
|
|
2351
|
+
scanSignals.clear();
|
|
2129
2352
|
}
|
|
2353
|
+
if (scanFailure)
|
|
2354
|
+
throw scanFailure.error;
|
|
2130
2355
|
await Promise.all(joins);
|
|
2131
2356
|
}
|
|
2132
2357
|
catch (error) {
|
|
@@ -2856,7 +3081,7 @@ export class TopicControlPlane extends DirectStream {
|
|
|
2856
3081
|
const imported = [];
|
|
2857
3082
|
const outerPeerHash = stream.publicKey.hashcode();
|
|
2858
3083
|
for (const claim of pubsubMessage.claims) {
|
|
2859
|
-
if (await this.importSignedTopicRootCandidateClaim(claim, outerPeerHash)) {
|
|
3084
|
+
if (await this.importSignedTopicRootCandidateClaim(claim, outerPeerHash, stream)) {
|
|
2860
3085
|
imported.push(claim);
|
|
2861
3086
|
}
|
|
2862
3087
|
}
|