@optimystic/db-p2p 0.21.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 (219) 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-policy.d.ts +13 -2
  6. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-policy.js +51 -4
  8. package/dist/src/cluster/cluster-policy.js.map +1 -1
  9. package/dist/src/cluster/cluster-repo.d.ts +42 -17
  10. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  11. package/dist/src/cluster/cluster-repo.js +229 -122
  12. package/dist/src/cluster/cluster-repo.js.map +1 -1
  13. package/dist/src/cluster/cluster-size-coupling.d.ts +28 -0
  14. package/dist/src/cluster/cluster-size-coupling.d.ts.map +1 -0
  15. package/dist/src/cluster/cluster-size-coupling.js +35 -0
  16. package/dist/src/cluster/cluster-size-coupling.js.map +1 -0
  17. package/dist/src/cluster/quorum-restore.d.ts +6 -0
  18. package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
  19. package/dist/src/cluster/quorum-restore.js +1 -1
  20. package/dist/src/cluster/quorum-restore.js.map +1 -1
  21. package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
  22. package/dist/src/cluster/reconcile-block.js +15 -3
  23. package/dist/src/cluster/reconcile-block.js.map +1 -1
  24. package/dist/src/cluster/service.d.ts +32 -1
  25. package/dist/src/cluster/service.d.ts.map +1 -1
  26. package/dist/src/cluster/service.js +43 -2
  27. package/dist/src/cluster/service.js.map +1 -1
  28. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  29. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  30. package/dist/src/cohort-topic/stream-util.js +56 -10
  31. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  32. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  33. package/dist/src/dispute/dispute-service.js +9 -3
  34. package/dist/src/dispute/dispute-service.js.map +1 -1
  35. package/dist/src/index.d.ts +5 -0
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +5 -0
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/libp2p-key-network.d.ts +134 -7
  40. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  41. package/dist/src/libp2p-key-network.js +174 -37
  42. package/dist/src/libp2p-key-network.js.map +1 -1
  43. package/dist/src/libp2p-node-base.d.ts +3 -2
  44. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  45. package/dist/src/libp2p-node-base.js +859 -778
  46. package/dist/src/libp2p-node-base.js.map +1 -1
  47. package/dist/src/libp2p-node-rn.d.ts +2 -2
  48. package/dist/src/libp2p-node-rn.d.ts.map +1 -1
  49. package/dist/src/libp2p-node-rn.js.map +1 -1
  50. package/dist/src/libp2p-node.d.ts +2 -2
  51. package/dist/src/libp2p-node.d.ts.map +1 -1
  52. package/dist/src/libp2p-node.js.map +1 -1
  53. package/dist/src/logger.d.ts +17 -1
  54. package/dist/src/logger.d.ts.map +1 -1
  55. package/dist/src/logger.js +19 -2
  56. package/dist/src/logger.js.map +1 -1
  57. package/dist/src/network/network-manager-service.d.ts +2 -0
  58. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  59. package/dist/src/network/network-manager-service.js +4 -0
  60. package/dist/src/network/network-manager-service.js.map +1 -1
  61. package/dist/src/optimystic-node.d.ts +35 -0
  62. package/dist/src/optimystic-node.d.ts.map +1 -0
  63. package/dist/src/optimystic-node.js +2 -0
  64. package/dist/src/optimystic-node.js.map +1 -0
  65. package/dist/src/owned-block-seed.d.ts +6 -3
  66. package/dist/src/owned-block-seed.d.ts.map +1 -1
  67. package/dist/src/owned-block-seed.js +16 -3
  68. package/dist/src/owned-block-seed.js.map +1 -1
  69. package/dist/src/peer-address-book.d.ts +72 -0
  70. package/dist/src/peer-address-book.d.ts.map +1 -0
  71. package/dist/src/peer-address-book.js +123 -0
  72. package/dist/src/peer-address-book.js.map +1 -0
  73. package/dist/src/repo/client.d.ts.map +1 -1
  74. package/dist/src/repo/client.js +11 -2
  75. package/dist/src/repo/client.js.map +1 -1
  76. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  77. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  78. package/dist/src/repo/cluster-coordinator.js +95 -3
  79. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  80. package/dist/src/repo/coordinator-repo.d.ts +78 -14
  81. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  82. package/dist/src/repo/coordinator-repo.js +266 -81
  83. package/dist/src/repo/coordinator-repo.js.map +1 -1
  84. package/dist/src/rn.d.ts +5 -0
  85. package/dist/src/rn.d.ts.map +1 -1
  86. package/dist/src/rn.js +5 -0
  87. package/dist/src/rn.js.map +1 -1
  88. package/dist/src/storage/block-storage.d.ts.map +1 -1
  89. package/dist/src/storage/block-storage.js +57 -5
  90. package/dist/src/storage/block-storage.js.map +1 -1
  91. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  92. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  93. package/dist/src/storage/cached-raw-storage.js +152 -0
  94. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  95. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  96. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  97. package/dist/src/storage/cached-store-driver.js +775 -0
  98. package/dist/src/storage/cached-store-driver.js.map +1 -0
  99. package/dist/src/storage/i-block-storage.d.ts +20 -1
  100. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  101. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  102. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  103. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  104. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  105. package/dist/src/storage/shared-cache-pool.js +354 -0
  106. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  107. package/dist/src/storage/storage-repo.d.ts +56 -3
  108. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  109. package/dist/src/storage/storage-repo.js +124 -18
  110. package/dist/src/storage/storage-repo.js.map +1 -1
  111. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  112. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  113. package/dist/src/testing/raw-storage-conformance.js +52 -2
  114. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  115. package/package.json +3 -3
  116. package/readme.md +668 -653
  117. package/src/cluster/block-transfer.ts +424 -424
  118. package/src/cluster/client.ts +119 -88
  119. package/src/cluster/cluster-error.ts +64 -64
  120. package/src/cluster/cluster-policy.ts +203 -152
  121. package/src/cluster/cluster-repo.ts +245 -125
  122. package/src/cluster/cluster-size-coupling.ts +45 -0
  123. package/src/cluster/commit-cert.ts +139 -139
  124. package/src/cluster/i-transaction-state-store.ts +43 -43
  125. package/src/cluster/memory-transaction-state-store.ts +56 -56
  126. package/src/cluster/peer-key-binding.ts +37 -37
  127. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  128. package/src/cluster/quorum-restore.ts +223 -223
  129. package/src/cluster/reconcile-block.ts +203 -191
  130. package/src/cluster/service.ts +293 -241
  131. package/src/cluster/supermajority-coupling.ts +37 -37
  132. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  133. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  134. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  135. package/src/cohort-topic/change-bridge.ts +109 -109
  136. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  137. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  138. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  139. package/src/cohort-topic/host.ts +2901 -2901
  140. package/src/cohort-topic/index.ts +13 -13
  141. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  142. package/src/cohort-topic/membership-source.ts +68 -68
  143. package/src/cohort-topic/peer-codec.ts +31 -31
  144. package/src/cohort-topic/peer-sig.ts +86 -86
  145. package/src/cohort-topic/protocols.ts +71 -71
  146. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  147. package/src/cohort-topic/size-estimator.ts +16 -16
  148. package/src/cohort-topic/stream-util.ts +135 -87
  149. package/src/cohort-topic/threshold-crypto.ts +239 -239
  150. package/src/cohort-topic/topic-router.ts +77 -77
  151. package/src/dispute/arbitrator-selection.ts +138 -138
  152. package/src/dispute/cascade.ts +524 -524
  153. package/src/dispute/dispute-service.ts +11 -5
  154. package/src/dispute/invalidation.ts +625 -625
  155. package/src/inbound-authorization.ts +190 -190
  156. package/src/index.ts +52 -47
  157. package/src/libp2p-key-network.ts +1120 -958
  158. package/src/libp2p-node-base.ts +1675 -1591
  159. package/src/libp2p-node-rn.ts +30 -30
  160. package/src/libp2p-node.ts +36 -36
  161. package/src/logger.ts +19 -2
  162. package/src/matchmaking/aggregate-counts.ts +104 -104
  163. package/src/matchmaking/index.ts +20 -20
  164. package/src/matchmaking/module.ts +363 -363
  165. package/src/matchmaking/protocols.ts +51 -51
  166. package/src/matchmaking/provider-manager.ts +95 -95
  167. package/src/matchmaking/query-handler.ts +88 -88
  168. package/src/matchmaking/query-transport.ts +492 -492
  169. package/src/matchmaking/seeker-manager.ts +64 -64
  170. package/src/matchmaking/seeker-walk-client.ts +293 -293
  171. package/src/matchmaking/traffic-validation.ts +195 -195
  172. package/src/network/network-manager-service.ts +5 -0
  173. package/src/optimystic-node.ts +36 -0
  174. package/src/owned-block-seed.ts +53 -40
  175. package/src/peer-address-book.ts +149 -0
  176. package/src/protocol-limits.ts +33 -33
  177. package/src/reactivity/forwarder-host.ts +438 -438
  178. package/src/reactivity/index.ts +19 -19
  179. package/src/reactivity/notify-transport.ts +144 -144
  180. package/src/reactivity/origination-manager.ts +192 -192
  181. package/src/reactivity/protocols.ts +61 -61
  182. package/src/reactivity/push-state-gossip.ts +291 -291
  183. package/src/reactivity/recover-transport.ts +408 -408
  184. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  185. package/src/reactivity/subscriber-registry.ts +96 -96
  186. package/src/reactivity/subscription-manager.ts +450 -450
  187. package/src/reactivity/topic-bytes.ts +37 -37
  188. package/src/repo/client.ts +12 -2
  189. package/src/repo/cluster-coordinator.ts +99 -3
  190. package/src/repo/coordinator-repo.ts +305 -82
  191. package/src/repo/types.ts +7 -7
  192. package/src/rn.ts +39 -34
  193. package/src/rpc-deadline.ts +45 -45
  194. package/src/storage/arachnode-partition.ts +74 -74
  195. package/src/storage/block-storage.ts +59 -6
  196. package/src/storage/cached-raw-storage.ts +180 -0
  197. package/src/storage/cached-store-driver.ts +859 -0
  198. package/src/storage/i-block-storage.ts +20 -1
  199. package/src/storage/i-kv-store.ts +8 -8
  200. package/src/storage/i-raw-storage.ts +12 -5
  201. package/src/storage/kv-raw-storage.ts +135 -135
  202. package/src/storage/memory-kv-store.ts +28 -28
  203. package/src/storage/memory-storage.ts +25 -25
  204. package/src/storage/memory-store-driver.ts +157 -157
  205. package/src/storage/raw-store-codec.ts +42 -42
  206. package/src/storage/raw-store-driver.ts +80 -80
  207. package/src/storage/ring-selector.ts +317 -317
  208. package/src/storage/ring-shift-coordinator.ts +271 -271
  209. package/src/storage/shared-cache-pool.ts +452 -0
  210. package/src/storage/storage-repo.ts +1014 -903
  211. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  212. package/src/testing/index.ts +8 -8
  213. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  214. package/src/testing/raw-storage-conformance.ts +453 -397
  215. package/src/testing/reactivity-mesh-harness.ts +922 -922
  216. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  217. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  218. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  219. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -1,30 +1,30 @@
1
- import type { Libp2p } from 'libp2p';
2
- import {
3
- createLibp2pNodeBase,
4
- type Libp2pTransports,
5
- type NodeOptions,
6
- type RawStorageProvider,
7
- } from './libp2p-node-base.js';
8
-
9
- export type { Libp2pTransports, NodeOptions, RawStorageProvider };
10
-
11
- /**
12
- * React Native-friendly libp2p node factory.
13
- *
14
- * This entrypoint intentionally does not import Node-only transports (like `@libp2p/tcp`).
15
- * Callers must provide `options.transports` (and typically `options.listenAddrs`).
16
- */
17
- export async function createLibp2pNode(options: NodeOptions): Promise<Libp2p> {
18
- const transports = options.transports;
19
- if (!transports || transports.length === 0) {
20
- throw new Error(
21
- 'createLibp2pNode (RN) requires options.transports. ' +
22
- 'Provide an RN-compatible transport (e.g. WebSockets) and any required listenAddrs.'
23
- );
24
- }
25
-
26
- return await createLibp2pNodeBase(options, {
27
- listenAddrs: [],
28
- transports,
29
- });
30
- }
1
+ import {
2
+ createLibp2pNodeBase,
3
+ type Libp2pTransports,
4
+ type NodeOptions,
5
+ type RawStorageProvider,
6
+ } from './libp2p-node-base.js';
7
+ import type { OptimysticNode } from './optimystic-node.js';
8
+
9
+ export type { Libp2pTransports, NodeOptions, RawStorageProvider };
10
+
11
+ /**
12
+ * React Native-friendly libp2p node factory.
13
+ *
14
+ * This entrypoint intentionally does not import Node-only transports (like `@libp2p/tcp`).
15
+ * Callers must provide `options.transports` (and typically `options.listenAddrs`).
16
+ */
17
+ export async function createLibp2pNode(options: NodeOptions): Promise<OptimysticNode> {
18
+ const transports = options.transports;
19
+ if (!transports || transports.length === 0) {
20
+ throw new Error(
21
+ 'createLibp2pNode (RN) requires options.transports. ' +
22
+ 'Provide an RN-compatible transport (e.g. WebSockets) and any required listenAddrs.'
23
+ );
24
+ }
25
+
26
+ return await createLibp2pNodeBase(options, {
27
+ listenAddrs: [],
28
+ transports,
29
+ });
30
+ }
@@ -1,36 +1,36 @@
1
- import type { Libp2p } from 'libp2p';
2
- import { tcp } from '@libp2p/tcp';
3
- import { webSockets } from '@libp2p/websockets';
4
- import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
5
- import {
6
- createLibp2pNodeBase,
7
- type Libp2pTransports,
8
- type NodeOptions,
9
- type RawStorageProvider,
10
- } from './libp2p-node-base.js';
11
-
12
- export type { Libp2pTransports, NodeOptions, RawStorageProvider };
13
-
14
- export async function createLibp2pNode(options: NodeOptions): Promise<Libp2p> {
15
- const port = options.port ?? 0;
16
- const wsHost = options.wsHost ?? '0.0.0.0';
17
-
18
- const defaultTransports: Libp2pTransports = [];
19
- const defaultListenAddrs: string[] = [];
20
-
21
- if (!options.disableTcp) {
22
- defaultTransports.push(tcp());
23
- defaultListenAddrs.push(`/ip4/0.0.0.0/tcp/${port}`);
24
- }
25
- if (options.wsPort !== undefined) {
26
- defaultTransports.push(webSockets());
27
- defaultListenAddrs.push(`/ip4/${wsHost}/tcp/${options.wsPort}/ws`);
28
- }
29
- // Always include the relay transport so this node can dial through relays
30
- defaultTransports.push(circuitRelayTransport());
31
-
32
- return await createLibp2pNodeBase(options, {
33
- listenAddrs: defaultListenAddrs,
34
- transports: defaultTransports,
35
- });
36
- }
1
+ import { tcp } from '@libp2p/tcp';
2
+ import { webSockets } from '@libp2p/websockets';
3
+ import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
4
+ import {
5
+ createLibp2pNodeBase,
6
+ type Libp2pTransports,
7
+ type NodeOptions,
8
+ type RawStorageProvider,
9
+ } from './libp2p-node-base.js';
10
+ import type { OptimysticNode } from './optimystic-node.js';
11
+
12
+ export type { Libp2pTransports, NodeOptions, RawStorageProvider };
13
+
14
+ export async function createLibp2pNode(options: NodeOptions): Promise<OptimysticNode> {
15
+ const port = options.port ?? 0;
16
+ const wsHost = options.wsHost ?? '0.0.0.0';
17
+
18
+ const defaultTransports: Libp2pTransports = [];
19
+ const defaultListenAddrs: string[] = [];
20
+
21
+ if (!options.disableTcp) {
22
+ defaultTransports.push(tcp());
23
+ defaultListenAddrs.push(`/ip4/0.0.0.0/tcp/${port}`);
24
+ }
25
+ if (options.wsPort !== undefined) {
26
+ defaultTransports.push(webSockets());
27
+ defaultListenAddrs.push(`/ip4/${wsHost}/tcp/${options.wsPort}/ws`);
28
+ }
29
+ // Always include the relay transport so this node can dial through relays
30
+ defaultTransports.push(circuitRelayTransport());
31
+
32
+ return await createLibp2pNodeBase(options, {
33
+ listenAddrs: defaultListenAddrs,
34
+ transports: defaultTransports,
35
+ });
36
+ }
package/src/logger.ts CHANGED
@@ -2,8 +2,25 @@ import debug from 'debug'
2
2
 
3
3
  const BASE_NAMESPACE = 'optimystic:db-p2p'
4
4
 
5
- export function createLogger(subNamespace: string): debug.Debugger {
6
- return debug(`${BASE_NAMESPACE}:${subNamespace}`)
5
+ /**
6
+ * Build a `debug` logger under `optimystic:db-p2p:<subNamespace>`, optionally suffixed with the
7
+ * owning node's peer id (`:<first 12 chars>`) so lines from several nodes sharing one process —
8
+ * every integration test — are attributable. Omit `peerId` and the namespace is byte-for-byte the
9
+ * un-suffixed one, so callers that don't know their peer id are unaffected.
10
+ *
11
+ * NOTE: 12 chars matches the truncation the rest of this package already uses in log payloads, but
12
+ * every Ed25519 peer id starts with the constant `12D3KooW`, so only ~4 base58 characters actually
13
+ * distinguish nodes (~11M combinations — ample for the handful of nodes a test process runs).
14
+ * Widen this only if a run ever needs to match a namespace against a full peer id.
15
+ *
16
+ * NOTE: only the two classes the diagnosability ticket named pass a peer id today
17
+ * (`Libp2pKeyPeerNetwork`, `CoordinatorRepo`); the package's other ~30 `createLogger` call sites
18
+ * still log under a flat namespace. Thread a peer id through any of them if a future diagnosis
19
+ * needs per-node attribution from that subsystem — the mechanism is already here.
20
+ */
21
+ export function createLogger(subNamespace: string, peerId?: string): debug.Debugger {
22
+ const suffix = peerId ? `:${peerId.substring(0, 12)}` : ''
23
+ return debug(`${BASE_NAMESPACE}:${subNamespace}${suffix}`)
7
24
  }
8
25
 
9
26
  export const verbose = typeof process !== 'undefined'
@@ -1,104 +1,104 @@
1
- /**
2
- * Matchmaking — root-cohort aggregate-count producer (db-p2p, wires the substrate to the sweep summary).
3
- *
4
- * `docs/matchmaking.md` §Multi-cohort sweep / §Aggregated provider counts. A *promoted* topic's root
5
- * cohort answers a sweeping seeker with an {@link AggregateCountV1}: log-bucketed provider counts per
6
- * tier-1 prefix shard, **threshold-signed** (an attested *registered*-provider count — unlike the
7
- * advisory, single-member-signed `QueryReplyV1`). This module is the thin db-p2p binding that builds
8
- * that message from the root's per-shard accounting and the cohort threshold signer.
9
- *
10
- * Two invariants the doc mandates, enforced here:
11
- *
12
- * - **Depth gate.** The aggregate is produced **only** when the tree depth at this cohort is
13
- * `>= aggregate_count_minimum_tier` (default 1). A cold cohort that fell through to `NoState` has no
14
- * tier-1 children to summarize, so {@link buildAggregateCount} returns `undefined` and the seeker
15
- * falls back to the single-cohort sample.
16
- * - **Log-bucketing.** Raw per-shard counts are quantized through the db-core {@link logBucketCount}
17
- * (largest power of two `<= n`), never forwarded exactly.
18
- *
19
- * Like the cohort query handler, the inputs are injected (records/accounting, epoch, depth, the threshold
20
- * signer), so this unit-tests without a live FRET/libp2p stack — the mock-tier e2e that drives it from a
21
- * real promoted root is a documented follow-on. In the FRET host the root `CoordEngine` supplies
22
- * `shardCounts` from its child-cohort accounting (the per-tier-1 `childCohortCount` / gossip-derived
23
- * summaries), `treeDepth` from its tier state, `cohortEpoch` from the membership, and `thresholdSign`
24
- * bound to the cohort signer's `/sign` assembly.
25
- */
26
-
27
- import {
28
- bytesToB64url,
29
- logBucketCount,
30
- aggregateCountSigningPayload,
31
- AGGREGATE_COUNT_MINIMUM_TIER,
32
- DEFAULT_FANOUT,
33
- DEFAULT_SWEEP_TARGET_TIER,
34
- type AggregateBucketV1,
35
- type AggregateCountV1,
36
- } from "@optimystic/db-core";
37
-
38
- /** Everything {@link buildAggregateCount} needs from the root cohort, all injected. */
39
- export interface AggregateCountContext {
40
- /** The topic id being summarized, 32 bytes. */
41
- readonly topicId: Uint8Array;
42
- /** The current cohort epoch, 32 bytes. */
43
- readonly cohortEpoch: Uint8Array;
44
- /**
45
- * Tree depth at this cohort. The aggregate is produced only when `treeDepth >= minimumTier`; a cold
46
- * cohort (depth 0) summarizes nothing.
47
- */
48
- readonly treeDepth: number;
49
- /**
50
- * Raw per-tier-1-shard provider counts, keyed by `prefixSlot` (`0..fanout-1`). Slots absent from the
51
- * map count as `0`. The root derives these from its child-cohort accounting; this binding log-buckets
52
- * them and never trusts the caller to pre-bucket.
53
- */
54
- readonly shardCounts: ReadonlyMap<number, number>;
55
- /**
56
- * Assemble a cohort threshold signature over the canonical aggregate image (db-p2p binds the cohort
57
- * signer). Returns the multisig blob plus the `>= minSigs` signer subset that produced it.
58
- */
59
- readonly thresholdSign: (payload: Uint8Array) => Promise<{ thresholdSig: Uint8Array; signers: Uint8Array[] }>;
60
- /** The tier whose shards are summarized. Default {@link DEFAULT_SWEEP_TARGET_TIER} (1). */
61
- readonly targetTier?: number;
62
- /** Number of prefix slots `F`. Default {@link DEFAULT_FANOUT} (16). */
63
- readonly fanout?: number;
64
- /** Depth gate. Default {@link AGGREGATE_COUNT_MINIMUM_TIER} (1). */
65
- readonly minimumTier?: number;
66
- /** Emit zero-count shards too (default `false` — empty shards are omitted to keep the summary compact). */
67
- readonly includeEmptyShards?: boolean;
68
- }
69
-
70
- /**
71
- * Build the threshold-signed {@link AggregateCountV1} for the root's per-shard accounting, or `undefined`
72
- * when the depth gate is not met (`treeDepth < minimumTier`). Counts are log-bucketed; the canonical
73
- * image is order-independent (the signing payload sorts buckets), so signer and verifier agree.
74
- */
75
- export async function buildAggregateCount(ctx: AggregateCountContext): Promise<AggregateCountV1 | undefined> {
76
- const minimumTier = ctx.minimumTier ?? AGGREGATE_COUNT_MINIMUM_TIER;
77
- if (ctx.treeDepth < minimumTier) {
78
- // Cold / unpromoted root: no tier-1 children to summarize (matches the seeker's NoState fallback).
79
- return undefined;
80
- }
81
-
82
- const targetTier = ctx.targetTier ?? DEFAULT_SWEEP_TARGET_TIER;
83
- const fanout = ctx.fanout ?? DEFAULT_FANOUT;
84
- const bucketCounts: AggregateBucketV1[] = [];
85
- for (let prefixSlot = 0; prefixSlot < fanout; prefixSlot++) {
86
- const count = logBucketCount(ctx.shardCounts.get(prefixSlot) ?? 0);
87
- if (count > 0 || ctx.includeEmptyShards === true) {
88
- bucketCounts.push({ targetTier, prefixSlot, count });
89
- }
90
- }
91
-
92
- const unsigned: Omit<AggregateCountV1, "signature" | "signers"> = {
93
- v: 1,
94
- topicId: bytesToB64url(ctx.topicId),
95
- bucketCounts,
96
- cohortEpoch: bytesToB64url(ctx.cohortEpoch),
97
- };
98
- const { thresholdSig, signers } = await ctx.thresholdSign(aggregateCountSigningPayload(unsigned));
99
- return {
100
- ...unsigned,
101
- signature: bytesToB64url(thresholdSig),
102
- signers: signers.map(bytesToB64url),
103
- };
104
- }
1
+ /**
2
+ * Matchmaking — root-cohort aggregate-count producer (db-p2p, wires the substrate to the sweep summary).
3
+ *
4
+ * `docs/matchmaking.md` §Multi-cohort sweep / §Aggregated provider counts. A *promoted* topic's root
5
+ * cohort answers a sweeping seeker with an {@link AggregateCountV1}: log-bucketed provider counts per
6
+ * tier-1 prefix shard, **threshold-signed** (an attested *registered*-provider count — unlike the
7
+ * advisory, single-member-signed `QueryReplyV1`). This module is the thin db-p2p binding that builds
8
+ * that message from the root's per-shard accounting and the cohort threshold signer.
9
+ *
10
+ * Two invariants the doc mandates, enforced here:
11
+ *
12
+ * - **Depth gate.** The aggregate is produced **only** when the tree depth at this cohort is
13
+ * `>= aggregate_count_minimum_tier` (default 1). A cold cohort that fell through to `NoState` has no
14
+ * tier-1 children to summarize, so {@link buildAggregateCount} returns `undefined` and the seeker
15
+ * falls back to the single-cohort sample.
16
+ * - **Log-bucketing.** Raw per-shard counts are quantized through the db-core {@link logBucketCount}
17
+ * (largest power of two `<= n`), never forwarded exactly.
18
+ *
19
+ * Like the cohort query handler, the inputs are injected (records/accounting, epoch, depth, the threshold
20
+ * signer), so this unit-tests without a live FRET/libp2p stack — the mock-tier e2e that drives it from a
21
+ * real promoted root is a documented follow-on. In the FRET host the root `CoordEngine` supplies
22
+ * `shardCounts` from its child-cohort accounting (the per-tier-1 `childCohortCount` / gossip-derived
23
+ * summaries), `treeDepth` from its tier state, `cohortEpoch` from the membership, and `thresholdSign`
24
+ * bound to the cohort signer's `/sign` assembly.
25
+ */
26
+
27
+ import {
28
+ bytesToB64url,
29
+ logBucketCount,
30
+ aggregateCountSigningPayload,
31
+ AGGREGATE_COUNT_MINIMUM_TIER,
32
+ DEFAULT_FANOUT,
33
+ DEFAULT_SWEEP_TARGET_TIER,
34
+ type AggregateBucketV1,
35
+ type AggregateCountV1,
36
+ } from "@optimystic/db-core";
37
+
38
+ /** Everything {@link buildAggregateCount} needs from the root cohort, all injected. */
39
+ export interface AggregateCountContext {
40
+ /** The topic id being summarized, 32 bytes. */
41
+ readonly topicId: Uint8Array;
42
+ /** The current cohort epoch, 32 bytes. */
43
+ readonly cohortEpoch: Uint8Array;
44
+ /**
45
+ * Tree depth at this cohort. The aggregate is produced only when `treeDepth >= minimumTier`; a cold
46
+ * cohort (depth 0) summarizes nothing.
47
+ */
48
+ readonly treeDepth: number;
49
+ /**
50
+ * Raw per-tier-1-shard provider counts, keyed by `prefixSlot` (`0..fanout-1`). Slots absent from the
51
+ * map count as `0`. The root derives these from its child-cohort accounting; this binding log-buckets
52
+ * them and never trusts the caller to pre-bucket.
53
+ */
54
+ readonly shardCounts: ReadonlyMap<number, number>;
55
+ /**
56
+ * Assemble a cohort threshold signature over the canonical aggregate image (db-p2p binds the cohort
57
+ * signer). Returns the multisig blob plus the `>= minSigs` signer subset that produced it.
58
+ */
59
+ readonly thresholdSign: (payload: Uint8Array) => Promise<{ thresholdSig: Uint8Array; signers: Uint8Array[] }>;
60
+ /** The tier whose shards are summarized. Default {@link DEFAULT_SWEEP_TARGET_TIER} (1). */
61
+ readonly targetTier?: number;
62
+ /** Number of prefix slots `F`. Default {@link DEFAULT_FANOUT} (16). */
63
+ readonly fanout?: number;
64
+ /** Depth gate. Default {@link AGGREGATE_COUNT_MINIMUM_TIER} (1). */
65
+ readonly minimumTier?: number;
66
+ /** Emit zero-count shards too (default `false` — empty shards are omitted to keep the summary compact). */
67
+ readonly includeEmptyShards?: boolean;
68
+ }
69
+
70
+ /**
71
+ * Build the threshold-signed {@link AggregateCountV1} for the root's per-shard accounting, or `undefined`
72
+ * when the depth gate is not met (`treeDepth < minimumTier`). Counts are log-bucketed; the canonical
73
+ * image is order-independent (the signing payload sorts buckets), so signer and verifier agree.
74
+ */
75
+ export async function buildAggregateCount(ctx: AggregateCountContext): Promise<AggregateCountV1 | undefined> {
76
+ const minimumTier = ctx.minimumTier ?? AGGREGATE_COUNT_MINIMUM_TIER;
77
+ if (ctx.treeDepth < minimumTier) {
78
+ // Cold / unpromoted root: no tier-1 children to summarize (matches the seeker's NoState fallback).
79
+ return undefined;
80
+ }
81
+
82
+ const targetTier = ctx.targetTier ?? DEFAULT_SWEEP_TARGET_TIER;
83
+ const fanout = ctx.fanout ?? DEFAULT_FANOUT;
84
+ const bucketCounts: AggregateBucketV1[] = [];
85
+ for (let prefixSlot = 0; prefixSlot < fanout; prefixSlot++) {
86
+ const count = logBucketCount(ctx.shardCounts.get(prefixSlot) ?? 0);
87
+ if (count > 0 || ctx.includeEmptyShards === true) {
88
+ bucketCounts.push({ targetTier, prefixSlot, count });
89
+ }
90
+ }
91
+
92
+ const unsigned: Omit<AggregateCountV1, "signature" | "signers"> = {
93
+ v: 1,
94
+ topicId: bytesToB64url(ctx.topicId),
95
+ bucketCounts,
96
+ cohortEpoch: bytesToB64url(ctx.cohortEpoch),
97
+ };
98
+ const { thresholdSig, signers } = await ctx.thresholdSign(aggregateCountSigningPayload(unsigned));
99
+ return {
100
+ ...unsigned,
101
+ signature: bytesToB64url(thresholdSig),
102
+ signers: signers.map(bytesToB64url),
103
+ };
104
+ }
@@ -1,20 +1,20 @@
1
- /**
2
- * Matchmaking — db-p2p wiring to the cohort-topic substrate.
3
- *
4
- * The db-core `matchmaking` module owns the wire codecs, anchor, config, provider/seeker
5
- * decision/state, the capability filter, the pure query evaluation, and the hang-out decision engine;
6
- * these db-p2p bindings wire that logic to the cohort-topic substrate: the register/renew/withdraw
7
- * managers (tier T2), the cohort-side `QueryV1` handler, and the seeker walk client that drives the
8
- * hang-out-vs-continue walk. See `docs/matchmaking.md` §Provider registration / §Seeker query /
9
- * §Hang-out vs. continue.
10
- */
11
-
12
- export * from "./protocols.js";
13
- export * from "./provider-manager.js";
14
- export * from "./seeker-manager.js";
15
- export * from "./query-handler.js";
16
- export * from "./query-transport.js";
17
- export * from "./seeker-walk-client.js";
18
- export * from "./aggregate-counts.js";
19
- export * from "./traffic-validation.js";
20
- export * from "./module.js";
1
+ /**
2
+ * Matchmaking — db-p2p wiring to the cohort-topic substrate.
3
+ *
4
+ * The db-core `matchmaking` module owns the wire codecs, anchor, config, provider/seeker
5
+ * decision/state, the capability filter, the pure query evaluation, and the hang-out decision engine;
6
+ * these db-p2p bindings wire that logic to the cohort-topic substrate: the register/renew/withdraw
7
+ * managers (tier T2), the cohort-side `QueryV1` handler, and the seeker walk client that drives the
8
+ * hang-out-vs-continue walk. See `docs/matchmaking.md` §Provider registration / §Seeker query /
9
+ * §Hang-out vs. continue.
10
+ */
11
+
12
+ export * from "./protocols.js";
13
+ export * from "./provider-manager.js";
14
+ export * from "./seeker-manager.js";
15
+ export * from "./query-handler.js";
16
+ export * from "./query-transport.js";
17
+ export * from "./seeker-walk-client.js";
18
+ export * from "./aggregate-counts.js";
19
+ export * from "./traffic-validation.js";
20
+ export * from "./module.js";