@optimystic/db-p2p 0.13.5 → 0.14.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 (342) hide show
  1. package/README.md +6 -6
  2. package/dist/src/cluster/block-transfer-service.d.ts +67 -8
  3. package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
  4. package/dist/src/cluster/block-transfer-service.js +101 -62
  5. package/dist/src/cluster/block-transfer-service.js.map +1 -1
  6. package/dist/src/cluster/client.d.ts +18 -1
  7. package/dist/src/cluster/client.d.ts.map +1 -1
  8. package/dist/src/cluster/client.js +55 -39
  9. package/dist/src/cluster/client.js.map +1 -1
  10. package/dist/src/cluster/cluster-error.d.ts +38 -0
  11. package/dist/src/cluster/cluster-error.d.ts.map +1 -0
  12. package/dist/src/cluster/cluster-error.js +51 -0
  13. package/dist/src/cluster/cluster-error.js.map +1 -0
  14. package/dist/src/cluster/cluster-repo.d.ts +135 -3
  15. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  16. package/dist/src/cluster/cluster-repo.js +333 -58
  17. package/dist/src/cluster/cluster-repo.js.map +1 -1
  18. package/dist/src/cluster/commit-cert.d.ts +63 -0
  19. package/dist/src/cluster/commit-cert.d.ts.map +1 -0
  20. package/dist/src/cluster/commit-cert.js +108 -0
  21. package/dist/src/cluster/commit-cert.js.map +1 -0
  22. package/dist/src/cluster/service.d.ts +46 -11
  23. package/dist/src/cluster/service.d.ts.map +1 -1
  24. package/dist/src/cluster/service.js +97 -33
  25. package/dist/src/cluster/service.js.map +1 -1
  26. package/dist/src/cluster/spread-on-churn.d.ts +13 -0
  27. package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
  28. package/dist/src/cluster/spread-on-churn.js +34 -4
  29. package/dist/src/cluster/spread-on-churn.js.map +1 -1
  30. package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts +59 -0
  31. package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts.map +1 -0
  32. package/dist/src/cohort-topic/bootstrap-evidence-builder.js +79 -0
  33. package/dist/src/cohort-topic/bootstrap-evidence-builder.js.map +1 -0
  34. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts +81 -0
  35. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts.map +1 -0
  36. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js +93 -0
  37. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js.map +1 -0
  38. package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts +108 -0
  39. package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts.map +1 -0
  40. package/dist/src/cohort-topic/bootstrap-parent-reference.js +108 -0
  41. package/dist/src/cohort-topic/bootstrap-parent-reference.js.map +1 -0
  42. package/dist/src/cohort-topic/change-bridge.d.ts +65 -0
  43. package/dist/src/cohort-topic/change-bridge.d.ts.map +1 -0
  44. package/dist/src/cohort-topic/change-bridge.js +76 -0
  45. package/dist/src/cohort-topic/change-bridge.js.map +1 -0
  46. package/dist/src/cohort-topic/cohort-gossip-driver.d.ts +74 -0
  47. package/dist/src/cohort-topic/cohort-gossip-driver.d.ts.map +1 -0
  48. package/dist/src/cohort-topic/cohort-gossip-driver.js +87 -0
  49. package/dist/src/cohort-topic/cohort-gossip-driver.js.map +1 -0
  50. package/dist/src/cohort-topic/cohort-gossip-transport.d.ts +42 -0
  51. package/dist/src/cohort-topic/cohort-gossip-transport.d.ts.map +1 -0
  52. package/dist/src/cohort-topic/cohort-gossip-transport.js +57 -0
  53. package/dist/src/cohort-topic/cohort-gossip-transport.js.map +1 -0
  54. package/dist/src/cohort-topic/fret-trust-anchor.d.ts +100 -0
  55. package/dist/src/cohort-topic/fret-trust-anchor.d.ts.map +1 -0
  56. package/dist/src/cohort-topic/fret-trust-anchor.js +115 -0
  57. package/dist/src/cohort-topic/fret-trust-anchor.js.map +1 -0
  58. package/dist/src/cohort-topic/host.d.ts +573 -0
  59. package/dist/src/cohort-topic/host.d.ts.map +1 -0
  60. package/dist/src/cohort-topic/host.js +1460 -0
  61. package/dist/src/cohort-topic/host.js.map +1 -0
  62. package/dist/src/cohort-topic/index.d.ts +14 -0
  63. package/dist/src/cohort-topic/index.d.ts.map +1 -0
  64. package/dist/src/cohort-topic/index.js +14 -0
  65. package/dist/src/cohort-topic/index.js.map +1 -0
  66. package/dist/src/cohort-topic/membership-publish-sink.d.ts +14 -0
  67. package/dist/src/cohort-topic/membership-publish-sink.d.ts.map +1 -0
  68. package/dist/src/cohort-topic/membership-publish-sink.js +17 -0
  69. package/dist/src/cohort-topic/membership-publish-sink.js.map +1 -0
  70. package/dist/src/cohort-topic/membership-source.d.ts +36 -0
  71. package/dist/src/cohort-topic/membership-source.d.ts.map +1 -0
  72. package/dist/src/cohort-topic/membership-source.js +58 -0
  73. package/dist/src/cohort-topic/membership-source.js.map +1 -0
  74. package/dist/src/cohort-topic/peer-codec.d.ts +18 -0
  75. package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -0
  76. package/dist/src/cohort-topic/peer-codec.js +26 -0
  77. package/dist/src/cohort-topic/peer-codec.js.map +1 -0
  78. package/dist/src/cohort-topic/peer-sig.d.ts +52 -0
  79. package/dist/src/cohort-topic/peer-sig.d.ts.map +1 -0
  80. package/dist/src/cohort-topic/peer-sig.js +83 -0
  81. package/dist/src/cohort-topic/peer-sig.js.map +1 -0
  82. package/dist/src/cohort-topic/protocols.d.ts +49 -0
  83. package/dist/src/cohort-topic/protocols.d.ts.map +1 -0
  84. package/dist/src/cohort-topic/protocols.js +58 -0
  85. package/dist/src/cohort-topic/protocols.js.map +1 -0
  86. package/dist/src/cohort-topic/reactivity-membership-gate.d.ts +49 -0
  87. package/dist/src/cohort-topic/reactivity-membership-gate.d.ts.map +1 -0
  88. package/dist/src/cohort-topic/reactivity-membership-gate.js +42 -0
  89. package/dist/src/cohort-topic/reactivity-membership-gate.js.map +1 -0
  90. package/dist/src/cohort-topic/size-estimator.d.ts +16 -0
  91. package/dist/src/cohort-topic/size-estimator.d.ts.map +1 -0
  92. package/dist/src/cohort-topic/size-estimator.js +16 -0
  93. package/dist/src/cohort-topic/size-estimator.js.map +1 -0
  94. package/dist/src/cohort-topic/stream-util.d.ts +19 -0
  95. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -0
  96. package/dist/src/cohort-topic/stream-util.js +76 -0
  97. package/dist/src/cohort-topic/stream-util.js.map +1 -0
  98. package/dist/src/cohort-topic/threshold-crypto.d.ts +94 -0
  99. package/dist/src/cohort-topic/threshold-crypto.d.ts.map +1 -0
  100. package/dist/src/cohort-topic/threshold-crypto.js +178 -0
  101. package/dist/src/cohort-topic/threshold-crypto.js.map +1 -0
  102. package/dist/src/cohort-topic/topic-router.d.ts +41 -0
  103. package/dist/src/cohort-topic/topic-router.d.ts.map +1 -0
  104. package/dist/src/cohort-topic/topic-router.js +63 -0
  105. package/dist/src/cohort-topic/topic-router.js.map +1 -0
  106. package/dist/src/dispute/cascade.d.ts +179 -0
  107. package/dist/src/dispute/cascade.d.ts.map +1 -0
  108. package/dist/src/dispute/cascade.js +315 -0
  109. package/dist/src/dispute/cascade.js.map +1 -0
  110. package/dist/src/dispute/client.d.ts +2 -1
  111. package/dist/src/dispute/client.d.ts.map +1 -1
  112. package/dist/src/dispute/client.js +12 -3
  113. package/dist/src/dispute/client.js.map +1 -1
  114. package/dist/src/dispute/dispute-service.d.ts +41 -1
  115. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  116. package/dist/src/dispute/dispute-service.js +147 -21
  117. package/dist/src/dispute/dispute-service.js.map +1 -1
  118. package/dist/src/dispute/index.d.ts +2 -0
  119. package/dist/src/dispute/index.d.ts.map +1 -1
  120. package/dist/src/dispute/index.js +2 -0
  121. package/dist/src/dispute/index.js.map +1 -1
  122. package/dist/src/dispute/invalidation.d.ts +266 -0
  123. package/dist/src/dispute/invalidation.d.ts.map +1 -0
  124. package/dist/src/dispute/invalidation.js +405 -0
  125. package/dist/src/dispute/invalidation.js.map +1 -0
  126. package/dist/src/dispute/types.d.ts +14 -0
  127. package/dist/src/dispute/types.d.ts.map +1 -1
  128. package/dist/src/dispute/types.js.map +1 -1
  129. package/dist/src/index.d.ts +4 -0
  130. package/dist/src/index.d.ts.map +1 -1
  131. package/dist/src/index.js +4 -0
  132. package/dist/src/index.js.map +1 -1
  133. package/dist/src/libp2p-key-network.d.ts +7 -0
  134. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  135. package/dist/src/libp2p-key-network.js +49 -5
  136. package/dist/src/libp2p-key-network.js.map +1 -1
  137. package/dist/src/libp2p-node-base.d.ts +45 -3
  138. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  139. package/dist/src/libp2p-node-base.js +522 -5
  140. package/dist/src/libp2p-node-base.js.map +1 -1
  141. package/dist/src/matchmaking/aggregate-counts.d.ts +67 -0
  142. package/dist/src/matchmaking/aggregate-counts.d.ts.map +1 -0
  143. package/dist/src/matchmaking/aggregate-counts.js +60 -0
  144. package/dist/src/matchmaking/aggregate-counts.js.map +1 -0
  145. package/dist/src/matchmaking/index.d.ts +20 -0
  146. package/dist/src/matchmaking/index.d.ts.map +1 -0
  147. package/dist/src/matchmaking/index.js +20 -0
  148. package/dist/src/matchmaking/index.js.map +1 -0
  149. package/dist/src/matchmaking/module.d.ts +181 -0
  150. package/dist/src/matchmaking/module.d.ts.map +1 -0
  151. package/dist/src/matchmaking/module.js +221 -0
  152. package/dist/src/matchmaking/module.js.map +1 -0
  153. package/dist/src/matchmaking/protocols.d.ts +37 -0
  154. package/dist/src/matchmaking/protocols.d.ts.map +1 -0
  155. package/dist/src/matchmaking/protocols.js +42 -0
  156. package/dist/src/matchmaking/protocols.js.map +1 -0
  157. package/dist/src/matchmaking/provider-manager.d.ts +52 -0
  158. package/dist/src/matchmaking/provider-manager.d.ts.map +1 -0
  159. package/dist/src/matchmaking/provider-manager.js +74 -0
  160. package/dist/src/matchmaking/provider-manager.js.map +1 -0
  161. package/dist/src/matchmaking/query-handler.d.ts +34 -0
  162. package/dist/src/matchmaking/query-handler.d.ts.map +1 -0
  163. package/dist/src/matchmaking/query-handler.js +62 -0
  164. package/dist/src/matchmaking/query-handler.js.map +1 -0
  165. package/dist/src/matchmaking/query-transport.d.ts +180 -0
  166. package/dist/src/matchmaking/query-transport.d.ts.map +1 -0
  167. package/dist/src/matchmaking/query-transport.js +315 -0
  168. package/dist/src/matchmaking/query-transport.js.map +1 -0
  169. package/dist/src/matchmaking/seeker-manager.d.ts +39 -0
  170. package/dist/src/matchmaking/seeker-manager.d.ts.map +1 -0
  171. package/dist/src/matchmaking/seeker-manager.js +49 -0
  172. package/dist/src/matchmaking/seeker-manager.js.map +1 -0
  173. package/dist/src/matchmaking/seeker-walk-client.d.ts +127 -0
  174. package/dist/src/matchmaking/seeker-walk-client.d.ts.map +1 -0
  175. package/dist/src/matchmaking/seeker-walk-client.js +189 -0
  176. package/dist/src/matchmaking/seeker-walk-client.js.map +1 -0
  177. package/dist/src/matchmaking/traffic-validation.d.ts +128 -0
  178. package/dist/src/matchmaking/traffic-validation.d.ts.map +1 -0
  179. package/dist/src/matchmaking/traffic-validation.js +105 -0
  180. package/dist/src/matchmaking/traffic-validation.js.map +1 -0
  181. package/dist/src/network/network-manager-service.d.ts +4 -2
  182. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  183. package/dist/src/network/network-manager-service.js +9 -3
  184. package/dist/src/network/network-manager-service.js.map +1 -1
  185. package/dist/src/protocol-client.d.ts +13 -0
  186. package/dist/src/protocol-client.d.ts.map +1 -1
  187. package/dist/src/protocol-client.js +74 -2
  188. package/dist/src/protocol-client.js.map +1 -1
  189. package/dist/src/reactivity/forwarder-host.d.ts +198 -0
  190. package/dist/src/reactivity/forwarder-host.d.ts.map +1 -0
  191. package/dist/src/reactivity/forwarder-host.js +355 -0
  192. package/dist/src/reactivity/forwarder-host.js.map +1 -0
  193. package/dist/src/reactivity/index.d.ts +19 -0
  194. package/dist/src/reactivity/index.d.ts.map +1 -0
  195. package/dist/src/reactivity/index.js +19 -0
  196. package/dist/src/reactivity/index.js.map +1 -0
  197. package/dist/src/reactivity/notify-transport.d.ts +70 -0
  198. package/dist/src/reactivity/notify-transport.d.ts.map +1 -0
  199. package/dist/src/reactivity/notify-transport.js +111 -0
  200. package/dist/src/reactivity/notify-transport.js.map +1 -0
  201. package/dist/src/reactivity/origination-manager.d.ts +110 -0
  202. package/dist/src/reactivity/origination-manager.d.ts.map +1 -0
  203. package/dist/src/reactivity/origination-manager.js +129 -0
  204. package/dist/src/reactivity/origination-manager.js.map +1 -0
  205. package/dist/src/reactivity/protocols.d.ts +43 -0
  206. package/dist/src/reactivity/protocols.d.ts.map +1 -0
  207. package/dist/src/reactivity/protocols.js +50 -0
  208. package/dist/src/reactivity/protocols.js.map +1 -0
  209. package/dist/src/reactivity/push-state-gossip.d.ts +132 -0
  210. package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -0
  211. package/dist/src/reactivity/push-state-gossip.js +231 -0
  212. package/dist/src/reactivity/push-state-gossip.js.map +1 -0
  213. package/dist/src/reactivity/recover-transport.d.ts +167 -0
  214. package/dist/src/reactivity/recover-transport.d.ts.map +1 -0
  215. package/dist/src/reactivity/recover-transport.js +308 -0
  216. package/dist/src/reactivity/recover-transport.js.map +1 -0
  217. package/dist/src/reactivity/rotation-rereg-scheduler.d.ts +142 -0
  218. package/dist/src/reactivity/rotation-rereg-scheduler.d.ts.map +1 -0
  219. package/dist/src/reactivity/rotation-rereg-scheduler.js +223 -0
  220. package/dist/src/reactivity/rotation-rereg-scheduler.js.map +1 -0
  221. package/dist/src/reactivity/subscriber-registry.d.ts +47 -0
  222. package/dist/src/reactivity/subscriber-registry.d.ts.map +1 -0
  223. package/dist/src/reactivity/subscriber-registry.js +87 -0
  224. package/dist/src/reactivity/subscriber-registry.js.map +1 -0
  225. package/dist/src/reactivity/subscription-manager.d.ts +221 -0
  226. package/dist/src/reactivity/subscription-manager.d.ts.map +1 -0
  227. package/dist/src/reactivity/subscription-manager.js +302 -0
  228. package/dist/src/reactivity/subscription-manager.js.map +1 -0
  229. package/dist/src/reactivity/topic-bytes.d.ts +32 -0
  230. package/dist/src/reactivity/topic-bytes.d.ts.map +1 -0
  231. package/dist/src/reactivity/topic-bytes.js +34 -0
  232. package/dist/src/reactivity/topic-bytes.js.map +1 -0
  233. package/dist/src/repo/client.d.ts.map +1 -1
  234. package/dist/src/repo/client.js +51 -23
  235. package/dist/src/repo/client.js.map +1 -1
  236. package/dist/src/repo/service.d.ts +46 -1
  237. package/dist/src/repo/service.d.ts.map +1 -1
  238. package/dist/src/repo/service.js +79 -45
  239. package/dist/src/repo/service.js.map +1 -1
  240. package/dist/src/rpc-deadline.d.ts +37 -0
  241. package/dist/src/rpc-deadline.d.ts.map +1 -0
  242. package/dist/src/rpc-deadline.js +31 -0
  243. package/dist/src/rpc-deadline.js.map +1 -0
  244. package/dist/src/storage/block-storage.d.ts +2 -0
  245. package/dist/src/storage/block-storage.d.ts.map +1 -1
  246. package/dist/src/storage/block-storage.js +95 -2
  247. package/dist/src/storage/block-storage.js.map +1 -1
  248. package/dist/src/storage/i-block-storage.d.ts +25 -0
  249. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  250. package/dist/src/storage/storage-repo.d.ts +53 -2
  251. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  252. package/dist/src/storage/storage-repo.js +193 -9
  253. package/dist/src/storage/storage-repo.js.map +1 -1
  254. package/dist/src/sync/client.d.ts +4 -1
  255. package/dist/src/sync/client.d.ts.map +1 -1
  256. package/dist/src/sync/client.js +5 -2
  257. package/dist/src/sync/client.js.map +1 -1
  258. package/dist/src/testing/cohort-topic-mesh-harness.d.ts +219 -0
  259. package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -0
  260. package/dist/src/testing/cohort-topic-mesh-harness.js +467 -0
  261. package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -0
  262. package/dist/src/testing/matchmaking-mesh-harness.d.ts +187 -0
  263. package/dist/src/testing/matchmaking-mesh-harness.d.ts.map +1 -0
  264. package/dist/src/testing/matchmaking-mesh-harness.js +358 -0
  265. package/dist/src/testing/matchmaking-mesh-harness.js.map +1 -0
  266. package/dist/src/testing/mesh-harness.d.ts.map +1 -1
  267. package/dist/src/testing/mesh-harness.js +23 -1
  268. package/dist/src/testing/mesh-harness.js.map +1 -1
  269. package/dist/src/testing/reactivity-mesh-harness.d.ts +298 -0
  270. package/dist/src/testing/reactivity-mesh-harness.d.ts.map +1 -0
  271. package/dist/src/testing/reactivity-mesh-harness.js +663 -0
  272. package/dist/src/testing/reactivity-mesh-harness.js.map +1 -0
  273. package/package.json +7 -4
  274. package/src/cluster/block-transfer-service.ts +133 -68
  275. package/src/cluster/client.ts +64 -45
  276. package/src/cluster/cluster-error.ts +64 -0
  277. package/src/cluster/cluster-repo.ts +405 -55
  278. package/src/cluster/commit-cert.ts +139 -0
  279. package/src/cluster/service.ts +116 -44
  280. package/src/cluster/spread-on-churn.ts +328 -285
  281. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -0
  282. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -0
  283. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -0
  284. package/src/cohort-topic/change-bridge.ts +109 -0
  285. package/src/cohort-topic/cohort-gossip-driver.ts +144 -0
  286. package/src/cohort-topic/cohort-gossip-transport.ts +74 -0
  287. package/src/cohort-topic/fret-trust-anchor.ts +153 -0
  288. package/src/cohort-topic/host.ts +2175 -0
  289. package/src/cohort-topic/index.ts +13 -0
  290. package/src/cohort-topic/membership-publish-sink.ts +20 -0
  291. package/src/cohort-topic/membership-source.ts +68 -0
  292. package/src/cohort-topic/peer-codec.ts +31 -0
  293. package/src/cohort-topic/peer-sig.ts +86 -0
  294. package/src/cohort-topic/protocols.ts +71 -0
  295. package/src/cohort-topic/reactivity-membership-gate.ts +77 -0
  296. package/src/cohort-topic/size-estimator.ts +16 -0
  297. package/src/cohort-topic/stream-util.ts +87 -0
  298. package/src/cohort-topic/threshold-crypto.ts +239 -0
  299. package/src/cohort-topic/topic-router.ts +77 -0
  300. package/src/dispute/cascade.ts +517 -0
  301. package/src/dispute/client.ts +12 -2
  302. package/src/dispute/dispute-service.ts +171 -24
  303. package/src/dispute/index.ts +42 -0
  304. package/src/dispute/invalidation.ts +587 -0
  305. package/src/dispute/types.ts +14 -0
  306. package/src/index.ts +4 -0
  307. package/src/libp2p-key-network.ts +49 -6
  308. package/src/libp2p-node-base.ts +620 -12
  309. package/src/matchmaking/aggregate-counts.ts +104 -0
  310. package/src/matchmaking/index.ts +20 -0
  311. package/src/matchmaking/module.ts +363 -0
  312. package/src/matchmaking/protocols.ts +51 -0
  313. package/src/matchmaking/provider-manager.ts +95 -0
  314. package/src/matchmaking/query-handler.ts +88 -0
  315. package/src/matchmaking/query-transport.ts +483 -0
  316. package/src/matchmaking/seeker-manager.ts +64 -0
  317. package/src/matchmaking/seeker-walk-client.ts +293 -0
  318. package/src/matchmaking/traffic-validation.ts +195 -0
  319. package/src/network/network-manager-service.ts +9 -2
  320. package/src/protocol-client.ts +64 -3
  321. package/src/reactivity/forwarder-host.ts +438 -0
  322. package/src/reactivity/index.ts +19 -0
  323. package/src/reactivity/notify-transport.ts +144 -0
  324. package/src/reactivity/origination-manager.ts +192 -0
  325. package/src/reactivity/protocols.ts +61 -0
  326. package/src/reactivity/push-state-gossip.ts +291 -0
  327. package/src/reactivity/recover-transport.ts +408 -0
  328. package/src/reactivity/rotation-rereg-scheduler.ts +256 -0
  329. package/src/reactivity/subscriber-registry.ts +96 -0
  330. package/src/reactivity/subscription-manager.ts +450 -0
  331. package/src/reactivity/topic-bytes.ts +37 -0
  332. package/src/repo/client.ts +54 -23
  333. package/src/repo/service.ts +90 -42
  334. package/src/rpc-deadline.ts +45 -0
  335. package/src/storage/block-storage.ts +106 -3
  336. package/src/storage/i-block-storage.ts +27 -0
  337. package/src/storage/storage-repo.ts +209 -13
  338. package/src/sync/client.ts +5 -2
  339. package/src/testing/cohort-topic-mesh-harness.ts +613 -0
  340. package/src/testing/matchmaking-mesh-harness.ts +475 -0
  341. package/src/testing/mesh-harness.ts +22 -2
  342. package/src/testing/reactivity-mesh-harness.ts +922 -0
@@ -0,0 +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
+ }
@@ -0,0 +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";
@@ -0,0 +1,363 @@
1
+ /**
2
+ * Matchmaking — cohesive public module (db-p2p, the cohort-topic-integrated entry points).
3
+ *
4
+ * `docs/matchmaking.md` §Overview / §Configuration names two public roles. This module composes the
5
+ * lower db-core state + db-p2p substrate wiring (managers, seeker walk client, multi-cohort sweep) into
6
+ * those roles, plus the `QuorumDiscovery` binding the voting plan consumes:
7
+ *
8
+ * - {@link MatchmakingProviderSession} — `register(topic, payload)` / `renew()` / `withdraw()` (plus
9
+ * `setCapacity` / `signalFull` self-throttling), driving a {@link MatchmakingProvider} through the
10
+ * {@link MatchmakingProviderManager} at cohort-topic tier **T2**.
11
+ * - {@link MatchmakingSeekerSession} — `register(topic, payload)` (brief T2 registration), `query(q)`
12
+ * (one-shot cohort query), and `walk(topic, want)` (the hang-out engine, escalating to the
13
+ * multi-cohort sweep on a hot topic).
14
+ * - {@link createMatchmakingQuorumDiscovery} — binds the db-core voting `QuorumDiscovery` port to the
15
+ * seeker walk (single-cohort) + {@link runMultiCohortSweep} (sweep), so `VotingQuorumAssembler` runs
16
+ * over real substrate I/O.
17
+ *
18
+ * **Naming note (honest):** the db-core `MatchmakingProvider` / `MatchmakingSeeker` are the
19
+ * transport-free *state + signed-payload builders*; these `*Session` classes are the substrate-wired
20
+ * *public entry points* the doc's `MatchmakingProvider` / `MatchmakingSeeker` sketch refers to. They are
21
+ * named `*Session` to avoid colliding with the re-exported db-core state classes.
22
+ *
23
+ * The substrate I/O is **injected** — the seeker walk transport, the one-shot query, the `d_max`
24
+ * estimate, and the sweep ports are all ports the FRET host binds. The module therefore unit-tests
25
+ * without a live libp2p stack; the mock-tier e2e that drives these against a real promoted tree is a
26
+ * documented follow-on (the same posture as the rest of the subsystem).
27
+ */
28
+
29
+ import {
30
+ MatchmakingProvider,
31
+ MatchmakingSeeker,
32
+ createMatchTopicAnchor,
33
+ runMultiCohortSweep,
34
+ type MatchTopicAnchor,
35
+ type MatchTopicKind,
36
+ type CohortTopicService,
37
+ type NodeProfile,
38
+ type CapabilityFilter,
39
+ type EntrySigVerifier,
40
+ type HangOutConfig,
41
+ type ProviderEntryV1,
42
+ type QueryReplyV1,
43
+ type QueryV1,
44
+ type MultiCohortSweepPorts,
45
+ type QuorumDiscovery,
46
+ type QuorumDiscoveryRequest,
47
+ type QuorumDiscoverySlice,
48
+ } from "@optimystic/db-core";
49
+ import { MatchmakingProviderManager } from "./provider-manager.js";
50
+ import { MatchmakingSeekerManager } from "./seeker-manager.js";
51
+ import { SeekerWalkClient, type SeekerWalkTransport } from "./seeker-walk-client.js";
52
+
53
+ /** A matchmaking topic reference: `(kind, label)` resolved to `topicId = H(kind ‖ label ‖ "match")`. */
54
+ export interface MatchTopicRef {
55
+ readonly kind: MatchTopicKind;
56
+ readonly label: string;
57
+ }
58
+
59
+ // --- provider session ----------------------------------------------------------------------------
60
+
61
+ /** The provider registration inputs a {@link MatchmakingProviderSession.register} call carries. */
62
+ export interface ProviderRegistrationInput {
63
+ readonly capabilities: readonly string[];
64
+ readonly capacityBudget: number;
65
+ readonly contactHint: string;
66
+ readonly serviceUntil?: number;
67
+ }
68
+
69
+ /** Construction inputs for a {@link MatchmakingProviderSession}. */
70
+ export interface MatchmakingProviderSessionDeps {
71
+ /** Participant-facing cohort-topic substrate API. */
72
+ readonly service: CohortTopicService;
73
+ /** Sign the provider registration image (db-p2p supplies the libp2p peer key). */
74
+ readonly sign: (payload: Uint8Array) => Promise<string>;
75
+ /** Topic anchor; defaults to db-core's ring-hash anchor. */
76
+ readonly anchor?: MatchTopicAnchor;
77
+ /** Node profile used to derive the provider TTL (Core 90 s / Edge 60 s). */
78
+ readonly profile?: NodeProfile;
79
+ /** Explicit provider TTL (ms); overrides the profile-derived TTL. */
80
+ readonly ttlMs?: number;
81
+ /** CSPRNG source for the provider correlation id (injectable for deterministic tests). */
82
+ readonly randomBytes?: (n: number) => Uint8Array;
83
+ }
84
+
85
+ /**
86
+ * Public provider entry point: registers a {@link MatchmakingProvider} at a topic (cohort-topic T2) and
87
+ * drives its lifecycle. One session owns one live registration; calling {@link register} again
88
+ * re-registers (e.g. at a different topic).
89
+ */
90
+ export class MatchmakingProviderSession {
91
+ private readonly deps: MatchmakingProviderSessionDeps;
92
+ private readonly anchor: MatchTopicAnchor;
93
+ private manager?: MatchmakingProviderManager;
94
+
95
+ constructor(deps: MatchmakingProviderSessionDeps) {
96
+ this.deps = deps;
97
+ this.anchor = deps.anchor ?? createMatchTopicAnchor();
98
+ }
99
+
100
+ /** The live registration handle, or `undefined` before the first {@link register}. */
101
+ get registration(): MatchmakingProviderManager["registration"] {
102
+ return this.manager?.registration;
103
+ }
104
+
105
+ /** Register the provider at `topic` with the signed payload, at cohort-topic tier T2. */
106
+ async register(topic: MatchTopicRef, reg: ProviderRegistrationInput): Promise<void> {
107
+ const topicId = this.anchor.topicId(topic.kind, topic.label);
108
+ const provider = new MatchmakingProvider({
109
+ topicId,
110
+ capabilities: reg.capabilities,
111
+ capacityBudget: reg.capacityBudget,
112
+ contactHint: reg.contactHint,
113
+ sign: this.deps.sign,
114
+ ...(reg.serviceUntil !== undefined ? { serviceUntil: reg.serviceUntil } : {}),
115
+ ...(this.deps.randomBytes !== undefined ? { randomBytes: this.deps.randomBytes } : {}),
116
+ });
117
+ this.manager = new MatchmakingProviderManager({
118
+ service: this.deps.service,
119
+ provider,
120
+ ...(this.deps.ttlMs !== undefined ? { ttlMs: this.deps.ttlMs } : {}),
121
+ ...(this.deps.profile !== undefined ? { profile: this.deps.profile } : {}),
122
+ });
123
+ await this.manager.register();
124
+ }
125
+
126
+ /** Run one renewal cycle (keep-alive). No-op before {@link register}. */
127
+ async renew(): Promise<void> {
128
+ await this.manager?.renew();
129
+ }
130
+
131
+ /** Update the live capacity budget and push it by re-registering (`RenewV1` cannot carry a payload). */
132
+ async setCapacity(budget: number): Promise<void> {
133
+ await this.requireManager().setCapacity(budget);
134
+ }
135
+
136
+ /** Signal "available but at capacity" (`capacityBudget = 0`) by re-registering. */
137
+ async signalFull(): Promise<void> {
138
+ await this.requireManager().signalFull();
139
+ }
140
+
141
+ /** Withdraw: sends a best-effort signed `RenewV1.withdraw` tombstone so the cohort frees the record
142
+ * immediately (an optimization; the record otherwise TTL-expires). No-op before register. */
143
+ async withdraw(): Promise<void> {
144
+ await this.manager?.withdraw();
145
+ }
146
+
147
+ private requireManager(): MatchmakingProviderManager {
148
+ if (this.manager === undefined) {
149
+ throw new Error("MatchmakingProviderSession: register() must precede setCapacity()/signalFull()");
150
+ }
151
+ return this.manager;
152
+ }
153
+ }
154
+
155
+ // --- seeker session ------------------------------------------------------------------------------
156
+
157
+ /** The seeker registration inputs a {@link MatchmakingSeekerSession.register} call carries. */
158
+ export interface SeekerRegistrationInput {
159
+ readonly wantCount: number;
160
+ readonly contactHint: string;
161
+ readonly filter?: CapabilityFilter;
162
+ readonly pushOnArrival?: boolean;
163
+ }
164
+
165
+ /** A {@link MatchmakingSeekerSession.walk} request — the hang-out engine's per-task knobs. */
166
+ export interface SeekerWalkRequest {
167
+ readonly wantCount: number;
168
+ readonly patienceMs: number;
169
+ readonly filter?: CapabilityFilter;
170
+ /** Force the multi-cohort sweep even when the single-cohort walk would suffice (representativeness). */
171
+ readonly preferSweep?: boolean;
172
+ }
173
+
174
+ /** The injected substrate-I/O seam a seeker session drives. The FRET host binds these to libp2p RPCs. */
175
+ export interface MatchmakingSeekerSessionDeps {
176
+ /** Participant-facing cohort-topic substrate API (seeker registration). */
177
+ readonly service: CohortTopicService;
178
+ /** Sign the seeker registration image. */
179
+ readonly sign: (payload: Uint8Array) => Promise<string>;
180
+ /** Per-entry signature verifier (db-p2p binds `verifyPeerSig`). */
181
+ readonly verifyEntry: EntrySigVerifier;
182
+ /** Topic anchor; defaults to db-core's ring-hash anchor. */
183
+ readonly anchor?: MatchTopicAnchor;
184
+ /** Build the seeker walk transport (register/query/renew/withdraw at a tier) for a topic. */
185
+ readonly walkTransport: (topicId: Uint8Array) => SeekerWalkTransport;
186
+ /** Issue a one-shot `QueryV1` (resolves the cohort from `q.topicId`). */
187
+ readonly queryCohort: (q: QueryV1) => Promise<QueryReplyV1>;
188
+ /** Estimate `d_max` for a topic (the size-estimator seam). */
189
+ readonly estimateDMax: (topicId: Uint8Array) => Promise<number>;
190
+ /** Build the multi-cohort sweep ports for a topic; absent ⇒ the sweep is unavailable (walk only). */
191
+ readonly sweepPorts?: (topicId: Uint8Array) => MultiCohortSweepPorts;
192
+ /** Seeker registration TTL (ms); default {@link MatchmakingSeekerManager}'s seeker TTL. */
193
+ readonly ttlMs?: number;
194
+ /** Hang-out decision config (default {@link import("@optimystic/db-core").DEFAULT_HANG_OUT_CONFIG}). */
195
+ readonly config?: HangOutConfig;
196
+ /** Assumed competing-seeker mean `wantCount`. */
197
+ readonly meanWantCount?: number;
198
+ /** Wall clock (unix ms); injectable for tests. */
199
+ readonly clock?: () => number;
200
+ /** Sleep for the requery cadence; injectable for tests. */
201
+ readonly sleep?: (ms: number) => Promise<void>;
202
+ }
203
+
204
+ /**
205
+ * Public seeker entry point: brief registration, one-shot query, and the hang-out `walk` (which escalates
206
+ * to the multi-cohort sweep on a hot topic). One session may register and walk independently.
207
+ */
208
+ export class MatchmakingSeekerSession {
209
+ private readonly deps: MatchmakingSeekerSessionDeps;
210
+ private readonly anchor: MatchTopicAnchor;
211
+ private manager?: MatchmakingSeekerManager;
212
+
213
+ constructor(deps: MatchmakingSeekerSessionDeps) {
214
+ this.deps = deps;
215
+ this.anchor = deps.anchor ?? createMatchTopicAnchor();
216
+ }
217
+
218
+ /** Resolve the `topicId` for a topic ref. */
219
+ topicIdFor(topic: MatchTopicRef): Uint8Array {
220
+ return this.anchor.topicId(topic.kind, topic.label);
221
+ }
222
+
223
+ /** Register the seeker briefly at `topic` (cohort-topic T2, short TTL). */
224
+ async register(topic: MatchTopicRef, reg: SeekerRegistrationInput): Promise<void> {
225
+ const topicId = this.topicIdFor(topic);
226
+ const seeker = new MatchmakingSeeker({
227
+ topicId,
228
+ wantCount: reg.wantCount,
229
+ contactHint: reg.contactHint,
230
+ sign: this.deps.sign,
231
+ ...(reg.filter !== undefined ? { filter: reg.filter } : {}),
232
+ ...(reg.pushOnArrival !== undefined ? { pushOnArrival: reg.pushOnArrival } : {}),
233
+ });
234
+ this.manager = new MatchmakingSeekerManager({
235
+ service: this.deps.service,
236
+ seeker,
237
+ ...(this.deps.ttlMs !== undefined ? { ttlMs: this.deps.ttlMs } : {}),
238
+ });
239
+ await this.manager.register();
240
+ }
241
+
242
+ /** Drop the seeker registration: sends a best-effort tombstone so the cohort frees the record
243
+ * immediately (the record otherwise TTL-expires). No-op before register. */
244
+ async withdraw(): Promise<void> {
245
+ await this.manager?.withdraw();
246
+ }
247
+
248
+ /** Issue a one-shot `QueryV1` against the topic's cohort (resolved from `q.topicId`). */
249
+ async query(q: QueryV1): Promise<QueryReplyV1> {
250
+ return this.deps.queryCohort(q);
251
+ }
252
+
253
+ /**
254
+ * Run the hang-out walk for `topic`, then — on a hot topic (`childCohortCount > 0`) or when
255
+ * `preferSweep` is set, and when the sweep ports are bound — escalate to the multi-cohort sweep and
256
+ * union the deduped providers. Returns the assembled (possibly `< wantCount`) provider set.
257
+ */
258
+ async walk(topic: MatchTopicRef, want: SeekerWalkRequest): Promise<ProviderEntryV1[]> {
259
+ const topicId = this.topicIdFor(topic);
260
+ const dMax = await this.deps.estimateDMax(topicId);
261
+ const client = new SeekerWalkClient({
262
+ transport: this.deps.walkTransport(topicId),
263
+ topicId,
264
+ wantCount: want.wantCount,
265
+ dMax,
266
+ patienceMs: want.patienceMs,
267
+ verifyEntry: this.deps.verifyEntry,
268
+ ...(want.filter !== undefined ? { filter: want.filter } : {}),
269
+ ...(this.deps.config !== undefined ? { config: this.deps.config } : {}),
270
+ ...(this.deps.meanWantCount !== undefined ? { meanWantCount: this.deps.meanWantCount } : {}),
271
+ ...(this.deps.clock !== undefined ? { clock: this.deps.clock } : {}),
272
+ ...(this.deps.sleep !== undefined ? { sleep: this.deps.sleep } : {}),
273
+ });
274
+ const result = await client.run();
275
+ const providers = new Map<string, ProviderEntryV1>(result.providers.map((p) => [p.participantId, p]));
276
+
277
+ const hot = result.maxChildCohortCount > 0;
278
+ const wantSweep = want.preferSweep === true || (hot && providers.size < want.wantCount);
279
+ if (wantSweep && this.deps.sweepPorts !== undefined) {
280
+ // Pass want.patienceMs as a fresh (coarser) bound — this path does not track a draining
281
+ // remainder (SeekerWalkClient ran on its own deadline). Still strictly better than unbounded.
282
+ const sweep = await runMultiCohortSweep(this.deps.sweepPorts(topicId), {
283
+ topicId,
284
+ wantCount: want.wantCount,
285
+ verifyEntry: this.deps.verifyEntry,
286
+ patienceMs: want.patienceMs,
287
+ ...(want.filter !== undefined ? { filter: want.filter } : {}),
288
+ ...(this.deps.clock !== undefined ? { clock: this.deps.clock } : {}),
289
+ });
290
+ for (const entry of sweep.providers) {
291
+ providers.set(entry.participantId, entry);
292
+ }
293
+ }
294
+ return [...providers.values()];
295
+ }
296
+ }
297
+
298
+ // --- voting quorum-discovery binding -------------------------------------------------------------
299
+
300
+ /** Construction inputs for {@link createMatchmakingQuorumDiscovery}. */
301
+ export interface MatchmakingQuorumDiscoveryDeps {
302
+ /** Per-entry signature verifier (db-p2p binds `verifyPeerSig`). */
303
+ readonly verifyEntry: EntrySigVerifier;
304
+ /** Build the seeker walk transport for a topic. */
305
+ readonly walkTransport: (topicId: Uint8Array) => SeekerWalkTransport;
306
+ /** Estimate `d_max` for a topic. */
307
+ readonly estimateDMax: (topicId: Uint8Array) => Promise<number>;
308
+ /** Build the multi-cohort sweep ports for a topic. */
309
+ readonly sweepPorts: (topicId: Uint8Array) => MultiCohortSweepPorts;
310
+ /** Optional capability filter applied to both the walk and the sweep. */
311
+ readonly filter?: CapabilityFilter;
312
+ /** Hang-out decision config (passed to the walk). */
313
+ readonly config?: HangOutConfig;
314
+ /** Assumed competing-seeker mean `wantCount` (passed to the walk). */
315
+ readonly meanWantCount?: number;
316
+ /** Wall clock (unix ms); injectable for tests. */
317
+ readonly clock?: () => number;
318
+ /** Sleep for the requery cadence; injectable for tests. */
319
+ readonly sleep?: (ms: number) => Promise<void>;
320
+ }
321
+
322
+ /**
323
+ * Bind the db-core voting {@link QuorumDiscovery} port (`docs/matchmaking.md` §Voting-quorum assembly)
324
+ * to the matchmaking substrate: `walk` runs the single-cohort hang-out walk and surfaces its hotness
325
+ * signal (`childCohortCount`); `sweep` runs {@link runMultiCohortSweep} across the high-population tier
326
+ * shards. `VotingQuorumAssembler` composes the two — walk, then sweep on a hot topic — re-validating
327
+ * every entry itself, so this binding stays a thin transport adapter.
328
+ */
329
+ export function createMatchmakingQuorumDiscovery(deps: MatchmakingQuorumDiscoveryDeps): QuorumDiscovery {
330
+ return {
331
+ async walk(req: QuorumDiscoveryRequest): Promise<QuorumDiscoverySlice> {
332
+ const dMax = await deps.estimateDMax(req.topicId);
333
+ const client = new SeekerWalkClient({
334
+ transport: deps.walkTransport(req.topicId),
335
+ topicId: req.topicId,
336
+ wantCount: req.wantCount,
337
+ dMax,
338
+ patienceMs: req.patienceMs,
339
+ verifyEntry: deps.verifyEntry,
340
+ ...(deps.filter !== undefined ? { filter: deps.filter } : {}),
341
+ ...(deps.config !== undefined ? { config: deps.config } : {}),
342
+ ...(deps.meanWantCount !== undefined ? { meanWantCount: deps.meanWantCount } : {}),
343
+ ...(deps.clock !== undefined ? { clock: deps.clock } : {}),
344
+ ...(deps.sleep !== undefined ? { sleep: deps.sleep } : {}),
345
+ });
346
+ const result = await client.run();
347
+ return { entries: result.providers, childCohortCount: result.maxChildCohortCount };
348
+ },
349
+ async sweep(req: QuorumDiscoveryRequest): Promise<QuorumDiscoverySlice> {
350
+ const result = await runMultiCohortSweep(deps.sweepPorts(req.topicId), {
351
+ topicId: req.topicId,
352
+ wantCount: req.wantCount,
353
+ verifyEntry: deps.verifyEntry,
354
+ patienceMs: req.patienceMs,
355
+ ...(deps.filter !== undefined ? { filter: deps.filter } : {}),
356
+ ...(deps.clock !== undefined ? { clock: deps.clock } : {}),
357
+ });
358
+ // The sweep hop has already crossed the ring — its childCohortCount is moot (the escalation
359
+ // decision is made). Report 0 so the assembler never double-escalates.
360
+ return { entries: result.providers, childCohortCount: 0 };
361
+ },
362
+ };
363
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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;