@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
@@ -3,17 +3,21 @@ import { noise } from '@chainsafe/libp2p-noise';
3
3
  import { yamux } from '@chainsafe/libp2p-yamux';
4
4
  import { identify } from '@libp2p/identify';
5
5
  import { ping } from '@libp2p/ping';
6
+ import { dcutr } from '@libp2p/dcutr';
7
+ import { autoNAT } from '@libp2p/autonat';
6
8
  import { gossipsub } from '@chainsafe/libp2p-gossipsub';
7
9
  import { bootstrap } from '@libp2p/bootstrap';
8
10
  import { circuitRelayServer } from '@libp2p/circuit-relay-v2';
9
11
  import { peerIdFromString } from '@libp2p/peer-id';
10
12
  import { generateKeyPair } from '@libp2p/crypto/keys';
11
13
  import { clusterService } from './cluster/service.js';
14
+ import { blockTransferService } from './cluster/block-transfer-service.js';
12
15
  import { repoService } from './repo/service.js';
13
16
  import { StorageRepo } from './storage/storage-repo.js';
14
17
  import { BlockStorage } from './storage/block-storage.js';
15
18
  import { MemoryRawStorage } from './storage/memory-storage.js';
16
19
  import { clusterMember } from './cluster/cluster-repo.js';
20
+ import { createCommitCertStore, makeClusterCommitCertExtractor } from './cluster/commit-cert.js';
17
21
  import { coordinatorRepo } from './repo/coordinator-repo.js';
18
22
  import { Libp2pKeyPeerNetwork } from './libp2p-key-network.js';
19
23
  import { ClusterClient } from './cluster/client.js';
@@ -25,16 +29,65 @@ import { RestorationCoordinator } from './storage/restoration-coordinator-v2.js'
25
29
  import { RingSelector } from './storage/ring-selector.js';
26
30
  import { StorageMonitor } from './storage/storage-monitor.js';
27
31
  import { ArachnodeFretAdapter } from './storage/arachnode-fret-adapter.js';
32
+ import { createCohortTopicHost } from './cohort-topic/host.js';
33
+ import { attachCohortChangeBridge } from './cohort-topic/change-bridge.js';
34
+ import { createReactivitySelfMembershipGate, reactivityTailBytes } from './cohort-topic/reactivity-membership-gate.js';
35
+ import { Libp2pReactivityNotifyTransport, registerNotifyHandler } from './reactivity/notify-transport.js';
36
+ import { Libp2pReactivityRecoverTransport, createLibp2pRecoverDialer, registerRecoverHandler, createRecoverRequestSigners, } from './reactivity/recover-transport.js';
37
+ import { ReactivityForwarderHost, reactivityDirectSubscribers, reactivityNotificationTopicId } from './reactivity/forwarder-host.js';
38
+ import { ReactivityOriginationManager } from './reactivity/origination-manager.js';
39
+ import { ReactivityPushStateGossipDriver, registerPushStateGossipHandler } from './reactivity/push-state-gossip.js';
40
+ import { RotationReRegistrationScheduler } from './reactivity/rotation-rereg-scheduler.js';
41
+ import { ReactivitySubscriberRegistry } from './reactivity/subscriber-registry.js';
42
+ import { DEFAULT_REACTIVITY_PROTOCOLS, reactivityProtocolList } from './reactivity/protocols.js';
43
+ import { registerMatchmakingQueryHandler } from './matchmaking/query-transport.js';
44
+ import { DEFAULT_MATCHMAKING_PROTOCOLS, matchmakingProtocolList } from './matchmaking/protocols.js';
45
+ import { signPeer } from './cohort-topic/peer-sig.js';
46
+ import { createNotificationVerifier, createCorrelationReplayGuard, createStickyCohortHintCache, reactivityNodePolicy, createTierAddressing, createRingHash, Tier, b64urlToBytes, bytesToB64url, } from '@optimystic/db-core';
28
47
  import { PartitionDetector } from './cluster/partition-detector.js';
48
+ import { createLogger } from './logger.js';
29
49
  import { PeerReputationService } from './reputation/peer-reputation.js';
30
50
  import { DisputeService } from './dispute/dispute-service.js';
31
51
  import { DisputeClient } from './dispute/client.js';
52
+ /** Logger for the reactivity node-wiring (origination/forwarder/recover/rotation composition). */
53
+ const reactivityWiringLog = createLogger('reactivity-node-wiring');
32
54
  function resolveStorage(provider) {
33
55
  if (!provider) {
34
56
  return new MemoryRawStorage();
35
57
  }
36
58
  return typeof provider === 'function' ? provider() : provider;
37
59
  }
60
+ /**
61
+ * Resolve the full FRET engine the cohort-topic host needs.
62
+ *
63
+ * `createCohortTopicHost` consumes the complete {@link FretService} engine surface — notably
64
+ * `setActivityHandler` (and `routeAct`, size estimation, …). The value at `node.services.fret` is the
65
+ * libp2p `Libp2pFretService` *wrapper*, which re-exports only a subset (`assembleCohort`, `routeAct`, …)
66
+ * and keeps the real engine private behind its lazy `ensure()` accessor. By the time activation runs the
67
+ * engine is already initialized — the wrapper's `Startable.start()` ran during `node.start()` — and the
68
+ * engine and wrapper share one underlying routing store, so the host and the membership gate observe the
69
+ * same cohort state. Returns the engine when reachable; otherwise the value as-is (a test may inject a
70
+ * raw engine that needs no unwrapping).
71
+ */
72
+ function resolveFretEngine(fret) {
73
+ if (!fret) {
74
+ return undefined;
75
+ }
76
+ const candidate = fret;
77
+ return typeof candidate.ensure === 'function' ? candidate.ensure() : fret;
78
+ }
79
+ /**
80
+ * The raw topic id bytes of a collection's current served reactivity {@link PushState}, or `undefined` if the
81
+ * node serves none. The forwarder host keys its served map by topicId, but a **backfill** recover request
82
+ * carries only a collectionId — so the drain-redirect binding resolves the collection's current tail topic
83
+ * here (the highest-`lastRevision` served PushState) before consulting `rotationRedirectFor`. While the old
84
+ * tail is the only served state this resolves it (and its drain gate redirects); once the new tail is served
85
+ * this resolves the new tail (no gate → no redirect), exactly as the recover serve's backfill path intends.
86
+ */
87
+ function resolveCurrentServedTopic(forwarderHost, collectionId) {
88
+ const ps = forwarderHost.pushStateForCollection(collectionId);
89
+ return ps === undefined ? undefined : b64urlToBytes(ps.topicId);
90
+ }
38
91
  export async function createLibp2pNodeBase(options, defaults) {
39
92
  const rawStorage = resolveStorage(options.storage);
40
93
  // Create placeholder restore callback (will be replaced after node starts)
@@ -74,6 +127,22 @@ export async function createLibp2pNodeBase(options, defaults) {
74
127
  const nodePrivateKey = options.privateKey ?? await generateKeyPair('Ed25519');
75
128
  const listenAddrs = options.listenAddrs ?? defaults.listenAddrs;
76
129
  const transports = options.transports ?? defaults.transports;
130
+ // --- cohort-topic substrate activation (opt-in; default off → today's bare behavior, zero cost) ---
131
+ const cohortEnabled = options.cohortTopic?.enabled === true;
132
+ // Resolve wantK ONCE so the post-assembly host serves and the membership gate checks the SAME cohort.
133
+ const cohortWantK = options.cohortTopic?.wantK ?? 16;
134
+ // When enabled, the cluster member records the consensus commit cert into this store synchronously,
135
+ // BEFORE `storageRepo.commit` emits the change event the bridge's extractor resolves it from (see
136
+ // cluster-repo.ts §applyConsensusOperation). Created early because the sink must be passed into
137
+ // `clusterMember(...)` below. Composed with any caller-supplied `onCommitCertificate` so both fire.
138
+ const certStore = cohortEnabled ? createCommitCertStore() : undefined;
139
+ const onCommitCertificate = certStore
140
+ // `certStore.put` runs FIRST so origination's cert capture cannot be defeated by a throwing caller
141
+ // sink: the whole composed call is isolated in `ClusterMember.captureCommitCert`, so a caller sink
142
+ // that threw before the store was written would make that commit silently never originate. Ordering
143
+ // the store first keeps origination correct regardless of the caller sink (`put` never throws).
144
+ ? (actionId, cert) => { certStore.put(actionId, cert); options.onCommitCertificate?.(actionId, cert); }
145
+ : options.onCommitCertificate;
77
146
  const libp2pOptions = {
78
147
  start: false,
79
148
  privateKey: nodePrivateKey,
@@ -96,6 +165,13 @@ export async function createLibp2pNodeBase(options, defaults) {
96
165
  protocolPrefix: `/optimystic/${options.networkName}`
97
166
  }),
98
167
  ping: ping(),
168
+ // DCUtR (hole-punch) upgrades relayed node↔node connections to direct
169
+ // ones; AutoNAT learns this node's public reachability via peer dial-back.
170
+ // Both are always-on and depend on `identify` above. They are inert where
171
+ // the transport can't hole-punch or dial back (e.g. browser/WS-only), which
172
+ // is acceptable — they neither throw nor break the build in that case.
173
+ dcutr: dcutr(),
174
+ autoNAT: autoNAT(),
99
175
  pubsub: gossipsub({
100
176
  allowPublishToZeroTopicPeers: true,
101
177
  heartbeatInterval: 7000
@@ -106,15 +182,27 @@ export async function createLibp2pNodeBase(options, defaults) {
106
182
  cluster: (components) => {
107
183
  const serviceFactory = clusterService({
108
184
  protocolPrefix: `/optimystic/${options.networkName}`,
109
- configuredClusterSize: options.clusterSize ?? 10,
110
- allowClusterDownsize: options.clusterPolicy?.allowDownsize ?? true,
111
- clusterSizeTolerance: options.clusterPolicy?.sizeTolerance ?? 0.5,
112
185
  responsibilityK: options.responsibilityK ?? 1
113
186
  });
114
187
  return serviceFactory({
115
188
  logger: components.logger,
116
189
  registrar: components.registrar,
117
- cluster: clusterProxy
190
+ cluster: clusterProxy,
191
+ // Identity for membership scoping on the update path. peerId is a core
192
+ // libp2p component, available at service-construction time.
193
+ peerId: components.peerId,
194
+ // Fallback addr resolver for redirect targets whose multiaddrs are not
195
+ // already embedded in record.peers.
196
+ getConnectionAddrs: (peerId) => {
197
+ const conns = components.libp2p?.getConnections?.(peerId) ?? [];
198
+ const addrs = [];
199
+ for (const c of conns) {
200
+ const addr = c.remoteAddr?.toString?.();
201
+ if (addr)
202
+ addrs.push(addr);
203
+ }
204
+ return addrs;
205
+ }
118
206
  });
119
207
  },
120
208
  repo: (components) => {
@@ -122,6 +210,15 @@ export async function createLibp2pNodeBase(options, defaults) {
122
210
  protocolPrefix: `/optimystic/${options.networkName}`,
123
211
  responsibilityK: options.responsibilityK ?? 1
124
212
  });
213
+ // RepoService.checkRedirect needs the running node (network manager for the
214
+ // responsible-set computation, self id for the membership check, connection
215
+ // addrs for redirect targets). The libp2p components.libp2p proxy does NOT
216
+ // reliably resolve from inside a service at request time, so the node is
217
+ // injected explicitly post-construction via setLibp2p(node) below — the same
218
+ // mechanism networkManager/fret use — rather than forwarded here. checkRedirect
219
+ // keys the responsible set on the RAW encoded block id
220
+ // (getCluster(encode(blockKey)) → hashKey(encode(...))), matching the
221
+ // coordinator's findCluster(encode(blockId)) — same cohort, no spurious redirect.
125
222
  return serviceFactory({
126
223
  logger: components.logger,
127
224
  registrar: components.registrar,
@@ -138,6 +235,18 @@ export async function createLibp2pNodeBase(options, defaults) {
138
235
  repo: repoProxy
139
236
  });
140
237
  },
238
+ // Block-transfer protocol handler for churn re-replication. Wired to the
239
+ // *local* storageRepo (not repoProxy): a pushed replica must land in this
240
+ // node's own storage, not be re-routed through the cluster-coordinated repo.
241
+ blockTransfer: (components) => {
242
+ const serviceFactory = blockTransferService({
243
+ protocolPrefix: `/optimystic/${options.networkName}`
244
+ });
245
+ return serviceFactory({
246
+ registrar: components.registrar,
247
+ repo: storageRepo
248
+ });
249
+ },
141
250
  networkManager: (components) => {
142
251
  const svcFactory = networkManagerService({
143
252
  clusterSize: options.clusterSize ?? 10,
@@ -184,6 +293,14 @@ export async function createLibp2pNodeBase(options, defaults) {
184
293
  node.services?.networkManager?.setLibp2p?.(node);
185
294
  }
186
295
  catch { }
296
+ // RepoService.checkRedirect resolves the network manager / self id / connection
297
+ // addrs through this injected node (the components.libp2p proxy is unreliable
298
+ // from inside a service at request time). Done before start() so the protocol
299
+ // handler is live with a resolvable node from its first request.
300
+ try {
301
+ node.services?.repo?.setLibp2p?.(node);
302
+ }
303
+ catch { }
187
304
  await node.start();
188
305
  // Initialize peer reputation service
189
306
  const reputation = new PeerReputationService();
@@ -209,6 +326,63 @@ export async function createLibp2pNodeBase(options, defaults) {
209
326
  clusterSizeTolerance: options.clusterPolicy?.sizeTolerance ?? 0.5,
210
327
  partitionDetectionWindow: 60000
211
328
  };
329
+ // Fetch a block archive from one cohort peer over the sync protocol, bounded by a
330
+ // per-peer timeout so an unreachable peer can't stall reconciliation. Mirrors the
331
+ // SyncClient query in `clusterLatestCallback`, but returns the full archive (which
332
+ // carries the materialized block) rather than only the latest ActionRev.
333
+ const fetchArchiveFromPeer = async (peerIdStr, blockId) => {
334
+ let peerId;
335
+ try {
336
+ peerId = peerIdFromString(peerIdStr);
337
+ }
338
+ catch {
339
+ return undefined;
340
+ }
341
+ if (peerId.equals(node.peerId))
342
+ return undefined;
343
+ const syncClient = new SyncClient(peerId, keyNetwork, protocolPrefix);
344
+ try {
345
+ const response = await Promise.race([
346
+ syncClient.requestBlock({ blockId, rev: undefined }),
347
+ new Promise(resolve => { setTimeout(() => resolve({ success: false }), 1000).unref(); })
348
+ ]);
349
+ return response.success ? response.archive : undefined;
350
+ }
351
+ catch {
352
+ // Peer unreachable / no data — caller falls back to the next cohort peer.
353
+ return undefined;
354
+ }
355
+ };
356
+ // Active reconciliation for a block this member committed without the matching pend
357
+ // (cohort drift). Queries the commit cohort (self already excluded) for the block,
358
+ // picks the highest revision that is at least the committed rev, and persists it via
359
+ // the churn-replication funnel so the block is no longer under-replicated.
360
+ const reconcileBlock = async (blockId, committed, cohortPeerIds) => {
361
+ const targets = cohortPeerIds.filter(id => id !== node.peerId.toString());
362
+ if (targets.length === 0)
363
+ return;
364
+ const archives = await Promise.all(targets.map(peerIdStr => fetchArchiveFromPeer(peerIdStr, blockId)));
365
+ let best;
366
+ for (const archive of archives) {
367
+ if (!archive)
368
+ continue;
369
+ const revs = Object.keys(archive.revisions).map(Number);
370
+ if (revs.length === 0)
371
+ continue;
372
+ const maxRev = Math.max(...revs);
373
+ if (maxRev < committed.rev)
374
+ continue;
375
+ const data = archive.revisions[maxRev];
376
+ if (!data?.block)
377
+ continue;
378
+ if (!best || maxRev > best.source.rev) {
379
+ best = { block: data.block, source: { actionId: data.action.actionId, rev: maxRev } };
380
+ }
381
+ }
382
+ if (best) {
383
+ await storageRepo.saveReplicatedBlock(blockId, best.block, best.source);
384
+ }
385
+ };
212
386
  clusterImpl = clusterMember({
213
387
  storageRepo,
214
388
  peerNetwork: keyNetwork,
@@ -220,7 +394,15 @@ export async function createLibp2pNodeBase(options, defaults) {
220
394
  validator: options.validator,
221
395
  reputation,
222
396
  consensusConfig,
223
- stateStore: options.transactionStateStore
397
+ stateStore: options.transactionStateStore,
398
+ reconcileBlock,
399
+ onCommitCertificate
400
+ // `recomputeArbitratorSet` (invalidation layer-2) is intentionally NOT wired here yet: a live FRET
401
+ // recompute needs a churn-tolerance window so it does not false-reject legitimate certificates from
402
+ // late-joiners (a liveness regression). Until that is tuned against live topology — and the
403
+ // cohort-topic membership-cert trust anchor (layer 3) lands — invalidation verification runs on the
404
+ // challenger-bound set + membership + dedup (layer 1) and LOGS the residual anchoring gap. See
405
+ // `verifyInvalidationCertificate` and `tickets/plan/cohort-topic-membership-cert-trust-anchoring.md`.
224
406
  });
225
407
  const coordinatorRepoFactory = coordinatorRepo(keyNetwork, createClusterClient, {
226
408
  clusterSize: options.clusterSize ?? 10,
@@ -271,6 +453,59 @@ export async function createLibp2pNodeBase(options, defaults) {
271
453
  await clusterImpl.recoverTransactions();
272
454
  await coordinatedRepo.recoverTransactions();
273
455
  }
456
+ // --- Churn-resilient spread: drive SpreadOnChurnMonitor on a live node ---
457
+ // Nothing previously activated the SENDING side of the churn-resilient spread protocol on a
458
+ // real node. Here we init + start the monitor and feed it the blocks this node physically
459
+ // holds, so a debounced connection:close re-pushes them to expansion-cohort peers (the
460
+ // receiver durably persists each push via saveReplicatedBlock).
461
+ const networkManager = node.services?.networkManager;
462
+ let spreadMonitor;
463
+ let offOwnedBlockFeed;
464
+ if (networkManager && (options.spreadOnChurn?.enabled ?? true) !== false) {
465
+ try {
466
+ spreadMonitor = networkManager.initSpreadOnChurnMonitor(partitionDetector, storageRepo, keyNetwork, options.clusterSize ?? 10, protocolPrefix, options.spreadOnChurn);
467
+ await spreadMonitor.start();
468
+ // Feed owned blocks: every block this node commits OR receives as a replica fires
469
+ // storageRepo.onAnyCollectionChange. Subscribe to storageRepo DIRECTLY (not
470
+ // node.blockChangeNotifier): the cohort-topic activation block below may replace
471
+ // blockChangeNotifier with a decorating bridge, but storageRepo keeps emitting on its
472
+ // own surface regardless of that opt-in. NOTE: blocks already durable from a previous
473
+ // run are NOT re-emitted on startup, so they are not tracked until next touched -
474
+ // acceptable here (churn re-replication re-derives over time); an initial-scan is a
475
+ // follow-on enhancement.
476
+ const monitor = spreadMonitor;
477
+ offOwnedBlockFeed = storageRepo.onAnyCollectionChange((e) => {
478
+ for (const blockId of e.blockIds)
479
+ monitor.trackBlock(blockId);
480
+ });
481
+ }
482
+ catch (err) {
483
+ // Spread is a resilience optimization, not a correctness requirement - a wiring
484
+ // failure (e.g. FRET briefly unavailable) must NOT hard-fail node startup, unlike the
485
+ // operator-opted-in cohortTopic block. Log and continue with spread inert.
486
+ (node.logger?.forComponent?.('db-p2p:spread-on-churn'))?.('init failed: %o', err);
487
+ }
488
+ }
489
+ // Expose for tests/diagnostics (mirrors node.keyNetwork / node.reputation).
490
+ node.spreadOnChurnMonitor = spreadMonitor;
491
+ // Disposal: release the owned-block subscription + stop the monitor deterministically
492
+ // before the transports close. Composes with the arachnode / clusterMember / cohort-topic
493
+ // stop wrappers (each calls its captured previousStop last). Both steps are idempotent (the
494
+ // unsubscribe is flag-guarded; SpreadOnChurnMonitor.stop early-returns when not running), so
495
+ // a double node.stop() does not throw.
496
+ {
497
+ const previousStop = node.stop.bind(node);
498
+ node.stop = async () => {
499
+ try {
500
+ offOwnedBlockFeed?.();
501
+ if (spreadMonitor)
502
+ await spreadMonitor.stop();
503
+ }
504
+ finally {
505
+ await previousStop();
506
+ }
507
+ };
508
+ }
274
509
  // Initialize Arachnode ring membership and restoration
275
510
  const enableArachnode = options.arachnode?.enableRingZulu ?? true;
276
511
  if (enableArachnode) {
@@ -364,9 +599,291 @@ export async function createLibp2pNodeBase(options, defaults) {
364
599
  // Expose coordinated repo and storage for external use
365
600
  node.coordinatedRepo = coordinatedRepo;
366
601
  node.storageRepo = storageRepo;
602
+ // The StorageRepo is the single commit funnel for both the coordinated and
603
+ // direct paths, so it is the node's per-collection change-notifier origin. This is the
604
+ // default; the cohort-topic activation block below REPLACES it with the origination-decorating
605
+ // bridge notifier when the substrate is enabled.
606
+ node.blockChangeNotifier = storageRepo;
367
607
  node.keyNetwork = keyNetwork;
368
608
  node.reputation = reputation;
369
609
  node.disputeService = disputeServiceInstance;
610
+ // --- Cohort-topic origination activation (post-node: consumes the fully-assembled node + FRET) ---
611
+ // This is the only place that is after the node + FRET are assembled (node.start() done, fretSvc
612
+ // available) yet before any caller can capture `blockChangeNotifier` — the Quereus collection-factory
613
+ // captures it once, immediately after createLibp2pNode returns, and reuses that reference as
614
+ // `localChangeNotifier` for every NetworkTransactor it builds. Installing the bridge here makes the
615
+ // origination path live for ALL collections created on the node.
616
+ if (cohortEnabled) {
617
+ // The host needs the full FRET engine surface; node.services.fret is the wrapper (see resolveFretEngine).
618
+ const fret = resolveFretEngine(fretSvc);
619
+ if (!fret) {
620
+ // Operator opted in; degrading silently to the bare notifier would hide misconfiguration.
621
+ // The node has already started (transports open, FRET running), so tear it down before the
622
+ // hard-fail rather than leaking a started node + open transports on the rejection.
623
+ await node.stop();
624
+ throw new Error('cohortTopic enabled but the FRET service is unavailable on the node');
625
+ }
626
+ // A host-construction failure also hard-fails (operator opted in); stop the started node first so
627
+ // the rejection does not leak open transports / a running FRET service. node.stop() runs the
628
+ // already-installed arachnode + clusterMember teardown wrappers and closes the node's connections.
629
+ let host;
630
+ try {
631
+ host = await createCohortTopicHost(node, fret, {
632
+ ...(options.cohortTopic.host ?? {}),
633
+ // Wire the node's reputation service in as the production backing for the bootstrap-evidence
634
+ // referee verifier (the `{ isBanned, getScore }` view `PeerReputationService` satisfies), so a
635
+ // configured cohort genuinely gates cold-root `bootstrap: true` (PoW always; reputation when a
636
+ // referee endorsement is offered; a signed reference to an existing parent topic on any tier).
637
+ // The node service is the *default* backing — a caller that supplies its own `antiDos.reputation`
638
+ // (or any other `antiDos` override) still wins, since the caller spread comes last.
639
+ antiDos: { reputation, ...(options.cohortTopic.host?.antiDos) },
640
+ // committedParentTopicReader (the T0/T1 committed-tier parent-reference existence backing) is
641
+ // intentionally left unwired: no coord-keyed committed-membership index exists yet (the
642
+ // transaction-log commit certificate is keyed by action, not by coord_0). So the host default
643
+ // fails T0/T1 parent-ref existence closed — a FRET-cached cert must not vouch for committed-tier
644
+ // existence (committed-tier integrity) — while T2/T3 parent-ref consults the FRET membership cache
645
+ // for real. The dedicated committed backing is the follow-on `cohort-topic-parent-ref-tx-log-content`;
646
+ // an operator may still pass one via cohortTopic.host.committedParentTopicReader.
647
+ privateKey: nodePrivateKey, // real k − x threshold signing
648
+ wantK: cohortWantK,
649
+ });
650
+ }
651
+ catch (err) {
652
+ await node.stop();
653
+ throw err;
654
+ }
655
+ // selfIsCohortMember: this node owns the collection's reactivity-topic fan-out iff it is in the
656
+ // FRET cohort around coord_0(H(currentTailId ‖ "reactivity")). Uses db-core's default hashes
657
+ // (createReactivityTopicAnchor / createTierAddressing / createRingHash), byte-identical to the
658
+ // host's internal `new RingHash()` and the subscriber-side anchor, and the SAME cohortWantK as
659
+ // the host — so the coord + cohort line up across origination and subscription.
660
+ const selfIsCohortMember = createReactivitySelfMembershipGate({
661
+ fret,
662
+ selfPeerId: node.peerId.toString(),
663
+ wantK: cohortWantK,
664
+ });
665
+ const { unsubscribe } = attachCohortChangeBridge(node, {
666
+ source: storageRepo,
667
+ service: host.service,
668
+ selfIsCohortMember,
669
+ extractCommitCert: makeClusterCommitCertExtractor(certStore),
670
+ });
671
+ // Expose the host so the reactivity origination wiring (and the activation test) can install
672
+ // `CohortTopicService.onLocalCommit`.
673
+ node.cohortTopicHost = host;
674
+ // --- Reactivity notification transport (origination → fan-out → inbound delivery → push-state gossip) ---
675
+ // Compose notify + forwarder-host + push-state-gossip onto the cohort-topic host so a committed change
676
+ // on a tail-cohort member actually reaches subscribers on OTHER nodes over real sockets. The change
677
+ // bridge above fires `onLocalCommit`; this is what the emitted notifications travel over.
678
+ // (docs/reactivity.md §Notification origination / §Propagation.) Reactivity reuses the canonical,
679
+ // network-agnostic protocol IDs, matching the cohort-topic family's production default.
680
+ const selfPeerId = node.peerId.toString();
681
+ const reactivityProtocols = DEFAULT_REACTIVITY_PROTOCOLS;
682
+ const reactivityProfile = host.profile; // Edge ⇒ subscriber-only via the policy gate; Core forwards.
683
+ const reactivityPolicy = reactivityNodePolicy(reactivityProfile);
684
+ // db-core default anchor + tier addressing, byte-identical to the host's `new RingHash()`, the
685
+ // origination gate, and the subscriber-side anchor — so coord_0 derivation lines up everywhere.
686
+ const reactivityAddressing = createTierAddressing(createRingHash());
687
+ // Reactivity's forwarder cohort sits at coord_0 — TREE tier 0 (peer-independent), distinct from the
688
+ // CAPACITY tier T3 the verifier/willingness use. `registry.findServing` keys on the engine's tree
689
+ // depth, so the served reactivity engine is found at tree tier 0, never at 3.
690
+ const REACTIVITY_FORWARDER_TREE_TIER = 0;
691
+ // Node-level subscriber registry: a constructed ReactivitySubscriptionManager registers here so a
692
+ // socket-delivered NotificationV1 reaches it. (The Quereus Database.watch → manager bridge that
693
+ // CONSTRUCTS managers stays the backlog item optimystic-network-reactive-watch-integration-test.)
694
+ const reactivitySubscribers = new ReactivitySubscriberRegistry();
695
+ node.reactivitySubscribers = reactivitySubscribers;
696
+ // 1. Notify transport — unicast NotificationV1 send + inbound subscribe. selfPeerId guards self-dials.
697
+ const notify = new Libp2pReactivityNotifyTransport(node, { selfPeerId });
698
+ // 2. Forwarder host — turns the forward decision into live fan-out over the notify transport.
699
+ const forwarderHost = new ReactivityForwarderHost({
700
+ transport: notify,
701
+ selfPeerId,
702
+ profile: reactivityProfile,
703
+ pushStateInit: (topicId, n) => ({
704
+ collectionId: n.collectionId,
705
+ topicId: bytesToB64url(topicId),
706
+ tailIdAtJoin: n.tailId,
707
+ deltaMaxBytes: reactivityPolicy.deltaMaxBytes,
708
+ }),
709
+ verifierFor: () => createNotificationVerifier({ verifier: host.service.verifier(), tier: Tier.T3 }),
710
+ directSubscribers: (topicId) => {
711
+ // Find the served reactivity engine at TREE tier 0 (see REACTIVITY_FORWARDER_TREE_TIER) and read
712
+ // its direct-subscriber records. The adapter filters to reactivity appState and maps participantId
713
+ // bytes → dialable peer-id strings (the transport's `peerIdFromString` space) — NOT base64url,
714
+ // which would silently fail to dial. `undefined` (no subscriber has registered here yet) ⇒ [].
715
+ const engine = host.registry.findServing(topicId, REACTIVITY_FORWARDER_TREE_TIER);
716
+ return engine === undefined ? [] : reactivityDirectSubscribers(engine, topicId);
717
+ },
718
+ // No childCohorts until cohort-topic-parent-child-link populates PushState.childCohorts (single
719
+ // tier-0 reach today); wire the resolver anyway. A child cohort's primary is the FRET-nearest member
720
+ // of its coord, returned as a peer-id string (the dial space).
721
+ resolveChildPrimary: (ref) => {
722
+ const peers = fret.assembleCohort(b64urlToBytes(ref.coord), cohortWantK);
723
+ return peers.length > 0 ? peers[0] : undefined;
724
+ },
725
+ deliverLocal: (topicId, n) => reactivitySubscribers.deliver(topicId, n),
726
+ });
727
+ // Inbound notify frames → forwarder host (subscriber role delivers in-process; forwarder role fans out).
728
+ registerNotifyHandler(node, reactivityProtocols.notify, notify);
729
+ const offInboundNotify = notify.onNotification((from, n) => { void forwarderHost.onInbound(from, n); });
730
+ // 3. Origination emit — install onLocalCommit: a member commit builds a NotificationV1 and ingests it.
731
+ const origination = new ReactivityOriginationManager({
732
+ service: host.service,
733
+ resolveContext: (event) => {
734
+ if (event.tailId === undefined) {
735
+ return undefined; // tail-less (read-driven promotion) never originates (the gate also returns first)
736
+ }
737
+ return {
738
+ // MUST reuse the gate's `reactivityTailBytes` (utf8), NOT db-core's double-hashing
739
+ // blockIdToBytes — else origination derives a different coord than subscribers resolve.
740
+ tailId: reactivityTailBytes(event.tailId),
741
+ deltaMaxBytes: reactivityPolicy.deltaMaxBytes,
742
+ // rotationHint stays undefined on a live node: the successor tail id is not knowable at the
743
+ // filling commit (random block ids; gated on 6.5-block-id-derivation). The authoritative,
744
+ // observable rotation signal is `event.tailId` CHANGING, which the manager observes via the
745
+ // `markRotated` binding below. (The pre-announce remains exercised in the mock-tier harness +
746
+ // the design simulator, both of which can synthesize the successor id.)
747
+ };
748
+ },
749
+ // reactivityNotificationTopicId(n) = reactivityTopicId(b64urlToBytes(n.tailId)); since
750
+ // n.tailId = b64url(reactivityTailBytes(tail)), this is the SAME topicId the gate assembled coord_0
751
+ // around and the subscriber/forwarder verifier derives — closing the encoding loop.
752
+ emit: (n) => { void forwarderHost.ingest(reactivityNotificationTopicId(n), n); },
753
+ // Observe-rotation: when a collection's tail id changes between commits the OLD tail's reactivity
754
+ // topic has rotated. Start its drain so the recover serve begins redirecting to the new tree (the
755
+ // `reactivity-rotation-recover-redirect-drain` markRotated seam). `oldTopicId` is byte-identical to
756
+ // the topic a subscriber subscribed under (both `reactivityTopicId(reactivityTailBytes(tail))`).
757
+ markRotated: (oldTopicId, redirect, now) => forwarderHost.markRotated(oldTopicId, redirect, now),
758
+ });
759
+ origination.install();
760
+ // 4. PushState gossip — periodic intra-cohort convergence so any member (not just the primary) can
761
+ // serve a replay/backfill. Rides the host's cohort gossip transport (no second transport).
762
+ const pushStateGossip = new ReactivityPushStateGossipDriver({
763
+ gossipTransport: host.gossipTransport,
764
+ liveCollections: () => forwarderHost.livePushStates().map((pushState) => ({
765
+ pushState,
766
+ cohortCoord: reactivityAddressing.coord0(b64urlToBytes(pushState.topicId)),
767
+ })),
768
+ pushStateForGossip: (g) => forwarderHost.pushStateFor(b64urlToBytes(g.topicId)),
769
+ // Authenticity gate: accept gossip only from a member of the cohort around the frame's reactivity
770
+ // coord (per-frame peer-sig envelope signing is deferred — reactivity-pushstate-gossip's hardening backlog).
771
+ isCohortMember: (fromPeerId, g) => fret.assembleCohort(reactivityAddressing.coord0(b64urlToBytes(g.topicId)), cohortWantK).includes(fromPeerId),
772
+ });
773
+ registerPushStateGossipHandler(node, reactivityProtocols.pushStateGossip, pushStateGossip);
774
+ pushStateGossip.start();
775
+ // 5. Recover RPC — the pull companion to notify (docs/reactivity.md §Backfill RPC / §Resume). A
776
+ // subscriber that detected a gap, or woke from sleep past the live tail, asks a serving cohort member
777
+ // "what did I miss?" and is brought current over a real request-reply socket. The SERVE side is live
778
+ // here: this node answers RecoverRequestV1 frames against its live forwarder PushStates. The OUTBOUND
779
+ // transport + signers are constructed and exposed for the subscribe factory that CONSTRUCTS managers
780
+ // (the Quereus Database.watch app-bridge — backlog optimystic-network-reactive-watch-integration-test);
781
+ // no node-internal manager calls them yet, exactly as the notify subscriber side is constructed against
782
+ // `reactivitySubscribers` rather than from a watch.
783
+ //
784
+ // Node-level sticky cohort-hint cache (keyed by collectionId), shared between the outbound transport's
785
+ // sticky-primary lookup and a future manager's rotation-invalidation so both see ONE cache. It starts
786
+ // empty ⇒ the transport falls through to the cohort-walk (any member holding the gossiped PushState
787
+ // answers); populating the sticky primary is a one-RT optimization, not a correctness need.
788
+ const reactivityCohortHintCache = createStickyCohortHintCache();
789
+ // topicId → dialable cohort member peer-id strings: the SAME FRET coord_0 assembly the push-state-gossip
790
+ // authenticity gate uses (`reactivityAddressing.coord0` → `fret.assembleCohort`), so a recover walk
791
+ // reaches exactly the cohort that holds the topic's gossiped PushState. `assembleCohort` returns peer-id
792
+ // strings (the recover dialer's `peerIdFromString` space), matching the notify dial-target space.
793
+ const resolveReactivityCohort = (topicId) => fret.assembleCohort(reactivityAddressing.coord0(topicId), cohortWantK);
794
+ // Outbound transport: exposes the db-core BackfillTransport / ResumeTransport seams against this node.
795
+ // maxBytes is omitted so the dialer + handler default to DEFAULT_STREAM_MAX_BYTES, matching the notify
796
+ // transport's default (constructed above without an override) — one frame ceiling across the family.
797
+ const recover = new Libp2pReactivityRecoverTransport({
798
+ dialer: createLibp2pRecoverDialer(node, reactivityProtocols.recover),
799
+ selfPeerId,
800
+ cohortHintCache: reactivityCohortHintCache,
801
+ resolveCohort: resolveReactivityCohort,
802
+ });
803
+ // Inbound serve handler: decode (bounded) → verify the dialing peer's signature → freshness/replay gate →
804
+ // resolve the live PushState off the forwarder host → serveBackfill/serveResume → reply (no reply on any
805
+ // failure; the stream aborts and the subscriber walks/chain-reads). One node-level replay guard is shared
806
+ // across all recover requests — a plain pruned-on-access map, so no new timer to tear down.
807
+ registerRecoverHandler(node, reactivityProtocols.recover, {
808
+ pushStateFor: forwarderHost.pushStateFor.bind(forwarderHost),
809
+ pushStateForCollection: forwarderHost.pushStateForCollection.bind(forwarderHost),
810
+ replayGuard: createCorrelationReplayGuard(),
811
+ rotationFor: (req, now) => {
812
+ // Drain-window redirect: a recover reaching an OLD (rotated, still-draining) tail is bounced to
813
+ // the new tree (reactivity-rotation-recover-redirect-drain). A resume carries the stale topic
814
+ // (topicId = reactivityTopicId(latestKnownTailId)); a backfill carries no topic, so resolve the
815
+ // collection's current served topic. rotationRedirectFor returns the gate's redirect while
816
+ // draining and undefined once drained (then evicting the gate + the old tail's served PushState).
817
+ const oldTopicId = req.topicId ?? resolveCurrentServedTopic(forwarderHost, req.collectionId);
818
+ return oldTopicId === undefined ? undefined : forwarderHost.rotationRedirectFor(oldTopicId, now);
819
+ },
820
+ });
821
+ // The subscriber's synchronous request signers over the node's Ed25519 key (resolves the recover wiring's
822
+ // lone design point — see recover-transport.ts §createRecoverRequestSigners). Fed to a manager by the
823
+ // subscribe factory alongside recover.backfillTransport(topicId, collectionId) /
824
+ // recover.resumeTransport(topicId, collectionId).
825
+ const recoverSigners = createRecoverRequestSigners(nodePrivateKey);
826
+ // Expose the recover seams so the subscribe factory wires backfill/resume RPC + signers + the shared
827
+ // sticky cache (mirrors `reactivitySubscribers` above).
828
+ node.reactivityRecover = recover;
829
+ node.reactivityRecoverSigners = recoverSigners;
830
+ node.reactivityCohortHintCache = reactivityCohortHintCache;
831
+ // 6. Rotation re-registration scheduler — the host timer that moves a subscriber to the rotated tree
832
+ // when its manager surfaces a `RotationNotice` (`reactivity-rotation-rereg-scheduler`). Constructed with
833
+ // the default unref'd `setTimeout` timer so an idle re-registration never pins the process. The
834
+ // `reRegister(plan)` MOVE belongs to the subscribe factory that CONSTRUCTS managers (the deferred Quereus
835
+ // `Database.watch` bridge — backlog optimystic-network-reactive-watch-integration-test): on fire it builds
836
+ // a fresh `ReactivitySubscriptionManager` under `plan.newTopicId` carrying `plan.lastRevision`, registers
837
+ // it, and swaps the `ReactivitySubscriberRegistry` entry — registering the NEW-topic handler BEFORE
838
+ // unregistering the old, so a notification mid-swap is never dropped. Until that factory lands no
839
+ // node-internal manager drives `schedule()`, so this seam is a logged no-op — exactly as 12.33 exposed
840
+ // `reactivitySubscribers` / `reactivityRecover` without a live manager constructor.
841
+ const reactivityRotation = new RotationReRegistrationScheduler({
842
+ reRegister: (plan) => {
843
+ reactivityWiringLog("reactivity rotation re-registration fired for successor topic=%s (lastRevision=%d) but no subscribe factory is wired yet — deferred to optimystic-network-reactive-watch-integration-test", bytesToB64url(plan.newTopicId), plan.lastRevision);
844
+ return Promise.resolve();
845
+ },
846
+ });
847
+ node.reactivityRotation = reactivityRotation;
848
+ // --- Matchmaking QueryV1 RPC — cohort serve side (docs/matchmaking.md §Seeker query) ---
849
+ // The server half of the seeker query transport: a remote seeker dials `/optimystic/matchmaking/1.0.0/query`
850
+ // and this node answers with its cohort's locally-held provider/seeker registrations, signed by the node
851
+ // peer key. Matchmaking is layered ABOVE the cohort-topic substrate, so it owns its own protocol family
852
+ // and is wired here (the composition root) over the host's PUBLIC surface only — mirroring the reactivity
853
+ // registration above; nothing reaches into host.ts internals. The OUTBOUND seeker walk client is the
854
+ // prereq follow-on `matchmaking-query-rpc-seeker-walk`; only the serve side is live here.
855
+ const matchmakingProtocols = DEFAULT_MATCHMAKING_PROTOCOLS;
856
+ registerMatchmakingQueryHandler(node, matchmakingProtocols.query, {
857
+ registry: host.registry,
858
+ // Reuse the reactivity addressing: createTierAddressing(createRingHash()) is byte-identical to the
859
+ // host's internal addressing for the tier-0 coord (peer- and fanout-independent), and the handler
860
+ // only ever derives coord_0(topicId).
861
+ addressing: reactivityAddressing,
862
+ // Single-member reply signature over the node peer key (same pattern reactivity uses for its signers).
863
+ sign: async (payload) => bytesToB64url(await signPeer(nodePrivateKey, payload)),
864
+ // Anti-DoS rate-limit seam (backlog matchmaking-query-rate-limit) intentionally left unwired here:
865
+ // default-allow. When that ticket lands it passes a `gate: (from, topicId) => boolean` that limits on
866
+ // the connection's verified `from` peer (NOT the self-asserted query.requesterId).
867
+ });
868
+ // Teardown: release reactivity timers + protocol handlers BEFORE host.stop() (which clears the cohort
869
+ // gossip timer + unhandles the cohort-topic protocols) BEFORE the node's transports close (previousStop).
870
+ // Composes with the existing arachnode + clusterMember stop wrappers (each calls its captured previousStop last).
871
+ const previousStop = node.stop.bind(node);
872
+ node.stop = async () => {
873
+ try {
874
+ reactivityRotation.stop();
875
+ pushStateGossip.stop();
876
+ offInboundNotify();
877
+ await node.unhandle(reactivityProtocolList(reactivityProtocols));
878
+ await node.unhandle(matchmakingProtocolList(matchmakingProtocols));
879
+ unsubscribe();
880
+ await host.stop();
881
+ }
882
+ finally {
883
+ await previousStop();
884
+ }
885
+ };
886
+ }
370
887
  return node;
371
888
  }
372
889
  //# sourceMappingURL=libp2p-node-base.js.map