@optimystic/db-p2p 0.22.0 → 0.24.1

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 (194) 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/host.js +34 -11
  14. package/dist/src/cohort-topic/host.js.map +1 -1
  15. package/dist/src/cohort-topic/stream-util.d.ts +37 -7
  16. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  17. package/dist/src/cohort-topic/stream-util.js +77 -19
  18. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  19. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  20. package/dist/src/dispute/dispute-service.js +9 -3
  21. package/dist/src/dispute/dispute-service.js.map +1 -1
  22. package/dist/src/index.d.ts +3 -0
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +3 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/libp2p-key-network.d.ts +88 -2
  27. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  28. package/dist/src/libp2p-key-network.js +134 -28
  29. package/dist/src/libp2p-key-network.js.map +1 -1
  30. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  31. package/dist/src/libp2p-node-base.js +25 -1
  32. package/dist/src/libp2p-node-base.js.map +1 -1
  33. package/dist/src/logger.d.ts +17 -1
  34. package/dist/src/logger.d.ts.map +1 -1
  35. package/dist/src/logger.js +19 -2
  36. package/dist/src/logger.js.map +1 -1
  37. package/dist/src/matchmaking/query-transport.js +3 -3
  38. package/dist/src/matchmaking/query-transport.js.map +1 -1
  39. package/dist/src/owned-block-seed.d.ts +6 -3
  40. package/dist/src/owned-block-seed.d.ts.map +1 -1
  41. package/dist/src/owned-block-seed.js +16 -3
  42. package/dist/src/owned-block-seed.js.map +1 -1
  43. package/dist/src/peer-address-book.d.ts +72 -0
  44. package/dist/src/peer-address-book.d.ts.map +1 -0
  45. package/dist/src/peer-address-book.js +123 -0
  46. package/dist/src/peer-address-book.js.map +1 -0
  47. package/dist/src/reactivity/notify-transport.d.ts +4 -4
  48. package/dist/src/reactivity/notify-transport.js +6 -6
  49. package/dist/src/reactivity/notify-transport.js.map +1 -1
  50. package/dist/src/reactivity/push-state-gossip.js +2 -2
  51. package/dist/src/reactivity/push-state-gossip.js.map +1 -1
  52. package/dist/src/reactivity/recover-transport.d.ts +6 -2
  53. package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
  54. package/dist/src/reactivity/recover-transport.js +7 -3
  55. package/dist/src/reactivity/recover-transport.js.map +1 -1
  56. package/dist/src/repo/client.d.ts.map +1 -1
  57. package/dist/src/repo/client.js +11 -2
  58. package/dist/src/repo/client.js.map +1 -1
  59. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  60. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  61. package/dist/src/repo/cluster-coordinator.js +95 -3
  62. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  63. package/dist/src/repo/coordinator-repo.d.ts +62 -9
  64. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  65. package/dist/src/repo/coordinator-repo.js +242 -73
  66. package/dist/src/repo/coordinator-repo.js.map +1 -1
  67. package/dist/src/rn.d.ts +3 -0
  68. package/dist/src/rn.d.ts.map +1 -1
  69. package/dist/src/rn.js +3 -0
  70. package/dist/src/rn.js.map +1 -1
  71. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  72. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  73. package/dist/src/storage/cached-raw-storage.js +152 -0
  74. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  75. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  76. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  77. package/dist/src/storage/cached-store-driver.js +775 -0
  78. package/dist/src/storage/cached-store-driver.js.map +1 -0
  79. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  80. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  81. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  82. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  83. package/dist/src/storage/shared-cache-pool.js +354 -0
  84. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  85. package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
  86. package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
  87. package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
  88. package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
  89. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  90. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  91. package/dist/src/testing/raw-storage-conformance.js +35 -2
  92. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  93. package/package.json +3 -3
  94. package/readme.md +668 -668
  95. package/src/cluster/block-transfer.ts +424 -424
  96. package/src/cluster/client.ts +119 -88
  97. package/src/cluster/cluster-error.ts +64 -64
  98. package/src/cluster/cluster-policy.ts +203 -203
  99. package/src/cluster/cluster-repo.ts +242 -122
  100. package/src/cluster/cluster-size-coupling.ts +45 -45
  101. package/src/cluster/commit-cert.ts +139 -139
  102. package/src/cluster/i-transaction-state-store.ts +43 -43
  103. package/src/cluster/memory-transaction-state-store.ts +56 -56
  104. package/src/cluster/peer-key-binding.ts +37 -37
  105. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  106. package/src/cluster/quorum-restore.ts +223 -223
  107. package/src/cluster/reconcile-block.ts +203 -203
  108. package/src/cluster/service.ts +293 -241
  109. package/src/cluster/supermajority-coupling.ts +37 -37
  110. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  111. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  112. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  113. package/src/cohort-topic/change-bridge.ts +109 -109
  114. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  115. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  116. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  117. package/src/cohort-topic/host.ts +42 -11
  118. package/src/cohort-topic/index.ts +13 -13
  119. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  120. package/src/cohort-topic/membership-source.ts +68 -68
  121. package/src/cohort-topic/peer-codec.ts +31 -31
  122. package/src/cohort-topic/peer-sig.ts +86 -86
  123. package/src/cohort-topic/protocols.ts +71 -71
  124. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  125. package/src/cohort-topic/size-estimator.ts +16 -16
  126. package/src/cohort-topic/stream-util.ts +79 -19
  127. package/src/cohort-topic/threshold-crypto.ts +239 -239
  128. package/src/cohort-topic/topic-router.ts +77 -77
  129. package/src/dispute/arbitrator-selection.ts +138 -138
  130. package/src/dispute/cascade.ts +524 -524
  131. package/src/dispute/dispute-service.ts +11 -5
  132. package/src/dispute/invalidation.ts +625 -625
  133. package/src/inbound-authorization.ts +190 -190
  134. package/src/index.ts +52 -49
  135. package/src/libp2p-key-network.ts +1120 -990
  136. package/src/libp2p-node-base.ts +1675 -1651
  137. package/src/libp2p-node-rn.ts +30 -30
  138. package/src/libp2p-node.ts +36 -36
  139. package/src/logger.ts +19 -2
  140. package/src/matchmaking/aggregate-counts.ts +104 -104
  141. package/src/matchmaking/index.ts +20 -20
  142. package/src/matchmaking/module.ts +363 -363
  143. package/src/matchmaking/protocols.ts +51 -51
  144. package/src/matchmaking/provider-manager.ts +95 -95
  145. package/src/matchmaking/query-handler.ts +88 -88
  146. package/src/matchmaking/query-transport.ts +3 -3
  147. package/src/matchmaking/seeker-manager.ts +64 -64
  148. package/src/matchmaking/seeker-walk-client.ts +293 -293
  149. package/src/matchmaking/traffic-validation.ts +195 -195
  150. package/src/optimystic-node.ts +36 -36
  151. package/src/owned-block-seed.ts +53 -40
  152. package/src/peer-address-book.ts +149 -0
  153. package/src/protocol-limits.ts +33 -33
  154. package/src/reactivity/forwarder-host.ts +438 -438
  155. package/src/reactivity/index.ts +19 -19
  156. package/src/reactivity/notify-transport.ts +144 -144
  157. package/src/reactivity/origination-manager.ts +192 -192
  158. package/src/reactivity/protocols.ts +61 -61
  159. package/src/reactivity/push-state-gossip.ts +291 -291
  160. package/src/reactivity/recover-transport.ts +7 -3
  161. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  162. package/src/reactivity/subscriber-registry.ts +96 -96
  163. package/src/reactivity/subscription-manager.ts +450 -450
  164. package/src/reactivity/topic-bytes.ts +37 -37
  165. package/src/repo/client.ts +12 -2
  166. package/src/repo/cluster-coordinator.ts +99 -3
  167. package/src/repo/coordinator-repo.ts +281 -74
  168. package/src/repo/types.ts +7 -7
  169. package/src/rn.ts +39 -36
  170. package/src/rpc-deadline.ts +45 -45
  171. package/src/storage/arachnode-partition.ts +74 -74
  172. package/src/storage/cached-raw-storage.ts +180 -0
  173. package/src/storage/cached-store-driver.ts +859 -0
  174. package/src/storage/i-kv-store.ts +8 -8
  175. package/src/storage/i-raw-storage.ts +12 -5
  176. package/src/storage/kv-raw-storage.ts +135 -135
  177. package/src/storage/memory-kv-store.ts +28 -28
  178. package/src/storage/memory-storage.ts +25 -25
  179. package/src/storage/memory-store-driver.ts +157 -157
  180. package/src/storage/raw-store-codec.ts +42 -42
  181. package/src/storage/raw-store-driver.ts +80 -80
  182. package/src/storage/ring-selector.ts +317 -317
  183. package/src/storage/ring-shift-coordinator.ts +271 -271
  184. package/src/storage/shared-cache-pool.ts +452 -0
  185. package/src/storage/storage-repo.ts +1014 -1014
  186. package/src/testing/cohort-topic-mesh-harness.ts +673 -663
  187. package/src/testing/index.ts +8 -8
  188. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  189. package/src/testing/raw-storage-conformance.ts +453 -417
  190. package/src/testing/reactivity-mesh-harness.ts +922 -922
  191. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  192. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  193. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  194. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -1,30 +1,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
+ 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 { 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
- }
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";