@optimystic/db-p2p 0.24.2 → 0.25.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 (249) hide show
  1. package/{readme.md → README.md} +688 -668
  2. package/dist/src/cluster/block-transfer-service.d.ts +133 -10
  3. package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
  4. package/dist/src/cluster/block-transfer-service.js +155 -9
  5. package/dist/src/cluster/block-transfer-service.js.map +1 -1
  6. package/dist/src/cluster/block-transfer.d.ts +67 -7
  7. package/dist/src/cluster/block-transfer.d.ts.map +1 -1
  8. package/dist/src/cluster/block-transfer.js +127 -24
  9. package/dist/src/cluster/block-transfer.js.map +1 -1
  10. package/dist/src/cluster/certified-claims.d.ts +170 -0
  11. package/dist/src/cluster/certified-claims.d.ts.map +1 -0
  12. package/dist/src/cluster/certified-claims.js +200 -0
  13. package/dist/src/cluster/certified-claims.js.map +1 -0
  14. package/dist/src/cluster/client-signature-verifier.d.ts +23 -0
  15. package/dist/src/cluster/client-signature-verifier.d.ts.map +1 -0
  16. package/dist/src/cluster/client-signature-verifier.js +33 -0
  17. package/dist/src/cluster/client-signature-verifier.js.map +1 -0
  18. package/dist/src/cluster/cluster-policy.d.ts +9 -3
  19. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  20. package/dist/src/cluster/cluster-policy.js +111 -27
  21. package/dist/src/cluster/cluster-policy.js.map +1 -1
  22. package/dist/src/cluster/cluster-repo.d.ts +140 -11
  23. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  24. package/dist/src/cluster/cluster-repo.js +485 -29
  25. package/dist/src/cluster/cluster-repo.js.map +1 -1
  26. package/dist/src/cluster/commit-proof.d.ts +134 -0
  27. package/dist/src/cluster/commit-proof.d.ts.map +1 -0
  28. package/dist/src/cluster/commit-proof.js +263 -0
  29. package/dist/src/cluster/commit-proof.js.map +1 -0
  30. package/dist/src/cluster/quorum-restore.d.ts +95 -8
  31. package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
  32. package/dist/src/cluster/quorum-restore.js +113 -14
  33. package/dist/src/cluster/quorum-restore.js.map +1 -1
  34. package/dist/src/cluster/rebalance-monitor.d.ts +84 -0
  35. package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
  36. package/dist/src/cluster/rebalance-monitor.js +207 -5
  37. package/dist/src/cluster/rebalance-monitor.js.map +1 -1
  38. package/dist/src/cluster/reconcile-block.d.ts +27 -7
  39. package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
  40. package/dist/src/cluster/reconcile-block.js +179 -38
  41. package/dist/src/cluster/reconcile-block.js.map +1 -1
  42. package/dist/src/cluster/service.d.ts +13 -4
  43. package/dist/src/cluster/service.d.ts.map +1 -1
  44. package/dist/src/cluster/service.js +17 -18
  45. package/dist/src/cluster/service.js.map +1 -1
  46. package/dist/src/cluster/spread-on-churn.d.ts +8 -2
  47. package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
  48. package/dist/src/cluster/spread-on-churn.js +7 -7
  49. package/dist/src/cluster/spread-on-churn.js.map +1 -1
  50. package/dist/src/cohort-topic/host.d.ts.map +1 -1
  51. package/dist/src/cohort-topic/host.js +8 -7
  52. package/dist/src/cohort-topic/host.js.map +1 -1
  53. package/dist/src/cohort-topic/stream-util.d.ts +7 -6
  54. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  55. package/dist/src/cohort-topic/stream-util.js +14 -41
  56. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  57. package/dist/src/dispute/cascade.d.ts +7 -8
  58. package/dist/src/dispute/cascade.d.ts.map +1 -1
  59. package/dist/src/dispute/cascade.js +24 -7
  60. package/dist/src/dispute/cascade.js.map +1 -1
  61. package/dist/src/dispute/dispute-service.d.ts +5 -0
  62. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  63. package/dist/src/dispute/dispute-service.js +13 -3
  64. package/dist/src/dispute/dispute-service.js.map +1 -1
  65. package/dist/src/dispute/invalidation.d.ts +33 -15
  66. package/dist/src/dispute/invalidation.d.ts.map +1 -1
  67. package/dist/src/dispute/invalidation.js +127 -34
  68. package/dist/src/dispute/invalidation.js.map +1 -1
  69. package/dist/src/dispute/service.d.ts.map +1 -1
  70. package/dist/src/dispute/service.js +2 -1
  71. package/dist/src/dispute/service.js.map +1 -1
  72. package/dist/src/index.d.ts +7 -0
  73. package/dist/src/index.d.ts.map +1 -1
  74. package/dist/src/index.js +7 -0
  75. package/dist/src/index.js.map +1 -1
  76. package/dist/src/libp2p-key-network.d.ts +14 -10
  77. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  78. package/dist/src/libp2p-key-network.js +28 -58
  79. package/dist/src/libp2p-key-network.js.map +1 -1
  80. package/dist/src/libp2p-node-base.d.ts +38 -7
  81. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  82. package/dist/src/libp2p-node-base.js +148 -63
  83. package/dist/src/libp2p-node-base.js.map +1 -1
  84. package/dist/src/network/open-protocol-stream.d.ts +56 -0
  85. package/dist/src/network/open-protocol-stream.d.ts.map +1 -0
  86. package/dist/src/network/open-protocol-stream.js +60 -0
  87. package/dist/src/network/open-protocol-stream.js.map +1 -0
  88. package/dist/src/network/register-protocol-handler.d.ts +66 -0
  89. package/dist/src/network/register-protocol-handler.d.ts.map +1 -0
  90. package/dist/src/network/register-protocol-handler.js +51 -0
  91. package/dist/src/network/register-protocol-handler.js.map +1 -0
  92. package/dist/src/peer-address-book.d.ts +60 -2
  93. package/dist/src/peer-address-book.d.ts.map +1 -1
  94. package/dist/src/peer-address-book.js +89 -1
  95. package/dist/src/peer-address-book.js.map +1 -1
  96. package/dist/src/pend-validation.d.ts +65 -0
  97. package/dist/src/pend-validation.d.ts.map +1 -0
  98. package/dist/src/pend-validation.js +69 -0
  99. package/dist/src/pend-validation.js.map +1 -0
  100. package/dist/src/reactivity/notify-transport.d.ts.map +1 -1
  101. package/dist/src/reactivity/notify-transport.js +2 -1
  102. package/dist/src/reactivity/notify-transport.js.map +1 -1
  103. package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -1
  104. package/dist/src/reactivity/push-state-gossip.js +2 -1
  105. package/dist/src/reactivity/push-state-gossip.js.map +1 -1
  106. package/dist/src/repo/cluster-coordinator.d.ts +32 -6
  107. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  108. package/dist/src/repo/cluster-coordinator.js +44 -10
  109. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  110. package/dist/src/repo/coordinator-repo.d.ts +181 -25
  111. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  112. package/dist/src/repo/coordinator-repo.js +650 -62
  113. package/dist/src/repo/coordinator-repo.js.map +1 -1
  114. package/dist/src/repo/served-repo-proxy.d.ts +30 -0
  115. package/dist/src/repo/served-repo-proxy.d.ts.map +1 -0
  116. package/dist/src/repo/served-repo-proxy.js +50 -0
  117. package/dist/src/repo/served-repo-proxy.js.map +1 -0
  118. package/dist/src/repo/service.d.ts +16 -1
  119. package/dist/src/repo/service.d.ts.map +1 -1
  120. package/dist/src/repo/service.js +18 -16
  121. package/dist/src/repo/service.js.map +1 -1
  122. package/dist/src/reputation/types.d.ts +3 -1
  123. package/dist/src/reputation/types.d.ts.map +1 -1
  124. package/dist/src/reputation/types.js +3 -1
  125. package/dist/src/reputation/types.js.map +1 -1
  126. package/dist/src/rn.d.ts +24 -2
  127. package/dist/src/rn.d.ts.map +1 -1
  128. package/dist/src/rn.js +33 -4
  129. package/dist/src/rn.js.map +1 -1
  130. package/dist/src/storage/block-archive.d.ts +153 -0
  131. package/dist/src/storage/block-archive.d.ts.map +1 -0
  132. package/dist/src/storage/block-archive.js +194 -0
  133. package/dist/src/storage/block-archive.js.map +1 -0
  134. package/dist/src/storage/block-latch.d.ts +92 -0
  135. package/dist/src/storage/block-latch.d.ts.map +1 -0
  136. package/dist/src/storage/block-latch.js +132 -0
  137. package/dist/src/storage/block-latch.js.map +1 -0
  138. package/dist/src/storage/block-storage.d.ts +169 -12
  139. package/dist/src/storage/block-storage.d.ts.map +1 -1
  140. package/dist/src/storage/block-storage.js +415 -152
  141. package/dist/src/storage/block-storage.js.map +1 -1
  142. package/dist/src/storage/cached-raw-storage.d.ts +5 -1
  143. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -1
  144. package/dist/src/storage/cached-raw-storage.js +16 -1
  145. package/dist/src/storage/cached-raw-storage.js.map +1 -1
  146. package/dist/src/storage/cached-store-driver.d.ts +14 -4
  147. package/dist/src/storage/cached-store-driver.d.ts.map +1 -1
  148. package/dist/src/storage/cached-store-driver.js +39 -5
  149. package/dist/src/storage/cached-store-driver.js.map +1 -1
  150. package/dist/src/storage/i-block-storage.d.ts +70 -19
  151. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  152. package/dist/src/storage/i-block-storage.js +21 -1
  153. package/dist/src/storage/i-block-storage.js.map +1 -1
  154. package/dist/src/storage/i-raw-storage.d.ts +38 -0
  155. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  156. package/dist/src/storage/kv-raw-storage.d.ts +14 -8
  157. package/dist/src/storage/kv-raw-storage.d.ts.map +1 -1
  158. package/dist/src/storage/kv-raw-storage.js +29 -9
  159. package/dist/src/storage/kv-raw-storage.js.map +1 -1
  160. package/dist/src/storage/memory-store-driver.d.ts +4 -1
  161. package/dist/src/storage/memory-store-driver.d.ts.map +1 -1
  162. package/dist/src/storage/memory-store-driver.js +22 -2
  163. package/dist/src/storage/memory-store-driver.js.map +1 -1
  164. package/dist/src/storage/raw-store-codec.js +2 -2
  165. package/dist/src/storage/raw-store-driver.d.ts +44 -3
  166. package/dist/src/storage/raw-store-driver.d.ts.map +1 -1
  167. package/dist/src/storage/shared-cache-pool.d.ts +60 -3
  168. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -1
  169. package/dist/src/storage/shared-cache-pool.js +68 -4
  170. package/dist/src/storage/shared-cache-pool.js.map +1 -1
  171. package/dist/src/storage/storage-repo.d.ts +204 -26
  172. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  173. package/dist/src/storage/storage-repo.js +422 -119
  174. package/dist/src/storage/storage-repo.js.map +1 -1
  175. package/dist/src/storage/store-identity.d.ts +21 -0
  176. package/dist/src/storage/store-identity.d.ts.map +1 -0
  177. package/dist/src/storage/store-identity.js +33 -0
  178. package/dist/src/storage/store-identity.js.map +1 -0
  179. package/dist/src/storage/struct.d.ts +15 -0
  180. package/dist/src/storage/struct.d.ts.map +1 -1
  181. package/dist/src/storage/with-read-cache.d.ts +92 -0
  182. package/dist/src/storage/with-read-cache.d.ts.map +1 -0
  183. package/dist/src/storage/with-read-cache.js +132 -0
  184. package/dist/src/storage/with-read-cache.js.map +1 -0
  185. package/dist/src/sync/service.d.ts +13 -3
  186. package/dist/src/sync/service.d.ts.map +1 -1
  187. package/dist/src/sync/service.js +7 -30
  188. package/dist/src/sync/service.js.map +1 -1
  189. package/dist/src/testing/mesh-harness.d.ts +75 -2
  190. package/dist/src/testing/mesh-harness.d.ts.map +1 -1
  191. package/dist/src/testing/mesh-harness.js +193 -81
  192. package/dist/src/testing/mesh-harness.js.map +1 -1
  193. package/dist/src/testing/raw-storage-conformance.d.ts +8 -0
  194. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  195. package/dist/src/testing/raw-storage-conformance.js +97 -2
  196. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  197. package/package.json +5 -6
  198. package/src/cluster/block-transfer-service.ts +528 -312
  199. package/src/cluster/block-transfer.ts +547 -424
  200. package/src/cluster/certified-claims.ts +319 -0
  201. package/src/cluster/client-signature-verifier.ts +34 -0
  202. package/src/cluster/cluster-policy.ts +294 -203
  203. package/src/cluster/cluster-repo.ts +2501 -2034
  204. package/src/cluster/commit-proof.ts +344 -0
  205. package/src/cluster/quorum-restore.ts +348 -223
  206. package/src/cluster/rebalance-monitor.ts +539 -254
  207. package/src/cluster/reconcile-block.ts +404 -203
  208. package/src/cluster/service.ts +23 -20
  209. package/src/cluster/spread-on-churn.ts +352 -346
  210. package/src/cohort-topic/host.ts +8 -7
  211. package/src/cohort-topic/stream-util.ts +14 -43
  212. package/src/dispute/cascade.ts +31 -15
  213. package/src/dispute/dispute-service.ts +706 -696
  214. package/src/dispute/invalidation.ts +162 -52
  215. package/src/dispute/service.ts +126 -125
  216. package/src/index.ts +7 -0
  217. package/src/libp2p-key-network.ts +1204 -1235
  218. package/src/libp2p-node-base.ts +185 -70
  219. package/src/network/open-protocol-stream.ts +87 -0
  220. package/src/network/register-protocol-handler.ts +85 -0
  221. package/src/peer-address-book.ts +96 -2
  222. package/src/pend-validation.ts +89 -0
  223. package/src/reactivity/notify-transport.ts +2 -1
  224. package/src/reactivity/push-state-gossip.ts +2 -1
  225. package/src/repo/cluster-coordinator.ts +65 -11
  226. package/src/repo/coordinator-repo.ts +729 -71
  227. package/src/repo/served-repo-proxy.ts +55 -0
  228. package/src/repo/service.ts +25 -16
  229. package/src/reputation/types.ts +3 -1
  230. package/src/rn.ts +34 -4
  231. package/src/storage/block-archive.ts +242 -0
  232. package/src/storage/block-latch.ts +144 -0
  233. package/src/storage/block-storage.ts +787 -498
  234. package/src/storage/cached-raw-storage.ts +21 -1
  235. package/src/storage/cached-store-driver.ts +898 -859
  236. package/src/storage/i-block-storage.ts +172 -116
  237. package/src/storage/i-raw-storage.ts +40 -0
  238. package/src/storage/kv-raw-storage.ts +34 -9
  239. package/src/storage/memory-store-driver.ts +26 -2
  240. package/src/storage/raw-store-codec.ts +42 -42
  241. package/src/storage/raw-store-driver.ts +45 -3
  242. package/src/storage/shared-cache-pool.ts +516 -452
  243. package/src/storage/storage-repo.ts +1421 -1014
  244. package/src/storage/store-identity.ts +41 -0
  245. package/src/storage/struct.ts +52 -34
  246. package/src/storage/with-read-cache.ts +182 -0
  247. package/src/sync/service.ts +178 -198
  248. package/src/testing/mesh-harness.ts +524 -344
  249. package/src/testing/raw-storage-conformance.ts +114 -2
@@ -161,6 +161,7 @@ import { FretCohortThresholdCrypto, createVerifyOnlyThresholdCrypto } from "./th
161
161
  import { FretTrustAnchor } from "./fret-trust-anchor.js";
162
162
  import { FretSizeEstimator } from "./size-estimator.js";
163
163
  import { peerIdToBytes, bytesToPeerIdString } from "./peer-codec.js";
164
+ import { registerProtocolHandler } from "../network/register-protocol-handler.js";
164
165
  import { signPeer, verifyPeerSig } from "./peer-sig.js";
165
166
  import { createPoWVerifier, createReputationVerifier, type BootstrapReputationView } from "./bootstrap-evidence-verifiers.js";
166
167
  import { createParentReferenceVerifier, createDefaultParentTopicView, type BootstrapParentTopicView } from "./bootstrap-parent-reference.js";
@@ -1037,7 +1038,7 @@ export async function createCohortTopicHost(node: Libp2p, fret: FretService, opt
1037
1038
  // --- protocol handlers + activity callback ---
1038
1039
  // Await registration so the host is not returned (and dialed) before the five handlers are live —
1039
1040
  // and, crucially, before the gossip driver below starts ticking (no tick may run on a half-wired node).
1040
- await registerProtocolHandlers(node, protocols, registry, dispatchRegister, childLinkDeps, minSigs, signEndorse, verifier, promoteGate, gossipTransport, maybeInstantiateColdSibling, publishSink, membershipSource, selfCoord, maxBytes);
1041
+ await registerCohortTopicProtocols(node, protocols, registry, dispatchRegister, childLinkDeps, minSigs, signEndorse, verifier, promoteGate, gossipTransport, maybeInstantiateColdSibling, publishSink, membershipSource, selfCoord, maxBytes);
1041
1042
  fret.setActivityHandler(async (activity: string, cohort: string[]): Promise<{ commitCertificate: string }> => {
1042
1043
  const decoded = decodeCohortMessage(b64urlToBytes(activity), maxBytes);
1043
1044
  // Decode-and-branch: a `ChildLinkV1` (a child cohort registering with this parent) runs the child-link
@@ -2788,7 +2789,7 @@ function tierOfTopic(store: ReturnType<typeof createRegistrationStore>, topicId:
2788
2789
  return (recs.length > 0 ? recs[0]!.tier : 0) as Tier;
2789
2790
  }
2790
2791
 
2791
- async function registerProtocolHandlers(
2792
+ async function registerCohortTopicProtocols(
2792
2793
  node: Libp2p,
2793
2794
  protocols: CohortTopicProtocols,
2794
2795
  registry: CoordRegistry,
@@ -2810,7 +2811,7 @@ async function registerProtocolHandlers(
2810
2811
  await Promise.all([
2811
2812
  // register: a direct dial carries a RenewV1 (ping), a ChildLinkV1 (a child cohort registering with this
2812
2813
  // parent), or a RegisterV1 (re-attach walk fallback). The three shapes are disjoint, so try each in turn.
2813
- node.handle(protocols.register, makeRequestHandler(async (frame): Promise<Uint8Array> => {
2814
+ registerProtocolHandler(node, protocols.register, makeRequestHandler(async (frame): Promise<Uint8Array> => {
2814
2815
  const decoded = decodeCohortMessage(frame, maxBytes);
2815
2816
  const renew = tryValidate(() => validateRenewV1(decoded));
2816
2817
  if (renew !== undefined) {
@@ -2830,7 +2831,7 @@ async function registerProtocolHandlers(
2830
2831
  // every coord engine's bus; per-bus epoch matching governs which engine merges the record deltas.
2831
2832
  // First, if this is a verified co-member frame for a coord we hold no engine for, instantiate that
2832
2833
  // engine (§Cold-start instantiation) so its freshly-subscribed bus merges this very frame on `deliver`.
2833
- node.handle(protocols.gossip, makeOneWayHandler(async (frame, from): Promise<void> => {
2834
+ registerProtocolHandler(node, protocols.gossip, makeOneWayHandler(async (frame, from): Promise<void> => {
2834
2835
  maybeInstantiateColdSibling(frame);
2835
2836
  gossipTransport.deliver(from.toString(), frame);
2836
2837
  }, maxBytes)),
@@ -2839,12 +2840,12 @@ async function registerProtocolHandlers(
2839
2840
  // peer arrives as `from`, so the handler can gate per-(peer, topic) before any expensive work. The
2840
2841
  // full pipeline (rate limit → findServing → effectiveAt high-water → verify+apply, bounded
2841
2842
  // refetch) lives in the exported `handleInboundNotice`; it logs and never throws on the stream.
2842
- node.handle(protocols.promote, makeOneWayHandler(async (frame, from): Promise<void> => {
2843
+ registerProtocolHandler(node, protocols.promote, makeOneWayHandler(async (frame, from): Promise<void> => {
2843
2844
  await handleInboundNotice(frame, peerIdToBytes(from), registry, verifier, promoteGate, Date.now(), maxBytes);
2844
2845
  }, maxBytes)),
2845
2846
 
2846
2847
  // membership: serve this node's latest published cert; cache any cert the requester returns.
2847
- node.handle(protocols.membership, makeRequestHandler(async (frame): Promise<Uint8Array> => {
2848
+ registerProtocolHandler(node, protocols.membership, makeRequestHandler(async (frame): Promise<Uint8Array> => {
2848
2849
  void frame; // request frame is the raw coord; this node serves its own cohort cert
2849
2850
  const latest = publishSink.latest();
2850
2851
  if (latest !== undefined) {
@@ -2856,7 +2857,7 @@ async function registerProtocolHandlers(
2856
2857
  // sign: per-member endorsement for threshold-signature assembly. Validate the request, run the
2857
2858
  // endorsement policy, and reply with this node's peer-key signature over the request payload (or a
2858
2859
  // refusal). One Ed25519 sign and nothing more — the cohort + epoch gate bounds who we sign for.
2859
- node.handle(protocols.sign, makeRequestHandler(async (frame, from): Promise<Uint8Array> => {
2860
+ registerProtocolHandler(node, protocols.sign, makeRequestHandler(async (frame, from): Promise<Uint8Array> => {
2860
2861
  const request = validateSignRequestV1(decodeCohortMessage(frame, maxBytes));
2861
2862
  const reply = await signEndorse(request, from.toString());
2862
2863
  return encodeCohortMessage(reply, maxBytes);
@@ -14,54 +14,23 @@
14
14
  * exactly one bounded frame per stream and then closes; if a caller ever writes repeatedly on one
15
15
  * stream, honor the flag by awaiting the stream's `'drain'` event the way FRET's `rpcRequest` does.
16
16
  *
17
- * {@link openStream} deliberately duplicates FRET's `rpc/protocols.ts#openRpcStream` connection
18
- * selection. FRET now exports `openRpcStream`, but the accepted tradeoff on
19
- * {@link STREAM_OPTIONS} is why the local copy stays: FRET's helper pins `negotiateFully: false`,
20
- * which this module deliberately does not set swapping would silently reverse that decision.
21
- * Same applies to `libp2p-key-network.ts#connect`, which is a third copy.
17
+ * NOTE: accepted tradeoff — both calls to {@link openProtocolStream} below deliberately omit
18
+ * `negotiateFully`, taking libp2p's default of full multistream-select negotiation at stream-open.
19
+ * FRET's `openRpcStream` and `libp2p-key-network.ts#connect` both pass `negotiateFully: false`,
20
+ * which saves a round trip but defers an unsupported-protocol failure from stream-open to the
21
+ * first read that would turn {@link sendOneWay} against a peer lacking the protocol into a
22
+ * silent no-op. Revisit if stream-open latency shows up in a profile.
22
23
  */
23
24
 
24
25
  import type { Libp2p } from "libp2p";
25
26
  import type { Connection, PeerId, Stream } from "@libp2p/interface";
26
27
  import { readFramed, sendFramed } from "p2p-fret";
28
+ import { openProtocolStream } from "../network/open-protocol-stream.js";
29
+ import { registerProtocolHandler } from "../network/register-protocol-handler.js";
27
30
 
28
31
  /** Default per-frame ceiling — matches FRET's 512 KiB maybe-act bound. */
29
32
  export const DEFAULT_STREAM_MAX_BYTES = 512 * 1024;
30
33
 
31
- /**
32
- * libp2p refuses to open a protocol stream over a *limited* (circuit-relay) connection unless the
33
- * caller opts in, so every peer reachable only through a relay — the steady state for browsers and
34
- * NATed peers — depends on this flag. It is a harmless no-op on a direct connection.
35
- *
36
- * NOTE: accepted tradeoff — FRET's `openRpcStream` and `libp2p-key-network.ts#connect` also set
37
- * `negotiateFully: false`; deliberately not set here. It saves a round trip but defers an
38
- * unsupported-protocol failure from stream-open to the first read, which would turn
39
- * {@link sendOneWay} against a peer lacking the protocol into a silent no-op. Revisit if
40
- * stream-open latency shows up in a profile.
41
- */
42
- const STREAM_OPTIONS = { runOnLimitedConnection: true } as const;
43
-
44
- /** True for a circuit-relay ("limited") connection: libp2p stamps one with per-circuit `limits`;
45
- * sniffing `/p2p-circuit` in the multiaddr covers transports that leave `limits` unpopulated. */
46
- function isLimitedConnection(c: Connection): boolean {
47
- if ((c as { limits?: unknown }).limits != null) return true;
48
- return c.remoteAddr?.toString?.().includes("/p2p-circuit") ?? false;
49
- }
50
-
51
- /**
52
- * Open `protocol` to `peer`, reusing a healthy existing connection when there is one.
53
- *
54
- * Skips connections libp2p has not yet evicted from its index but that are no longer open, and
55
- * prefers a direct connection over a relayed one — a relayed connection can be reset once the
56
- * relay's per-circuit cap or reservation lapses, and after DCUtR upgrades a link to direct both
57
- * briefly coexist. Falls back to the relayed connection when it is the only open path.
58
- */
59
- async function openStream(node: Libp2p, peer: PeerId, protocol: string): Promise<Stream> {
60
- const open = node.getConnections(peer).filter(c => c?.status === "open" && typeof c?.newStream === "function");
61
- const chosen = open.find(c => !isLimitedConnection(c)) ?? open[0];
62
- return chosen ? await chosen.newStream([protocol], STREAM_OPTIONS) : await node.dialProtocol(peer, [protocol], STREAM_OPTIONS);
63
- }
64
-
65
34
  /**
66
35
  * Open `protocol` to `peer`, send `frame`, and read the bounded reply frame.
67
36
  *
@@ -69,7 +38,7 @@ async function openStream(node: Libp2p, peer: PeerId, protocol: string): Promise
69
38
  * `readFramed` self-times-out at 5s and `dialProtocol` falls back to libp2p's default dial
70
39
  * timeout (~30s) — so an unresponsive peer is slow, not hung. If a caller ever needs a tighter
71
40
  * deadline (`membership-source.fetch` walks candidate peers *sequentially*, so its worst case is
72
- * peers × dial-timeout), thread a signal through {@link openStream} the way
41
+ * peers × dial-timeout), pass one through {@link openProtocolStream}'s `signal` the way
73
42
  * `libp2p-key-network.ts#connect` does.
74
43
  */
75
44
  export async function requestResponse(
@@ -81,7 +50,8 @@ export async function requestResponse(
81
50
  ): Promise<Uint8Array> {
82
51
  let stream: Stream | undefined;
83
52
  try {
84
- stream = await openStream(node, peer, protocol);
53
+ // `negotiateFully` deliberately omitted see the accepted tradeoff in the module docblock.
54
+ stream = await openProtocolStream(node, peer, protocol);
85
55
  sendFramed(stream, frame);
86
56
  await stream.close();
87
57
  return await readFramed(stream, maxBytes);
@@ -100,7 +70,8 @@ export async function requestResponse(
100
70
  export async function sendOneWay(node: Libp2p, peer: PeerId, protocol: string, frame: Uint8Array): Promise<void> {
101
71
  let stream: Stream | undefined;
102
72
  try {
103
- stream = await openStream(node, peer, protocol);
73
+ // `negotiateFully` deliberately omitted see the accepted tradeoff in the module docblock.
74
+ stream = await openProtocolStream(node, peer, protocol);
104
75
  sendFramed(stream, frame);
105
76
  await stream.close();
106
77
  } finally {
@@ -128,7 +99,7 @@ export function handleRequestResponse(
128
99
  handle: (frame: Uint8Array, from: PeerId) => Promise<Uint8Array | undefined>,
129
100
  maxBytes = DEFAULT_STREAM_MAX_BYTES,
130
101
  ): void {
131
- void node.handle(protocol, (stream: Stream, connection: Connection) => {
102
+ void registerProtocolHandler(node, protocol, (stream: Stream, connection: Connection) => {
132
103
  void (async (): Promise<void> => {
133
104
  try {
134
105
  const frame = await readFramed(stream, maxBytes);
@@ -40,13 +40,6 @@ export type CollectionEnv = {
40
40
  readonly collectionId: CollectionId;
41
41
  readonly log: Log<unknown>;
42
42
  readonly createBlockStorage: (blockId: BlockId) => IBlockStorage;
43
- /**
44
- * Optional per-block commit-latch runner threaded into each cascade child's {@link applyInvalidation}
45
- * so the child's compensating write serializes against a concurrent commit on the same block — the
46
- * same mutual-exclusion the root apply gets (see `InvalidationContext.withBlockCommitLatch`). Omitted
47
- * → child writes run unlatched (today's behavior).
48
- */
49
- readonly withBlockCommitLatch?: <T>(blockId: BlockId, fn: () => Promise<T>) => Promise<T>;
50
43
  };
51
44
 
52
45
  // ─── Invalidated (blockId, revision) pairs ───
@@ -213,7 +206,13 @@ export type CascadeEscalation = {
213
206
  readonly collections: ReadonlyArray<CollectionId>;
214
207
  /** Read-dependents left un-cascaded at the horizon — surfaced, never silently dropped. */
215
208
  readonly remainder: ReadonlyArray<CascadeStanding>;
216
- /** Candidates that could not be re-evaluated (e.g. legacy entries with no engine to re-execute them). */
209
+ /**
210
+ * Candidates whose fate could not be decided — a legacy entry with no persisted read set (no engine
211
+ * to re-execute it), or one the cascade decided to invalidate but whose `applyInvalidation` did not
212
+ * apply (a wholesale-refused compensating write, reason `stale-revision`; any other non-applied
213
+ * reason lands here too), so the reversal did not happen and the dependent must not be dropped from
214
+ * the frontier as if it were fine.
215
+ */
217
216
  readonly unevaluable: ReadonlyArray<CascadeStanding>;
218
217
  };
219
218
 
@@ -290,6 +289,18 @@ export async function cascadeInvalidate(input: CascadeInput): Promise<CascadeRes
290
289
  const children: CascadeChild[] = [];
291
290
  const affectedCollections = new Set<CollectionId>(input.seed.map(s => s.collectionId));
292
291
  const unevaluable: CascadeStanding[] = [];
292
+ /**
293
+ * Park a candidate whose fate the cascade could not decide, at most once. Both callers below mean
294
+ * the same thing — this dependent is neither retained nor reverted, so it must not leave the
295
+ * frontier as if it were fine — and both must mark its collection affected, or the `unevaluable`
296
+ * escalation would not name the collection that needs the re-sync.
297
+ */
298
+ const markUnevaluable = (cand: CascadeCandidate) => {
299
+ if (!unevaluable.some(u => u.collectionId === cand.collectionId && u.actionId === cand.actionId)) {
300
+ unevaluable.push(standing(cand));
301
+ }
302
+ affectedCollections.add(cand.collectionId);
303
+ };
293
304
  let retained: CascadeStanding[] = [];
294
305
  let rounds = 0;
295
306
  let escalation: CascadeEscalation | undefined;
@@ -335,10 +346,7 @@ export async function cascadeInvalidate(input: CascadeInput): Promise<CascadeRes
335
346
  continue;
336
347
  }
337
348
  if (verdict === 'unevaluable') {
338
- if (!unevaluable.some(u => u.collectionId === cand.collectionId && u.actionId === cand.actionId)) {
339
- unevaluable.push(standing(cand));
340
- }
341
- affectedCollections.add(cand.collectionId);
349
+ markUnevaluable(cand);
342
350
  continue;
343
351
  }
344
352
 
@@ -354,7 +362,7 @@ export async function cascadeInvalidate(input: CascadeInput): Promise<CascadeRes
354
362
 
355
363
  const env = cand.env;
356
364
  const result = await applyInvalidation(
357
- { log: env.log, createBlockStorage: env.createBlockStorage, withBlockCommitLatch: env.withBlockCommitLatch },
365
+ { log: env.log, createBlockStorage: env.createBlockStorage },
358
366
  {
359
367
  invalidatedActionId: cand.actionId,
360
368
  invalidatedRev: cand.rev,
@@ -370,9 +378,17 @@ export async function cascadeInvalidate(input: CascadeInput): Promise<CascadeRes
370
378
  // exists (prior cascade run / restart). Treat it as invalidated and reuse its reverted blocks
371
379
  // so the frontier still grows and the cascade reconverges without a second entry.
372
380
  if (!result.applied && result.reason !== 'already-applied') {
373
- // invalid-certificate should be impossible: the child verifies the root proof against the
374
- // root's target (rootCertificateTarget), and the root proof is a valid challenger-wins cert.
381
+ // The child was decided-to-invalidate but the reversal did not happen, so we do NOT know
382
+ // whether this dependent still holds it must not be dropped from the frontier as if it
383
+ // were fine. Deliberately keyed on "not applied", not on a specific reason, so a reason
384
+ // added later cannot re-open the silent-drop hole:
385
+ // - `stale-revision` — the compensating write was refused wholesale (nothing written,
386
+ // nothing appended). The reachable case.
387
+ // - `invalid-certificate` — should be impossible here (the child verifies the ROOT proof
388
+ // against the root's target, and that proof is a valid challenger-wins cert), but if it
389
+ // ever fires, an undecided dependent is the honest report.
375
390
  log('child-apply-rejected actionId=%s reason=%s', cand.actionId, result.reason);
391
+ markUnevaluable(cand);
376
392
  continue;
377
393
  }
378
394