@optimystic/db-p2p 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/src/cluster/client.d.ts +10 -0
  2. package/dist/src/cluster/client.d.ts.map +1 -1
  3. package/dist/src/cluster/client.js +30 -1
  4. package/dist/src/cluster/client.js.map +1 -1
  5. package/dist/src/cluster/cluster-repo.d.ts +39 -14
  6. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-repo.js +226 -119
  8. package/dist/src/cluster/cluster-repo.js.map +1 -1
  9. package/dist/src/cluster/service.d.ts +32 -1
  10. package/dist/src/cluster/service.d.ts.map +1 -1
  11. package/dist/src/cluster/service.js +43 -2
  12. package/dist/src/cluster/service.js.map +1 -1
  13. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  14. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  15. package/dist/src/cohort-topic/stream-util.js +56 -10
  16. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  17. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  18. package/dist/src/dispute/dispute-service.js +9 -3
  19. package/dist/src/dispute/dispute-service.js.map +1 -1
  20. package/dist/src/index.d.ts +3 -0
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +3 -0
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/libp2p-key-network.d.ts +88 -2
  25. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  26. package/dist/src/libp2p-key-network.js +134 -28
  27. package/dist/src/libp2p-key-network.js.map +1 -1
  28. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  29. package/dist/src/libp2p-node-base.js +25 -1
  30. package/dist/src/libp2p-node-base.js.map +1 -1
  31. package/dist/src/logger.d.ts +17 -1
  32. package/dist/src/logger.d.ts.map +1 -1
  33. package/dist/src/logger.js +19 -2
  34. package/dist/src/logger.js.map +1 -1
  35. package/dist/src/owned-block-seed.d.ts +6 -3
  36. package/dist/src/owned-block-seed.d.ts.map +1 -1
  37. package/dist/src/owned-block-seed.js +16 -3
  38. package/dist/src/owned-block-seed.js.map +1 -1
  39. package/dist/src/peer-address-book.d.ts +72 -0
  40. package/dist/src/peer-address-book.d.ts.map +1 -0
  41. package/dist/src/peer-address-book.js +123 -0
  42. package/dist/src/peer-address-book.js.map +1 -0
  43. package/dist/src/repo/client.d.ts.map +1 -1
  44. package/dist/src/repo/client.js +11 -2
  45. package/dist/src/repo/client.js.map +1 -1
  46. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  47. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  48. package/dist/src/repo/cluster-coordinator.js +95 -3
  49. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  50. package/dist/src/repo/coordinator-repo.d.ts +62 -9
  51. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  52. package/dist/src/repo/coordinator-repo.js +242 -73
  53. package/dist/src/repo/coordinator-repo.js.map +1 -1
  54. package/dist/src/rn.d.ts +3 -0
  55. package/dist/src/rn.d.ts.map +1 -1
  56. package/dist/src/rn.js +3 -0
  57. package/dist/src/rn.js.map +1 -1
  58. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  59. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  60. package/dist/src/storage/cached-raw-storage.js +152 -0
  61. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  62. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  63. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  64. package/dist/src/storage/cached-store-driver.js +775 -0
  65. package/dist/src/storage/cached-store-driver.js.map +1 -0
  66. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  67. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  68. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  69. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  70. package/dist/src/storage/shared-cache-pool.js +354 -0
  71. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  72. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  73. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  74. package/dist/src/testing/raw-storage-conformance.js +35 -2
  75. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  76. package/package.json +3 -3
  77. package/readme.md +668 -668
  78. package/src/cluster/block-transfer.ts +424 -424
  79. package/src/cluster/client.ts +119 -88
  80. package/src/cluster/cluster-error.ts +64 -64
  81. package/src/cluster/cluster-policy.ts +203 -203
  82. package/src/cluster/cluster-repo.ts +242 -122
  83. package/src/cluster/cluster-size-coupling.ts +45 -45
  84. package/src/cluster/commit-cert.ts +139 -139
  85. package/src/cluster/i-transaction-state-store.ts +43 -43
  86. package/src/cluster/memory-transaction-state-store.ts +56 -56
  87. package/src/cluster/peer-key-binding.ts +37 -37
  88. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  89. package/src/cluster/quorum-restore.ts +223 -223
  90. package/src/cluster/reconcile-block.ts +203 -203
  91. package/src/cluster/service.ts +293 -241
  92. package/src/cluster/supermajority-coupling.ts +37 -37
  93. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  94. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  95. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  96. package/src/cohort-topic/change-bridge.ts +109 -109
  97. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  98. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  99. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  100. package/src/cohort-topic/host.ts +2901 -2901
  101. package/src/cohort-topic/index.ts +13 -13
  102. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  103. package/src/cohort-topic/membership-source.ts +68 -68
  104. package/src/cohort-topic/peer-codec.ts +31 -31
  105. package/src/cohort-topic/peer-sig.ts +86 -86
  106. package/src/cohort-topic/protocols.ts +71 -71
  107. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  108. package/src/cohort-topic/size-estimator.ts +16 -16
  109. package/src/cohort-topic/stream-util.ts +135 -87
  110. package/src/cohort-topic/threshold-crypto.ts +239 -239
  111. package/src/cohort-topic/topic-router.ts +77 -77
  112. package/src/dispute/arbitrator-selection.ts +138 -138
  113. package/src/dispute/cascade.ts +524 -524
  114. package/src/dispute/dispute-service.ts +11 -5
  115. package/src/dispute/invalidation.ts +625 -625
  116. package/src/inbound-authorization.ts +190 -190
  117. package/src/index.ts +52 -49
  118. package/src/libp2p-key-network.ts +1120 -990
  119. package/src/libp2p-node-base.ts +1675 -1651
  120. package/src/libp2p-node-rn.ts +30 -30
  121. package/src/libp2p-node.ts +36 -36
  122. package/src/logger.ts +19 -2
  123. package/src/matchmaking/aggregate-counts.ts +104 -104
  124. package/src/matchmaking/index.ts +20 -20
  125. package/src/matchmaking/module.ts +363 -363
  126. package/src/matchmaking/protocols.ts +51 -51
  127. package/src/matchmaking/provider-manager.ts +95 -95
  128. package/src/matchmaking/query-handler.ts +88 -88
  129. package/src/matchmaking/query-transport.ts +492 -492
  130. package/src/matchmaking/seeker-manager.ts +64 -64
  131. package/src/matchmaking/seeker-walk-client.ts +293 -293
  132. package/src/matchmaking/traffic-validation.ts +195 -195
  133. package/src/optimystic-node.ts +36 -36
  134. package/src/owned-block-seed.ts +53 -40
  135. package/src/peer-address-book.ts +149 -0
  136. package/src/protocol-limits.ts +33 -33
  137. package/src/reactivity/forwarder-host.ts +438 -438
  138. package/src/reactivity/index.ts +19 -19
  139. package/src/reactivity/notify-transport.ts +144 -144
  140. package/src/reactivity/origination-manager.ts +192 -192
  141. package/src/reactivity/protocols.ts +61 -61
  142. package/src/reactivity/push-state-gossip.ts +291 -291
  143. package/src/reactivity/recover-transport.ts +408 -408
  144. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  145. package/src/reactivity/subscriber-registry.ts +96 -96
  146. package/src/reactivity/subscription-manager.ts +450 -450
  147. package/src/reactivity/topic-bytes.ts +37 -37
  148. package/src/repo/client.ts +12 -2
  149. package/src/repo/cluster-coordinator.ts +99 -3
  150. package/src/repo/coordinator-repo.ts +281 -74
  151. package/src/repo/types.ts +7 -7
  152. package/src/rn.ts +39 -36
  153. package/src/rpc-deadline.ts +45 -45
  154. package/src/storage/arachnode-partition.ts +74 -74
  155. package/src/storage/cached-raw-storage.ts +180 -0
  156. package/src/storage/cached-store-driver.ts +859 -0
  157. package/src/storage/i-kv-store.ts +8 -8
  158. package/src/storage/i-raw-storage.ts +12 -5
  159. package/src/storage/kv-raw-storage.ts +135 -135
  160. package/src/storage/memory-kv-store.ts +28 -28
  161. package/src/storage/memory-storage.ts +25 -25
  162. package/src/storage/memory-store-driver.ts +157 -157
  163. package/src/storage/raw-store-codec.ts +42 -42
  164. package/src/storage/raw-store-driver.ts +80 -80
  165. package/src/storage/ring-selector.ts +317 -317
  166. package/src/storage/ring-shift-coordinator.ts +271 -271
  167. package/src/storage/shared-cache-pool.ts +452 -0
  168. package/src/storage/storage-repo.ts +1014 -1014
  169. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  170. package/src/testing/index.ts +8 -8
  171. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  172. package/src/testing/raw-storage-conformance.ts +453 -417
  173. package/src/testing/reactivity-mesh-harness.ts +922 -922
  174. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  175. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  176. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  177. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -1,19 +1,19 @@
1
- /**
2
- * Reactivity — db-p2p wiring to the cohort-topic substrate.
3
- *
4
- * The db-core `reactivity` module owns the wire codecs, topic anchor, config, origination assembler,
5
- * forwarder/dedupe/replay logic, and subscriber-side verify/deliver; these managers bind that to the
6
- * participant-facing `CohortTopicService` (subscribe/renew/withdraw at tier T3) and install the
7
- * `onLocalCommit` origination hook. See `docs/reactivity.md` §Subscription / §Notification origination.
8
- */
9
-
10
- export * from "./topic-bytes.js";
11
- export * from "./protocols.js";
12
- export * from "./notify-transport.js";
13
- export * from "./recover-transport.js";
14
- export * from "./subscription-manager.js";
15
- export * from "./rotation-rereg-scheduler.js";
16
- export * from "./origination-manager.js";
17
- export * from "./forwarder-host.js";
18
- export * from "./push-state-gossip.js";
19
- export * from "./subscriber-registry.js";
1
+ /**
2
+ * Reactivity — db-p2p wiring to the cohort-topic substrate.
3
+ *
4
+ * The db-core `reactivity` module owns the wire codecs, topic anchor, config, origination assembler,
5
+ * forwarder/dedupe/replay logic, and subscriber-side verify/deliver; these managers bind that to the
6
+ * participant-facing `CohortTopicService` (subscribe/renew/withdraw at tier T3) and install the
7
+ * `onLocalCommit` origination hook. See `docs/reactivity.md` §Subscription / §Notification origination.
8
+ */
9
+
10
+ export * from "./topic-bytes.js";
11
+ export * from "./protocols.js";
12
+ export * from "./notify-transport.js";
13
+ export * from "./recover-transport.js";
14
+ export * from "./subscription-manager.js";
15
+ export * from "./rotation-rereg-scheduler.js";
16
+ export * from "./origination-manager.js";
17
+ export * from "./forwarder-host.js";
18
+ export * from "./push-state-gossip.js";
19
+ export * from "./subscriber-registry.js";
@@ -1,144 +1,144 @@
1
- /**
2
- * Reactivity notify transport — the one-way `NotificationV1` delivery primitive (`docs/reactivity.md`
3
- * §Propagation).
4
- *
5
- * Unlike the cohort-gossip transport (which *broadcasts* a frame to a FRET-assembled cohort), notify is
6
- * **unicast**: the fan-out orchestration above this layer (the `reactivity-forwarder-host` ticket) decides
7
- * who to dial and calls {@link ReactivityNotifyTransport.send} once per named target. This module owns only
8
- * the framing + dial + inbound-decode plumbing — no fan-out, no role decision, no gossip.
9
- *
10
- * The db-core `NotificationV1` codec ({@link encodeNotificationV1} / {@link decodeNotificationV1}) does the
11
- * length-prefixed JSON framing; this layer rides one self-delimiting frame each way over the notify
12
- * protocol, reusing the cohort-topic {@link sendOneWay} / {@link readAllBounded} stream lifecycle so the
13
- * two protocol families behave identically on the wire.
14
- *
15
- * Failure isolation is the load-bearing property: notify is fire-and-forget and hint-only, so a dead /
16
- * unreachable target's rejection is swallowed (logged), never propagated to the caller's fan-out loop or a
17
- * commit. There is no reply frame — a handler that tried to send one back would desync the dialer's
18
- * {@link sendOneWay} (which closes after send).
19
- */
20
-
21
- import type { NotificationV1, PeerRef } from "@optimystic/db-core";
22
- import { encodeNotificationV1, decodeNotificationV1 } from "@optimystic/db-core";
23
- import type { Libp2p } from "libp2p";
24
- import type { Connection, Stream } from "@libp2p/interface";
25
- import { peerIdFromString } from "@libp2p/peer-id";
26
- import { readAllBounded } from "p2p-fret";
27
- import { peerIdToBytes } from "../cohort-topic/peer-codec.js";
28
- import { sendOneWay, DEFAULT_STREAM_MAX_BYTES } from "../cohort-topic/stream-util.js";
29
- import { PROTOCOL_REACTIVITY_NOTIFY } from "./protocols.js";
30
- import { createLogger } from "../logger.js";
31
-
32
- const log = createLogger("reactivity-notify");
33
-
34
- /** One-way `NotificationV1` transport: unicast send, inbound subscribe, and the host's deliver seam. */
35
- export interface ReactivityNotifyTransport {
36
- /**
37
- * Frame `n` ({@link encodeNotificationV1}) and dial `target` (peer-id string) over the notify protocol.
38
- * Fire-and-forget, failure-isolated: a dead/unreachable target's rejection is swallowed (logged), never
39
- * propagated to the caller's fan-out loop.
40
- */
41
- send(target: string, n: NotificationV1): Promise<void>;
42
- /** Subscribe to inbound notifications (after decode); returns an unsubscribe handle. */
43
- onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void;
44
- /** Feed an inbound notify frame (called by the host's notify protocol handler). */
45
- deliver(fromPeerId: string, frame: Uint8Array): void;
46
- }
47
-
48
- /** Construction options for {@link Libp2pReactivityNotifyTransport}. */
49
- export interface ReactivityNotifyTransportOptions {
50
- /** Notify protocol ID; default {@link PROTOCOL_REACTIVITY_NOTIFY}. */
51
- readonly notifyProtocol?: string;
52
- /** Per-frame ceiling for the inbound decode bound. Default {@link DEFAULT_STREAM_MAX_BYTES}. */
53
- readonly maxBytes?: number;
54
- /** This node's peer-id string; when set, {@link Libp2pReactivityNotifyTransport.send} never dials self. */
55
- readonly selfPeerId?: string;
56
- }
57
-
58
- /**
59
- * libp2p-backed {@link ReactivityNotifyTransport}: {@link send} frames + dials a single target over
60
- * `/optimystic/reactivity/1.0.0/notify` (fire-and-forget, failure-isolated); inbound frames arrive through
61
- * the host's notify protocol handler ({@link registerNotifyHandler}), which calls {@link deliver}.
62
- * Subscribers registered via {@link onNotification} see every decoded notification.
63
- */
64
- export class Libp2pReactivityNotifyTransport implements ReactivityNotifyTransport {
65
- private readonly handlers = new Set<(from: PeerRef, n: NotificationV1) => void>();
66
- private readonly notifyProtocol: string;
67
- private readonly maxBytes: number;
68
- private readonly selfPeerId?: string;
69
-
70
- constructor(private readonly node: Libp2p, options: ReactivityNotifyTransportOptions = {}) {
71
- this.notifyProtocol = options.notifyProtocol ?? PROTOCOL_REACTIVITY_NOTIFY;
72
- this.maxBytes = options.maxBytes ?? DEFAULT_STREAM_MAX_BYTES;
73
- this.selfPeerId = options.selfPeerId;
74
- }
75
-
76
- send(target: string, n: NotificationV1): Promise<void> {
77
- if (this.selfPeerId !== undefined && target === this.selfPeerId) {
78
- // Never dial self; a co-located subscriber is delivered in-process by the forwarder host.
79
- return Promise.resolve();
80
- }
81
- try {
82
- const frame = encodeNotificationV1(n);
83
- return sendOneWay(this.node, peerIdFromString(target), this.notifyProtocol, frame).catch((err: unknown) => {
84
- // Best-effort, failure-isolated: a dead/unreachable target must not break the fan-out or a commit.
85
- log("send to %s failed (swallowed): %o", target, err);
86
- });
87
- } catch (err) {
88
- // Malformed notification or peer-id string: log + drop, never reject (reactivity is hint-only).
89
- log("dropped a send to %s: %o", target, err);
90
- return Promise.resolve();
91
- }
92
- }
93
-
94
- onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void {
95
- this.handlers.add(handler);
96
- return () => this.handlers.delete(handler);
97
- }
98
-
99
- /** Feed an inbound notify frame (called by the host's notify protocol handler). */
100
- deliver(fromPeerId: string, frame: Uint8Array): void {
101
- let n: NotificationV1;
102
- try {
103
- n = decodeNotificationV1(frame, this.maxBytes);
104
- } catch (err) {
105
- // A malformed frame must never throw out of a stream handler: log + drop.
106
- log("dropped an undecodable inbound frame from %s: %o", fromPeerId, err);
107
- return;
108
- }
109
- const from: PeerRef = { id: peerIdToBytes(fromPeerId) };
110
- for (const handler of this.handlers) {
111
- handler(from, n);
112
- }
113
- }
114
- }
115
-
116
- /**
117
- * Register the inbound notify protocol handler: read one bounded frame and hand it to
118
- * {@link ReactivityNotifyTransport.deliver}, then close. One-way — no reply frame (notify is strictly
119
- * fire-and-forget; a reply would desync the dialer's {@link sendOneWay}). Mirrors the cohort-topic
120
- * one-way handlers: a read error aborts the stream, and {@link ReactivityNotifyTransport.deliver}
121
- * swallows a decode failure, so the handler never throws on the stream.
122
- */
123
- export function registerNotifyHandler(
124
- node: Libp2p,
125
- protocol: string,
126
- transport: ReactivityNotifyTransport,
127
- maxBytes = DEFAULT_STREAM_MAX_BYTES,
128
- ): void {
129
- void node.handle(protocol, (stream: Stream, connection: Connection) => {
130
- void (async (): Promise<void> => {
131
- try {
132
- const frame = await readAllBounded(stream, maxBytes);
133
- transport.deliver(connection.remotePeer.toString(), frame);
134
- await stream.close();
135
- } catch {
136
- try {
137
- stream.abort(new Error("reactivity notify stream handler error"));
138
- } catch {
139
- /* already aborted */
140
- }
141
- }
142
- })();
143
- });
144
- }
1
+ /**
2
+ * Reactivity notify transport — the one-way `NotificationV1` delivery primitive (`docs/reactivity.md`
3
+ * §Propagation).
4
+ *
5
+ * Unlike the cohort-gossip transport (which *broadcasts* a frame to a FRET-assembled cohort), notify is
6
+ * **unicast**: the fan-out orchestration above this layer (the `reactivity-forwarder-host` ticket) decides
7
+ * who to dial and calls {@link ReactivityNotifyTransport.send} once per named target. This module owns only
8
+ * the framing + dial + inbound-decode plumbing — no fan-out, no role decision, no gossip.
9
+ *
10
+ * The db-core `NotificationV1` codec ({@link encodeNotificationV1} / {@link decodeNotificationV1}) does the
11
+ * length-prefixed JSON framing; this layer rides one self-delimiting frame each way over the notify
12
+ * protocol, reusing the cohort-topic {@link sendOneWay} / {@link readAllBounded} stream lifecycle so the
13
+ * two protocol families behave identically on the wire.
14
+ *
15
+ * Failure isolation is the load-bearing property: notify is fire-and-forget and hint-only, so a dead /
16
+ * unreachable target's rejection is swallowed (logged), never propagated to the caller's fan-out loop or a
17
+ * commit. There is no reply frame — a handler that tried to send one back would desync the dialer's
18
+ * {@link sendOneWay} (which closes after send).
19
+ */
20
+
21
+ import type { NotificationV1, PeerRef } from "@optimystic/db-core";
22
+ import { encodeNotificationV1, decodeNotificationV1 } from "@optimystic/db-core";
23
+ import type { Libp2p } from "libp2p";
24
+ import type { Connection, Stream } from "@libp2p/interface";
25
+ import { peerIdFromString } from "@libp2p/peer-id";
26
+ import { readAllBounded } from "p2p-fret";
27
+ import { peerIdToBytes } from "../cohort-topic/peer-codec.js";
28
+ import { sendOneWay, DEFAULT_STREAM_MAX_BYTES } from "../cohort-topic/stream-util.js";
29
+ import { PROTOCOL_REACTIVITY_NOTIFY } from "./protocols.js";
30
+ import { createLogger } from "../logger.js";
31
+
32
+ const log = createLogger("reactivity-notify");
33
+
34
+ /** One-way `NotificationV1` transport: unicast send, inbound subscribe, and the host's deliver seam. */
35
+ export interface ReactivityNotifyTransport {
36
+ /**
37
+ * Frame `n` ({@link encodeNotificationV1}) and dial `target` (peer-id string) over the notify protocol.
38
+ * Fire-and-forget, failure-isolated: a dead/unreachable target's rejection is swallowed (logged), never
39
+ * propagated to the caller's fan-out loop.
40
+ */
41
+ send(target: string, n: NotificationV1): Promise<void>;
42
+ /** Subscribe to inbound notifications (after decode); returns an unsubscribe handle. */
43
+ onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void;
44
+ /** Feed an inbound notify frame (called by the host's notify protocol handler). */
45
+ deliver(fromPeerId: string, frame: Uint8Array): void;
46
+ }
47
+
48
+ /** Construction options for {@link Libp2pReactivityNotifyTransport}. */
49
+ export interface ReactivityNotifyTransportOptions {
50
+ /** Notify protocol ID; default {@link PROTOCOL_REACTIVITY_NOTIFY}. */
51
+ readonly notifyProtocol?: string;
52
+ /** Per-frame ceiling for the inbound decode bound. Default {@link DEFAULT_STREAM_MAX_BYTES}. */
53
+ readonly maxBytes?: number;
54
+ /** This node's peer-id string; when set, {@link Libp2pReactivityNotifyTransport.send} never dials self. */
55
+ readonly selfPeerId?: string;
56
+ }
57
+
58
+ /**
59
+ * libp2p-backed {@link ReactivityNotifyTransport}: {@link send} frames + dials a single target over
60
+ * `/optimystic/reactivity/1.0.0/notify` (fire-and-forget, failure-isolated); inbound frames arrive through
61
+ * the host's notify protocol handler ({@link registerNotifyHandler}), which calls {@link deliver}.
62
+ * Subscribers registered via {@link onNotification} see every decoded notification.
63
+ */
64
+ export class Libp2pReactivityNotifyTransport implements ReactivityNotifyTransport {
65
+ private readonly handlers = new Set<(from: PeerRef, n: NotificationV1) => void>();
66
+ private readonly notifyProtocol: string;
67
+ private readonly maxBytes: number;
68
+ private readonly selfPeerId?: string;
69
+
70
+ constructor(private readonly node: Libp2p, options: ReactivityNotifyTransportOptions = {}) {
71
+ this.notifyProtocol = options.notifyProtocol ?? PROTOCOL_REACTIVITY_NOTIFY;
72
+ this.maxBytes = options.maxBytes ?? DEFAULT_STREAM_MAX_BYTES;
73
+ this.selfPeerId = options.selfPeerId;
74
+ }
75
+
76
+ send(target: string, n: NotificationV1): Promise<void> {
77
+ if (this.selfPeerId !== undefined && target === this.selfPeerId) {
78
+ // Never dial self; a co-located subscriber is delivered in-process by the forwarder host.
79
+ return Promise.resolve();
80
+ }
81
+ try {
82
+ const frame = encodeNotificationV1(n);
83
+ return sendOneWay(this.node, peerIdFromString(target), this.notifyProtocol, frame).catch((err: unknown) => {
84
+ // Best-effort, failure-isolated: a dead/unreachable target must not break the fan-out or a commit.
85
+ log("send to %s failed (swallowed): %o", target, err);
86
+ });
87
+ } catch (err) {
88
+ // Malformed notification or peer-id string: log + drop, never reject (reactivity is hint-only).
89
+ log("dropped a send to %s: %o", target, err);
90
+ return Promise.resolve();
91
+ }
92
+ }
93
+
94
+ onNotification(handler: (from: PeerRef, n: NotificationV1) => void): () => void {
95
+ this.handlers.add(handler);
96
+ return () => this.handlers.delete(handler);
97
+ }
98
+
99
+ /** Feed an inbound notify frame (called by the host's notify protocol handler). */
100
+ deliver(fromPeerId: string, frame: Uint8Array): void {
101
+ let n: NotificationV1;
102
+ try {
103
+ n = decodeNotificationV1(frame, this.maxBytes);
104
+ } catch (err) {
105
+ // A malformed frame must never throw out of a stream handler: log + drop.
106
+ log("dropped an undecodable inbound frame from %s: %o", fromPeerId, err);
107
+ return;
108
+ }
109
+ const from: PeerRef = { id: peerIdToBytes(fromPeerId) };
110
+ for (const handler of this.handlers) {
111
+ handler(from, n);
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Register the inbound notify protocol handler: read one bounded frame and hand it to
118
+ * {@link ReactivityNotifyTransport.deliver}, then close. One-way — no reply frame (notify is strictly
119
+ * fire-and-forget; a reply would desync the dialer's {@link sendOneWay}). Mirrors the cohort-topic
120
+ * one-way handlers: a read error aborts the stream, and {@link ReactivityNotifyTransport.deliver}
121
+ * swallows a decode failure, so the handler never throws on the stream.
122
+ */
123
+ export function registerNotifyHandler(
124
+ node: Libp2p,
125
+ protocol: string,
126
+ transport: ReactivityNotifyTransport,
127
+ maxBytes = DEFAULT_STREAM_MAX_BYTES,
128
+ ): void {
129
+ void node.handle(protocol, (stream: Stream, connection: Connection) => {
130
+ void (async (): Promise<void> => {
131
+ try {
132
+ const frame = await readAllBounded(stream, maxBytes);
133
+ transport.deliver(connection.remotePeer.toString(), frame);
134
+ await stream.close();
135
+ } catch {
136
+ try {
137
+ stream.abort(new Error("reactivity notify stream handler error"));
138
+ } catch {
139
+ /* already aborted */
140
+ }
141
+ }
142
+ })();
143
+ });
144
+ }