@optimystic/db-p2p 0.22.0 → 0.24.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.
Files changed (177) hide show
  1. package/dist/src/cluster/client.d.ts +10 -0
  2. package/dist/src/cluster/client.d.ts.map +1 -1
  3. package/dist/src/cluster/client.js +30 -1
  4. package/dist/src/cluster/client.js.map +1 -1
  5. package/dist/src/cluster/cluster-repo.d.ts +39 -14
  6. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-repo.js +226 -119
  8. package/dist/src/cluster/cluster-repo.js.map +1 -1
  9. package/dist/src/cluster/service.d.ts +32 -1
  10. package/dist/src/cluster/service.d.ts.map +1 -1
  11. package/dist/src/cluster/service.js +43 -2
  12. package/dist/src/cluster/service.js.map +1 -1
  13. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  14. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  15. package/dist/src/cohort-topic/stream-util.js +56 -10
  16. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  17. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  18. package/dist/src/dispute/dispute-service.js +9 -3
  19. package/dist/src/dispute/dispute-service.js.map +1 -1
  20. package/dist/src/index.d.ts +3 -0
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +3 -0
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/libp2p-key-network.d.ts +88 -2
  25. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  26. package/dist/src/libp2p-key-network.js +134 -28
  27. package/dist/src/libp2p-key-network.js.map +1 -1
  28. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  29. package/dist/src/libp2p-node-base.js +25 -1
  30. package/dist/src/libp2p-node-base.js.map +1 -1
  31. package/dist/src/logger.d.ts +17 -1
  32. package/dist/src/logger.d.ts.map +1 -1
  33. package/dist/src/logger.js +19 -2
  34. package/dist/src/logger.js.map +1 -1
  35. package/dist/src/owned-block-seed.d.ts +6 -3
  36. package/dist/src/owned-block-seed.d.ts.map +1 -1
  37. package/dist/src/owned-block-seed.js +16 -3
  38. package/dist/src/owned-block-seed.js.map +1 -1
  39. package/dist/src/peer-address-book.d.ts +72 -0
  40. package/dist/src/peer-address-book.d.ts.map +1 -0
  41. package/dist/src/peer-address-book.js +123 -0
  42. package/dist/src/peer-address-book.js.map +1 -0
  43. package/dist/src/repo/client.d.ts.map +1 -1
  44. package/dist/src/repo/client.js +11 -2
  45. package/dist/src/repo/client.js.map +1 -1
  46. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  47. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  48. package/dist/src/repo/cluster-coordinator.js +95 -3
  49. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  50. package/dist/src/repo/coordinator-repo.d.ts +62 -9
  51. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  52. package/dist/src/repo/coordinator-repo.js +242 -73
  53. package/dist/src/repo/coordinator-repo.js.map +1 -1
  54. package/dist/src/rn.d.ts +3 -0
  55. package/dist/src/rn.d.ts.map +1 -1
  56. package/dist/src/rn.js +3 -0
  57. package/dist/src/rn.js.map +1 -1
  58. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  59. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  60. package/dist/src/storage/cached-raw-storage.js +152 -0
  61. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  62. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  63. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  64. package/dist/src/storage/cached-store-driver.js +775 -0
  65. package/dist/src/storage/cached-store-driver.js.map +1 -0
  66. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  67. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  68. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  69. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  70. package/dist/src/storage/shared-cache-pool.js +354 -0
  71. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  72. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  73. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  74. package/dist/src/testing/raw-storage-conformance.js +35 -2
  75. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  76. package/package.json +3 -3
  77. package/readme.md +668 -668
  78. package/src/cluster/block-transfer.ts +424 -424
  79. package/src/cluster/client.ts +119 -88
  80. package/src/cluster/cluster-error.ts +64 -64
  81. package/src/cluster/cluster-policy.ts +203 -203
  82. package/src/cluster/cluster-repo.ts +242 -122
  83. package/src/cluster/cluster-size-coupling.ts +45 -45
  84. package/src/cluster/commit-cert.ts +139 -139
  85. package/src/cluster/i-transaction-state-store.ts +43 -43
  86. package/src/cluster/memory-transaction-state-store.ts +56 -56
  87. package/src/cluster/peer-key-binding.ts +37 -37
  88. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  89. package/src/cluster/quorum-restore.ts +223 -223
  90. package/src/cluster/reconcile-block.ts +203 -203
  91. package/src/cluster/service.ts +293 -241
  92. package/src/cluster/supermajority-coupling.ts +37 -37
  93. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  94. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  95. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  96. package/src/cohort-topic/change-bridge.ts +109 -109
  97. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  98. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  99. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  100. package/src/cohort-topic/host.ts +2901 -2901
  101. package/src/cohort-topic/index.ts +13 -13
  102. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  103. package/src/cohort-topic/membership-source.ts +68 -68
  104. package/src/cohort-topic/peer-codec.ts +31 -31
  105. package/src/cohort-topic/peer-sig.ts +86 -86
  106. package/src/cohort-topic/protocols.ts +71 -71
  107. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  108. package/src/cohort-topic/size-estimator.ts +16 -16
  109. package/src/cohort-topic/stream-util.ts +135 -87
  110. package/src/cohort-topic/threshold-crypto.ts +239 -239
  111. package/src/cohort-topic/topic-router.ts +77 -77
  112. package/src/dispute/arbitrator-selection.ts +138 -138
  113. package/src/dispute/cascade.ts +524 -524
  114. package/src/dispute/dispute-service.ts +11 -5
  115. package/src/dispute/invalidation.ts +625 -625
  116. package/src/inbound-authorization.ts +190 -190
  117. package/src/index.ts +52 -49
  118. package/src/libp2p-key-network.ts +1120 -990
  119. package/src/libp2p-node-base.ts +1675 -1651
  120. package/src/libp2p-node-rn.ts +30 -30
  121. package/src/libp2p-node.ts +36 -36
  122. package/src/logger.ts +19 -2
  123. package/src/matchmaking/aggregate-counts.ts +104 -104
  124. package/src/matchmaking/index.ts +20 -20
  125. package/src/matchmaking/module.ts +363 -363
  126. package/src/matchmaking/protocols.ts +51 -51
  127. package/src/matchmaking/provider-manager.ts +95 -95
  128. package/src/matchmaking/query-handler.ts +88 -88
  129. package/src/matchmaking/query-transport.ts +492 -492
  130. package/src/matchmaking/seeker-manager.ts +64 -64
  131. package/src/matchmaking/seeker-walk-client.ts +293 -293
  132. package/src/matchmaking/traffic-validation.ts +195 -195
  133. package/src/optimystic-node.ts +36 -36
  134. package/src/owned-block-seed.ts +53 -40
  135. package/src/peer-address-book.ts +149 -0
  136. package/src/protocol-limits.ts +33 -33
  137. package/src/reactivity/forwarder-host.ts +438 -438
  138. package/src/reactivity/index.ts +19 -19
  139. package/src/reactivity/notify-transport.ts +144 -144
  140. package/src/reactivity/origination-manager.ts +192 -192
  141. package/src/reactivity/protocols.ts +61 -61
  142. package/src/reactivity/push-state-gossip.ts +291 -291
  143. package/src/reactivity/recover-transport.ts +408 -408
  144. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  145. package/src/reactivity/subscriber-registry.ts +96 -96
  146. package/src/reactivity/subscription-manager.ts +450 -450
  147. package/src/reactivity/topic-bytes.ts +37 -37
  148. package/src/repo/client.ts +12 -2
  149. package/src/repo/cluster-coordinator.ts +99 -3
  150. package/src/repo/coordinator-repo.ts +281 -74
  151. package/src/repo/types.ts +7 -7
  152. package/src/rn.ts +39 -36
  153. package/src/rpc-deadline.ts +45 -45
  154. package/src/storage/arachnode-partition.ts +74 -74
  155. package/src/storage/cached-raw-storage.ts +180 -0
  156. package/src/storage/cached-store-driver.ts +859 -0
  157. package/src/storage/i-kv-store.ts +8 -8
  158. package/src/storage/i-raw-storage.ts +12 -5
  159. package/src/storage/kv-raw-storage.ts +135 -135
  160. package/src/storage/memory-kv-store.ts +28 -28
  161. package/src/storage/memory-storage.ts +25 -25
  162. package/src/storage/memory-store-driver.ts +157 -157
  163. package/src/storage/raw-store-codec.ts +42 -42
  164. package/src/storage/raw-store-driver.ts +80 -80
  165. package/src/storage/ring-selector.ts +317 -317
  166. package/src/storage/ring-shift-coordinator.ts +271 -271
  167. package/src/storage/shared-cache-pool.ts +452 -0
  168. package/src/storage/storage-repo.ts +1014 -1014
  169. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  170. package/src/testing/index.ts +8 -8
  171. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  172. package/src/testing/raw-storage-conformance.ts +453 -417
  173. package/src/testing/reactivity-mesh-harness.ts +922 -922
  174. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  175. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  176. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  177. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -1,51 +1,51 @@
1
- /**
2
- * Matchmaking libp2p protocol IDs (`docs/matchmaking.md` §Seeker query).
3
- *
4
- * Matchmaking is an application layered **above** the cohort-topic substrate, so it owns its own protocol
5
- * family rather than riding the cohort-topic protocols (which carry only substrate concerns — register,
6
- * gossip, promote, membership, sign). Exactly one protocol lives here today:
7
- *
8
- * - `query` — `QueryV1` → `QueryReplyV1`, the seeker's request-reply RPC against a cohort for the
9
- * providers/seekers it locally holds (the serve side is `query-transport.ts`; the seeker
10
- * walk client is the follow-on `matchmaking-query-rpc-seeker-walk`).
11
- *
12
- * The default (network-agnostic) ID omits the network segment; {@link makeMatchmakingProtocols} mirrors
13
- * FRET's `makeProtocols(networkName)` so a named network namespaces its matchmaking protocols the same way
14
- * FRET namespaces its routing protocols (and the cohort-topic / reactivity families namespace via their own
15
- * `make*Protocols`). Production wires the network-agnostic default, matching the cohort-topic + reactivity
16
- * families' production default.
17
- */
18
-
19
- /** Base path for the matchmaking protocol family. */
20
- export const MATCHMAKING_BASE = "/optimystic/matchmaking/1.0.0" as const;
21
-
22
- /** `QueryV1` / `QueryReplyV1` — a seeker's query for a cohort's locally-held provider/seeker registrations. */
23
- export const PROTOCOL_MATCHMAKING_QUERY = `${MATCHMAKING_BASE}/query` as const;
24
-
25
- /** The matchmaking protocol IDs in registration order. */
26
- export interface MatchmakingProtocols {
27
- readonly query: string;
28
- }
29
-
30
- /** Default (network-agnostic) protocol IDs, matching `docs/matchmaking.md`. */
31
- export const DEFAULT_MATCHMAKING_PROTOCOLS: MatchmakingProtocols = {
32
- query: PROTOCOL_MATCHMAKING_QUERY,
33
- };
34
-
35
- /**
36
- * Namespaced matchmaking protocol IDs for `networkName` (mirrors FRET's `makeProtocols`, which inserts the
37
- * network segment even for `"default"` → `/optimystic/default/...`). Note this does NOT equal
38
- * {@link DEFAULT_MATCHMAKING_PROTOCOLS}: the canonical, network-agnostic IDs omit the segment entirely
39
- * (`/optimystic/matchmaking/1.0.0/...`); use those unless you need per-network namespacing.
40
- */
41
- export function makeMatchmakingProtocols(networkName = "default"): MatchmakingProtocols {
42
- const base = `/optimystic/${networkName}/matchmaking/1.0.0`;
43
- return {
44
- query: `${base}/query`,
45
- };
46
- }
47
-
48
- /** All matchmaking protocol IDs as an array (for `node.handle` / `unhandle` over the set). */
49
- export function matchmakingProtocolList(p: MatchmakingProtocols): string[] {
50
- return [p.query];
51
- }
1
+ /**
2
+ * Matchmaking libp2p protocol IDs (`docs/matchmaking.md` §Seeker query).
3
+ *
4
+ * Matchmaking is an application layered **above** the cohort-topic substrate, so it owns its own protocol
5
+ * family rather than riding the cohort-topic protocols (which carry only substrate concerns — register,
6
+ * gossip, promote, membership, sign). Exactly one protocol lives here today:
7
+ *
8
+ * - `query` — `QueryV1` → `QueryReplyV1`, the seeker's request-reply RPC against a cohort for the
9
+ * providers/seekers it locally holds (the serve side is `query-transport.ts`; the seeker
10
+ * walk client is the follow-on `matchmaking-query-rpc-seeker-walk`).
11
+ *
12
+ * The default (network-agnostic) ID omits the network segment; {@link makeMatchmakingProtocols} mirrors
13
+ * FRET's `makeProtocols(networkName)` so a named network namespaces its matchmaking protocols the same way
14
+ * FRET namespaces its routing protocols (and the cohort-topic / reactivity families namespace via their own
15
+ * `make*Protocols`). Production wires the network-agnostic default, matching the cohort-topic + reactivity
16
+ * families' production default.
17
+ */
18
+
19
+ /** Base path for the matchmaking protocol family. */
20
+ export const MATCHMAKING_BASE = "/optimystic/matchmaking/1.0.0" as const;
21
+
22
+ /** `QueryV1` / `QueryReplyV1` — a seeker's query for a cohort's locally-held provider/seeker registrations. */
23
+ export const PROTOCOL_MATCHMAKING_QUERY = `${MATCHMAKING_BASE}/query` as const;
24
+
25
+ /** The matchmaking protocol IDs in registration order. */
26
+ export interface MatchmakingProtocols {
27
+ readonly query: string;
28
+ }
29
+
30
+ /** Default (network-agnostic) protocol IDs, matching `docs/matchmaking.md`. */
31
+ export const DEFAULT_MATCHMAKING_PROTOCOLS: MatchmakingProtocols = {
32
+ query: PROTOCOL_MATCHMAKING_QUERY,
33
+ };
34
+
35
+ /**
36
+ * Namespaced matchmaking protocol IDs for `networkName` (mirrors FRET's `makeProtocols`, which inserts the
37
+ * network segment even for `"default"` → `/optimystic/default/...`). Note this does NOT equal
38
+ * {@link DEFAULT_MATCHMAKING_PROTOCOLS}: the canonical, network-agnostic IDs omit the segment entirely
39
+ * (`/optimystic/matchmaking/1.0.0/...`); use those unless you need per-network namespacing.
40
+ */
41
+ export function makeMatchmakingProtocols(networkName = "default"): MatchmakingProtocols {
42
+ const base = `/optimystic/${networkName}/matchmaking/1.0.0`;
43
+ return {
44
+ query: `${base}/query`,
45
+ };
46
+ }
47
+
48
+ /** All matchmaking protocol IDs as an array (for `node.handle` / `unhandle` over the set). */
49
+ export function matchmakingProtocolList(p: MatchmakingProtocols): string[] {
50
+ return [p.query];
51
+ }
@@ -1,95 +1,95 @@
1
- /**
2
- * Matchmaking — provider manager (db-p2p, wires to the cohort-topic substrate).
3
- *
4
- * Drives a db-core {@link MatchmakingProvider}'s lifecycle against the participant-facing
5
- * {@link CohortTopicService}: register at cohort-topic tier **T2 (functional)** with a profile-based
6
- * TTL, renew to keep the record alive, and withdraw by ceasing renewal (`docs/matchmaking.md`
7
- * §Provider registration).
8
- *
9
- * Self-throttling maps onto the substrate as follows (an honest gap surfaced for the reviewer): the
10
- * cohort-topic `RenewV1` carries **no** `appPayload` and **no** `ttl` field — a renewal is a pure
11
- * keep-alive touch. So a capacity change ("signal full", `capacityBudget = 0`) is realized by
12
- * **re-registering** with the new signed payload (which updates the cohort record's `appState`), not
13
- * by a renewal. Likewise immediate withdrawal (`RenewV1` TTL = 0 in the matchmaking doc) has no wire
14
- * realization yet — {@link withdraw} stops renewing and lets the record age out by TTL. Per the
15
- * §Provider self-throttling GROUNDING resolution, withdrawal is an **optimization, not correctness**,
16
- * so passive TTL expiry is acceptable; an immediate-tombstone renew is a documented cohort-topic
17
- * follow-on.
18
- */
19
-
20
- import { Tier, providerTtlForProfile, type CohortTopicService, type MatchmakingProvider, type NodeProfile, type RegistrationHandle } from "@optimystic/db-core";
21
-
22
- /** Construction inputs for a {@link MatchmakingProviderManager}. */
23
- export interface MatchmakingProviderManagerOptions {
24
- /** Participant-facing cohort-topic substrate API. */
25
- readonly service: CohortTopicService;
26
- /** The provider state/decision object this manager drives. */
27
- readonly provider: MatchmakingProvider;
28
- /** Provider TTL (ms). Default: derived from {@link profile} via `providerTtlForProfile`. */
29
- readonly ttlMs?: number;
30
- /** Node profile used to derive the TTL when {@link ttlMs} is absent (Core 90 s / Edge 60 s). */
31
- readonly profile?: NodeProfile;
32
- }
33
-
34
- /** Wires one matchmaking provider to the cohort-topic substrate at tier T2. */
35
- export class MatchmakingProviderManager {
36
- private readonly service: CohortTopicService;
37
- private readonly provider: MatchmakingProvider;
38
- private readonly ttlMs: number;
39
- private handle?: RegistrationHandle;
40
-
41
- constructor(options: MatchmakingProviderManagerOptions) {
42
- this.service = options.service;
43
- this.provider = options.provider;
44
- this.ttlMs = options.ttlMs ?? (options.profile !== undefined ? providerTtlForProfile(options.profile) : undefined) ?? DEFAULT_PROVIDER_TTL_MS;
45
- }
46
-
47
- /** The live registration handle, or `undefined` before the first {@link register}. */
48
- get registration(): RegistrationHandle | undefined {
49
- return this.handle;
50
- }
51
-
52
- /** Register (or re-register) the provider at tier T2 with the current signed payload. */
53
- async register(): Promise<RegistrationHandle> {
54
- const appPayload = await this.provider.appPayloadBytes();
55
- this.handle = await this.service.register({
56
- topicId: this.provider.topicId,
57
- tier: Tier.T2,
58
- appPayload,
59
- ttl: this.ttlMs,
60
- });
61
- return this.handle;
62
- }
63
-
64
- /** Run one renewal cycle (keep-alive touch). No-op before the first {@link register}. */
65
- async renew(): Promise<void> {
66
- if (this.handle === undefined) {
67
- return;
68
- }
69
- await this.service.renew(this.handle);
70
- }
71
-
72
- /** Set the live capacity budget and push it by re-registering (`RenewV1` cannot carry payload). */
73
- async setCapacity(budget: number): Promise<RegistrationHandle> {
74
- this.provider.setCapacity(budget);
75
- return this.register();
76
- }
77
-
78
- /** Signal "available but at capacity" (`capacityBudget = 0`) and push it by re-registering. */
79
- async signalFull(): Promise<RegistrationHandle> {
80
- this.provider.signalFull();
81
- return this.register();
82
- }
83
-
84
- /** Withdraw: stop renewing and send a best-effort signed tombstone so the cohort frees the record
85
- * immediately; TTL expiry is the fallback (the immediate tombstone is an optimization, not correctness). */
86
- async withdraw(): Promise<void> {
87
- this.provider.markWithdrawn();
88
- if (this.handle !== undefined) {
89
- await this.service.withdraw(this.handle);
90
- }
91
- }
92
- }
93
-
94
- /** Fallback provider TTL when neither an explicit `ttlMs` nor a `profile` is supplied (Core default). */
95
- const DEFAULT_PROVIDER_TTL_MS = 90_000;
1
+ /**
2
+ * Matchmaking — provider manager (db-p2p, wires to the cohort-topic substrate).
3
+ *
4
+ * Drives a db-core {@link MatchmakingProvider}'s lifecycle against the participant-facing
5
+ * {@link CohortTopicService}: register at cohort-topic tier **T2 (functional)** with a profile-based
6
+ * TTL, renew to keep the record alive, and withdraw by ceasing renewal (`docs/matchmaking.md`
7
+ * §Provider registration).
8
+ *
9
+ * Self-throttling maps onto the substrate as follows (an honest gap surfaced for the reviewer): the
10
+ * cohort-topic `RenewV1` carries **no** `appPayload` and **no** `ttl` field — a renewal is a pure
11
+ * keep-alive touch. So a capacity change ("signal full", `capacityBudget = 0`) is realized by
12
+ * **re-registering** with the new signed payload (which updates the cohort record's `appState`), not
13
+ * by a renewal. Likewise immediate withdrawal (`RenewV1` TTL = 0 in the matchmaking doc) has no wire
14
+ * realization yet — {@link withdraw} stops renewing and lets the record age out by TTL. Per the
15
+ * §Provider self-throttling GROUNDING resolution, withdrawal is an **optimization, not correctness**,
16
+ * so passive TTL expiry is acceptable; an immediate-tombstone renew is a documented cohort-topic
17
+ * follow-on.
18
+ */
19
+
20
+ import { Tier, providerTtlForProfile, type CohortTopicService, type MatchmakingProvider, type NodeProfile, type RegistrationHandle } from "@optimystic/db-core";
21
+
22
+ /** Construction inputs for a {@link MatchmakingProviderManager}. */
23
+ export interface MatchmakingProviderManagerOptions {
24
+ /** Participant-facing cohort-topic substrate API. */
25
+ readonly service: CohortTopicService;
26
+ /** The provider state/decision object this manager drives. */
27
+ readonly provider: MatchmakingProvider;
28
+ /** Provider TTL (ms). Default: derived from {@link profile} via `providerTtlForProfile`. */
29
+ readonly ttlMs?: number;
30
+ /** Node profile used to derive the TTL when {@link ttlMs} is absent (Core 90 s / Edge 60 s). */
31
+ readonly profile?: NodeProfile;
32
+ }
33
+
34
+ /** Wires one matchmaking provider to the cohort-topic substrate at tier T2. */
35
+ export class MatchmakingProviderManager {
36
+ private readonly service: CohortTopicService;
37
+ private readonly provider: MatchmakingProvider;
38
+ private readonly ttlMs: number;
39
+ private handle?: RegistrationHandle;
40
+
41
+ constructor(options: MatchmakingProviderManagerOptions) {
42
+ this.service = options.service;
43
+ this.provider = options.provider;
44
+ this.ttlMs = options.ttlMs ?? (options.profile !== undefined ? providerTtlForProfile(options.profile) : undefined) ?? DEFAULT_PROVIDER_TTL_MS;
45
+ }
46
+
47
+ /** The live registration handle, or `undefined` before the first {@link register}. */
48
+ get registration(): RegistrationHandle | undefined {
49
+ return this.handle;
50
+ }
51
+
52
+ /** Register (or re-register) the provider at tier T2 with the current signed payload. */
53
+ async register(): Promise<RegistrationHandle> {
54
+ const appPayload = await this.provider.appPayloadBytes();
55
+ this.handle = await this.service.register({
56
+ topicId: this.provider.topicId,
57
+ tier: Tier.T2,
58
+ appPayload,
59
+ ttl: this.ttlMs,
60
+ });
61
+ return this.handle;
62
+ }
63
+
64
+ /** Run one renewal cycle (keep-alive touch). No-op before the first {@link register}. */
65
+ async renew(): Promise<void> {
66
+ if (this.handle === undefined) {
67
+ return;
68
+ }
69
+ await this.service.renew(this.handle);
70
+ }
71
+
72
+ /** Set the live capacity budget and push it by re-registering (`RenewV1` cannot carry payload). */
73
+ async setCapacity(budget: number): Promise<RegistrationHandle> {
74
+ this.provider.setCapacity(budget);
75
+ return this.register();
76
+ }
77
+
78
+ /** Signal "available but at capacity" (`capacityBudget = 0`) and push it by re-registering. */
79
+ async signalFull(): Promise<RegistrationHandle> {
80
+ this.provider.signalFull();
81
+ return this.register();
82
+ }
83
+
84
+ /** Withdraw: stop renewing and send a best-effort signed tombstone so the cohort frees the record
85
+ * immediately; TTL expiry is the fallback (the immediate tombstone is an optimization, not correctness). */
86
+ async withdraw(): Promise<void> {
87
+ this.provider.markWithdrawn();
88
+ if (this.handle !== undefined) {
89
+ await this.service.withdraw(this.handle);
90
+ }
91
+ }
92
+ }
93
+
94
+ /** Fallback provider TTL when neither an explicit `ttlMs` nor a `profile` is supplied (Core default). */
95
+ const DEFAULT_PROVIDER_TTL_MS = 90_000;
@@ -1,88 +1,88 @@
1
- /**
2
- * Matchmaking — cohort-side `QueryV1` handler (db-p2p, wires the substrate store to the query reply).
3
- *
4
- * `docs/matchmaking.md` §Seeker query / §Capability filter. When a seeker dials a cohort with a
5
- * {@link QueryV1}, the cohort returns its **locally-known direct registrations** for the topic, with the
6
- * capability filter applied. This handler is the thin db-p2p binding: it reads the cohort's local
7
- * {@link RegistrationRecord}s, decodes each one's `appState` into a provider/seeker payload, and hands
8
- * them to the pure db-core {@link evaluateQuery} (filter + truncation + entry building). It then
9
- * attaches the substrate's `topicTraffic` snapshot, the `cohortEpoch`, and the cohort **primary's**
10
- * single-member reply signature (NOT a threshold signature — the reply is advisory; the seeker
11
- * re-validates each entry's `registrationSig` via `verifyProviderEntry`).
12
- *
13
- * The handler is transport-light on purpose: the records, traffic, epoch, and signer are all injected,
14
- * so it unit-tests without a live libp2p stack (mock-tier e2e is a documented follow-on). The FRET host
15
- * supplies `records = store.listByTopic(topicId)`, `topicTraffic` from the cohort `TrafficCounters`,
16
- * the current `cohortEpoch`, and a `sign` bound to this node's peer key.
17
- */
18
-
19
- import {
20
- bytesToB64url,
21
- decodeMatchAppPayload,
22
- evaluateQuery,
23
- queryReplySigningPayload,
24
- type LocalProviderRegistration,
25
- type LocalSeekerRegistration,
26
- type QueryReplyV1,
27
- type QueryV1,
28
- type RegistrationRecord,
29
- type TopicTrafficV1,
30
- } from "@optimystic/db-core";
31
- import { bytesToPeerIdString } from "../cohort-topic/peer-codec.js";
32
-
33
- /** Everything the {@link handleMatchmakingQuery} needs from the cohort substrate, all injected. */
34
- export interface CohortQueryContext {
35
- /** The cohort's local registration records for the queried topic (e.g. `store.listByTopic(topicId)`). */
36
- readonly records: readonly RegistrationRecord[];
37
- /** The substrate's current traffic barometer for the topic (from the cohort `TrafficCounters`). */
38
- readonly topicTraffic: TopicTrafficV1;
39
- /** The current cohort epoch (32 bytes). */
40
- readonly cohortEpoch: Uint8Array;
41
- /** Sign the canonical reply image with the cohort primary's peer key; resolves the base64url signature. */
42
- readonly sign: (payload: Uint8Array) => Promise<string>;
43
- /** Optional logger for records whose `appState` fails to decode (skipped, not fatal). */
44
- readonly log?: (formatter: string, ...args: unknown[]) => void;
45
- }
46
-
47
- /** Build the advisory {@link QueryReplyV1} for `query` from the cohort's local registrations. */
48
- export async function handleMatchmakingQuery(query: QueryV1, ctx: CohortQueryContext): Promise<QueryReplyV1> {
49
- const providers: LocalProviderRegistration[] = [];
50
- const seekers: LocalSeekerRegistration[] = [];
51
-
52
- for (const rec of ctx.records) {
53
- if (rec.appState === undefined) {
54
- continue;
55
- }
56
- const participantId = bytesToPeerIdString(rec.participantId);
57
- let payload;
58
- try {
59
- payload = decodeMatchAppPayload(rec.appState);
60
- } catch (err) {
61
- // A record whose appState isn't a matchmaking payload (or is malformed) is not ours to serve;
62
- // skip it rather than fail the whole reply. Logged so it is never silently swallowed.
63
- ctx.log?.("matchmaking query handler: skipping undecodable record for %s: %o", participantId, err);
64
- continue;
65
- }
66
- if (payload.kind === "match-provider") {
67
- providers.push({ participantId, attachedAt: rec.attachedAt, payload });
68
- } else {
69
- seekers.push({ participantId, attachedAt: rec.attachedAt, payload });
70
- }
71
- }
72
-
73
- const evaluated = evaluateQuery(query, providers, seekers);
74
- const unsigned: Omit<QueryReplyV1, "signature"> = {
75
- v: 1,
76
- truncated: evaluated.truncated,
77
- cohortEpoch: bytesToB64url(ctx.cohortEpoch),
78
- topicTraffic: ctx.topicTraffic,
79
- };
80
- if (evaluated.providers !== undefined) {
81
- unsigned.providers = evaluated.providers;
82
- }
83
- if (evaluated.seekers !== undefined) {
84
- unsigned.seekers = evaluated.seekers;
85
- }
86
- const signature = await ctx.sign(queryReplySigningPayload(unsigned));
87
- return { ...unsigned, signature };
88
- }
1
+ /**
2
+ * Matchmaking — cohort-side `QueryV1` handler (db-p2p, wires the substrate store to the query reply).
3
+ *
4
+ * `docs/matchmaking.md` §Seeker query / §Capability filter. When a seeker dials a cohort with a
5
+ * {@link QueryV1}, the cohort returns its **locally-known direct registrations** for the topic, with the
6
+ * capability filter applied. This handler is the thin db-p2p binding: it reads the cohort's local
7
+ * {@link RegistrationRecord}s, decodes each one's `appState` into a provider/seeker payload, and hands
8
+ * them to the pure db-core {@link evaluateQuery} (filter + truncation + entry building). It then
9
+ * attaches the substrate's `topicTraffic` snapshot, the `cohortEpoch`, and the cohort **primary's**
10
+ * single-member reply signature (NOT a threshold signature — the reply is advisory; the seeker
11
+ * re-validates each entry's `registrationSig` via `verifyProviderEntry`).
12
+ *
13
+ * The handler is transport-light on purpose: the records, traffic, epoch, and signer are all injected,
14
+ * so it unit-tests without a live libp2p stack (mock-tier e2e is a documented follow-on). The FRET host
15
+ * supplies `records = store.listByTopic(topicId)`, `topicTraffic` from the cohort `TrafficCounters`,
16
+ * the current `cohortEpoch`, and a `sign` bound to this node's peer key.
17
+ */
18
+
19
+ import {
20
+ bytesToB64url,
21
+ decodeMatchAppPayload,
22
+ evaluateQuery,
23
+ queryReplySigningPayload,
24
+ type LocalProviderRegistration,
25
+ type LocalSeekerRegistration,
26
+ type QueryReplyV1,
27
+ type QueryV1,
28
+ type RegistrationRecord,
29
+ type TopicTrafficV1,
30
+ } from "@optimystic/db-core";
31
+ import { bytesToPeerIdString } from "../cohort-topic/peer-codec.js";
32
+
33
+ /** Everything the {@link handleMatchmakingQuery} needs from the cohort substrate, all injected. */
34
+ export interface CohortQueryContext {
35
+ /** The cohort's local registration records for the queried topic (e.g. `store.listByTopic(topicId)`). */
36
+ readonly records: readonly RegistrationRecord[];
37
+ /** The substrate's current traffic barometer for the topic (from the cohort `TrafficCounters`). */
38
+ readonly topicTraffic: TopicTrafficV1;
39
+ /** The current cohort epoch (32 bytes). */
40
+ readonly cohortEpoch: Uint8Array;
41
+ /** Sign the canonical reply image with the cohort primary's peer key; resolves the base64url signature. */
42
+ readonly sign: (payload: Uint8Array) => Promise<string>;
43
+ /** Optional logger for records whose `appState` fails to decode (skipped, not fatal). */
44
+ readonly log?: (formatter: string, ...args: unknown[]) => void;
45
+ }
46
+
47
+ /** Build the advisory {@link QueryReplyV1} for `query` from the cohort's local registrations. */
48
+ export async function handleMatchmakingQuery(query: QueryV1, ctx: CohortQueryContext): Promise<QueryReplyV1> {
49
+ const providers: LocalProviderRegistration[] = [];
50
+ const seekers: LocalSeekerRegistration[] = [];
51
+
52
+ for (const rec of ctx.records) {
53
+ if (rec.appState === undefined) {
54
+ continue;
55
+ }
56
+ const participantId = bytesToPeerIdString(rec.participantId);
57
+ let payload;
58
+ try {
59
+ payload = decodeMatchAppPayload(rec.appState);
60
+ } catch (err) {
61
+ // A record whose appState isn't a matchmaking payload (or is malformed) is not ours to serve;
62
+ // skip it rather than fail the whole reply. Logged so it is never silently swallowed.
63
+ ctx.log?.("matchmaking query handler: skipping undecodable record for %s: %o", participantId, err);
64
+ continue;
65
+ }
66
+ if (payload.kind === "match-provider") {
67
+ providers.push({ participantId, attachedAt: rec.attachedAt, payload });
68
+ } else {
69
+ seekers.push({ participantId, attachedAt: rec.attachedAt, payload });
70
+ }
71
+ }
72
+
73
+ const evaluated = evaluateQuery(query, providers, seekers);
74
+ const unsigned: Omit<QueryReplyV1, "signature"> = {
75
+ v: 1,
76
+ truncated: evaluated.truncated,
77
+ cohortEpoch: bytesToB64url(ctx.cohortEpoch),
78
+ topicTraffic: ctx.topicTraffic,
79
+ };
80
+ if (evaluated.providers !== undefined) {
81
+ unsigned.providers = evaluated.providers;
82
+ }
83
+ if (evaluated.seekers !== undefined) {
84
+ unsigned.seekers = evaluated.seekers;
85
+ }
86
+ const signature = await ctx.sign(queryReplySigningPayload(unsigned));
87
+ return { ...unsigned, signature };
88
+ }