@optimystic/db-p2p 0.21.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 (219) 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-policy.d.ts +13 -2
  6. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  7. package/dist/src/cluster/cluster-policy.js +51 -4
  8. package/dist/src/cluster/cluster-policy.js.map +1 -1
  9. package/dist/src/cluster/cluster-repo.d.ts +42 -17
  10. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  11. package/dist/src/cluster/cluster-repo.js +229 -122
  12. package/dist/src/cluster/cluster-repo.js.map +1 -1
  13. package/dist/src/cluster/cluster-size-coupling.d.ts +28 -0
  14. package/dist/src/cluster/cluster-size-coupling.d.ts.map +1 -0
  15. package/dist/src/cluster/cluster-size-coupling.js +35 -0
  16. package/dist/src/cluster/cluster-size-coupling.js.map +1 -0
  17. package/dist/src/cluster/quorum-restore.d.ts +6 -0
  18. package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
  19. package/dist/src/cluster/quorum-restore.js +1 -1
  20. package/dist/src/cluster/quorum-restore.js.map +1 -1
  21. package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
  22. package/dist/src/cluster/reconcile-block.js +15 -3
  23. package/dist/src/cluster/reconcile-block.js.map +1 -1
  24. package/dist/src/cluster/service.d.ts +32 -1
  25. package/dist/src/cluster/service.d.ts.map +1 -1
  26. package/dist/src/cluster/service.js +43 -2
  27. package/dist/src/cluster/service.js.map +1 -1
  28. package/dist/src/cohort-topic/stream-util.d.ts +22 -6
  29. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  30. package/dist/src/cohort-topic/stream-util.js +56 -10
  31. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  32. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  33. package/dist/src/dispute/dispute-service.js +9 -3
  34. package/dist/src/dispute/dispute-service.js.map +1 -1
  35. package/dist/src/index.d.ts +5 -0
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +5 -0
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/libp2p-key-network.d.ts +134 -7
  40. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  41. package/dist/src/libp2p-key-network.js +174 -37
  42. package/dist/src/libp2p-key-network.js.map +1 -1
  43. package/dist/src/libp2p-node-base.d.ts +3 -2
  44. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  45. package/dist/src/libp2p-node-base.js +859 -778
  46. package/dist/src/libp2p-node-base.js.map +1 -1
  47. package/dist/src/libp2p-node-rn.d.ts +2 -2
  48. package/dist/src/libp2p-node-rn.d.ts.map +1 -1
  49. package/dist/src/libp2p-node-rn.js.map +1 -1
  50. package/dist/src/libp2p-node.d.ts +2 -2
  51. package/dist/src/libp2p-node.d.ts.map +1 -1
  52. package/dist/src/libp2p-node.js.map +1 -1
  53. package/dist/src/logger.d.ts +17 -1
  54. package/dist/src/logger.d.ts.map +1 -1
  55. package/dist/src/logger.js +19 -2
  56. package/dist/src/logger.js.map +1 -1
  57. package/dist/src/network/network-manager-service.d.ts +2 -0
  58. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  59. package/dist/src/network/network-manager-service.js +4 -0
  60. package/dist/src/network/network-manager-service.js.map +1 -1
  61. package/dist/src/optimystic-node.d.ts +35 -0
  62. package/dist/src/optimystic-node.d.ts.map +1 -0
  63. package/dist/src/optimystic-node.js +2 -0
  64. package/dist/src/optimystic-node.js.map +1 -0
  65. package/dist/src/owned-block-seed.d.ts +6 -3
  66. package/dist/src/owned-block-seed.d.ts.map +1 -1
  67. package/dist/src/owned-block-seed.js +16 -3
  68. package/dist/src/owned-block-seed.js.map +1 -1
  69. package/dist/src/peer-address-book.d.ts +72 -0
  70. package/dist/src/peer-address-book.d.ts.map +1 -0
  71. package/dist/src/peer-address-book.js +123 -0
  72. package/dist/src/peer-address-book.js.map +1 -0
  73. package/dist/src/repo/client.d.ts.map +1 -1
  74. package/dist/src/repo/client.js +11 -2
  75. package/dist/src/repo/client.js.map +1 -1
  76. package/dist/src/repo/cluster-coordinator.d.ts +30 -0
  77. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  78. package/dist/src/repo/cluster-coordinator.js +95 -3
  79. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  80. package/dist/src/repo/coordinator-repo.d.ts +78 -14
  81. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  82. package/dist/src/repo/coordinator-repo.js +266 -81
  83. package/dist/src/repo/coordinator-repo.js.map +1 -1
  84. package/dist/src/rn.d.ts +5 -0
  85. package/dist/src/rn.d.ts.map +1 -1
  86. package/dist/src/rn.js +5 -0
  87. package/dist/src/rn.js.map +1 -1
  88. package/dist/src/storage/block-storage.d.ts.map +1 -1
  89. package/dist/src/storage/block-storage.js +57 -5
  90. package/dist/src/storage/block-storage.js.map +1 -1
  91. package/dist/src/storage/cached-raw-storage.d.ts +83 -0
  92. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
  93. package/dist/src/storage/cached-raw-storage.js +152 -0
  94. package/dist/src/storage/cached-raw-storage.js.map +1 -0
  95. package/dist/src/storage/cached-store-driver.d.ts +186 -0
  96. package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
  97. package/dist/src/storage/cached-store-driver.js +775 -0
  98. package/dist/src/storage/cached-store-driver.js.map +1 -0
  99. package/dist/src/storage/i-block-storage.d.ts +20 -1
  100. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  101. package/dist/src/storage/i-raw-storage.d.ts +12 -5
  102. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  103. package/dist/src/storage/shared-cache-pool.d.ts +234 -0
  104. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
  105. package/dist/src/storage/shared-cache-pool.js +354 -0
  106. package/dist/src/storage/shared-cache-pool.js.map +1 -0
  107. package/dist/src/storage/storage-repo.d.ts +56 -3
  108. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  109. package/dist/src/storage/storage-repo.js +124 -18
  110. package/dist/src/storage/storage-repo.js.map +1 -1
  111. package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
  112. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  113. package/dist/src/testing/raw-storage-conformance.js +52 -2
  114. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  115. package/package.json +3 -3
  116. package/readme.md +668 -653
  117. package/src/cluster/block-transfer.ts +424 -424
  118. package/src/cluster/client.ts +119 -88
  119. package/src/cluster/cluster-error.ts +64 -64
  120. package/src/cluster/cluster-policy.ts +203 -152
  121. package/src/cluster/cluster-repo.ts +245 -125
  122. package/src/cluster/cluster-size-coupling.ts +45 -0
  123. package/src/cluster/commit-cert.ts +139 -139
  124. package/src/cluster/i-transaction-state-store.ts +43 -43
  125. package/src/cluster/memory-transaction-state-store.ts +56 -56
  126. package/src/cluster/peer-key-binding.ts +37 -37
  127. package/src/cluster/persistent-transaction-state-store.ts +92 -92
  128. package/src/cluster/quorum-restore.ts +223 -223
  129. package/src/cluster/reconcile-block.ts +203 -191
  130. package/src/cluster/service.ts +293 -241
  131. package/src/cluster/supermajority-coupling.ts +37 -37
  132. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
  133. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
  134. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
  135. package/src/cohort-topic/change-bridge.ts +109 -109
  136. package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
  137. package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
  138. package/src/cohort-topic/fret-trust-anchor.ts +153 -153
  139. package/src/cohort-topic/host.ts +2901 -2901
  140. package/src/cohort-topic/index.ts +13 -13
  141. package/src/cohort-topic/membership-publish-sink.ts +20 -20
  142. package/src/cohort-topic/membership-source.ts +68 -68
  143. package/src/cohort-topic/peer-codec.ts +31 -31
  144. package/src/cohort-topic/peer-sig.ts +86 -86
  145. package/src/cohort-topic/protocols.ts +71 -71
  146. package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
  147. package/src/cohort-topic/size-estimator.ts +16 -16
  148. package/src/cohort-topic/stream-util.ts +135 -87
  149. package/src/cohort-topic/threshold-crypto.ts +239 -239
  150. package/src/cohort-topic/topic-router.ts +77 -77
  151. package/src/dispute/arbitrator-selection.ts +138 -138
  152. package/src/dispute/cascade.ts +524 -524
  153. package/src/dispute/dispute-service.ts +11 -5
  154. package/src/dispute/invalidation.ts +625 -625
  155. package/src/inbound-authorization.ts +190 -190
  156. package/src/index.ts +52 -47
  157. package/src/libp2p-key-network.ts +1120 -958
  158. package/src/libp2p-node-base.ts +1675 -1591
  159. package/src/libp2p-node-rn.ts +30 -30
  160. package/src/libp2p-node.ts +36 -36
  161. package/src/logger.ts +19 -2
  162. package/src/matchmaking/aggregate-counts.ts +104 -104
  163. package/src/matchmaking/index.ts +20 -20
  164. package/src/matchmaking/module.ts +363 -363
  165. package/src/matchmaking/protocols.ts +51 -51
  166. package/src/matchmaking/provider-manager.ts +95 -95
  167. package/src/matchmaking/query-handler.ts +88 -88
  168. package/src/matchmaking/query-transport.ts +492 -492
  169. package/src/matchmaking/seeker-manager.ts +64 -64
  170. package/src/matchmaking/seeker-walk-client.ts +293 -293
  171. package/src/matchmaking/traffic-validation.ts +195 -195
  172. package/src/network/network-manager-service.ts +5 -0
  173. package/src/optimystic-node.ts +36 -0
  174. package/src/owned-block-seed.ts +53 -40
  175. package/src/peer-address-book.ts +149 -0
  176. package/src/protocol-limits.ts +33 -33
  177. package/src/reactivity/forwarder-host.ts +438 -438
  178. package/src/reactivity/index.ts +19 -19
  179. package/src/reactivity/notify-transport.ts +144 -144
  180. package/src/reactivity/origination-manager.ts +192 -192
  181. package/src/reactivity/protocols.ts +61 -61
  182. package/src/reactivity/push-state-gossip.ts +291 -291
  183. package/src/reactivity/recover-transport.ts +408 -408
  184. package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
  185. package/src/reactivity/subscriber-registry.ts +96 -96
  186. package/src/reactivity/subscription-manager.ts +450 -450
  187. package/src/reactivity/topic-bytes.ts +37 -37
  188. package/src/repo/client.ts +12 -2
  189. package/src/repo/cluster-coordinator.ts +99 -3
  190. package/src/repo/coordinator-repo.ts +305 -82
  191. package/src/repo/types.ts +7 -7
  192. package/src/rn.ts +39 -34
  193. package/src/rpc-deadline.ts +45 -45
  194. package/src/storage/arachnode-partition.ts +74 -74
  195. package/src/storage/block-storage.ts +59 -6
  196. package/src/storage/cached-raw-storage.ts +180 -0
  197. package/src/storage/cached-store-driver.ts +859 -0
  198. package/src/storage/i-block-storage.ts +20 -1
  199. package/src/storage/i-kv-store.ts +8 -8
  200. package/src/storage/i-raw-storage.ts +12 -5
  201. package/src/storage/kv-raw-storage.ts +135 -135
  202. package/src/storage/memory-kv-store.ts +28 -28
  203. package/src/storage/memory-storage.ts +25 -25
  204. package/src/storage/memory-store-driver.ts +157 -157
  205. package/src/storage/raw-store-codec.ts +42 -42
  206. package/src/storage/raw-store-driver.ts +80 -80
  207. package/src/storage/ring-selector.ts +317 -317
  208. package/src/storage/ring-shift-coordinator.ts +271 -271
  209. package/src/storage/shared-cache-pool.ts +452 -0
  210. package/src/storage/storage-repo.ts +1014 -903
  211. package/src/testing/cohort-topic-mesh-harness.ts +663 -663
  212. package/src/testing/index.ts +8 -8
  213. package/src/testing/matchmaking-mesh-harness.ts +475 -475
  214. package/src/testing/raw-storage-conformance.ts +453 -397
  215. package/src/testing/reactivity-mesh-harness.ts +922 -922
  216. package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
  217. package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
  218. package/dist/src/storage/restoration-coordinator-v2.js +0 -172
  219. package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
@@ -1,190 +1,190 @@
1
- /**
2
- * Optional, embedder-supplied authorization for inbound protocol streams.
3
- *
4
- * The `repo`, `cluster`, `sync` and `block-transfer` services otherwise go straight from
5
- * "stream opened" to "decode and execute", so any peer that can open a connection can issue
6
- * database operations. An application that owns a *private* database (only nodes it admitted
7
- * may read or write) needs a seam ahead of decoding; this is that seam.
8
- *
9
- * Contract:
10
- *
11
- * - **Absent predicate → today's behavior exactly.** Every service holds
12
- * `InboundStreamAuthorization | undefined`; when it is `undefined` the handler never
13
- * awaits anything extra and no code on this path runs.
14
- * - **Supplied predicate → fail closed.** Only a literal `true` allows the stream. `false`,
15
- * a throw, a rejection, a timeout, or an unidentifiable remote peer all deny, and denial
16
- * aborts the stream *before* any frame is decoded or any operation executed.
17
- * - **Called once per inbound stream**, not once per operation — which is equivalent here
18
- * because all four protocols are strictly one request per stream (each handler's generator
19
- * `return`s after the first response, so a second queued frame is never read).
20
- * - **Peer id encoding**: the predicate receives `connection.remotePeer.toString()` — the
21
- * libp2p base58btc/CIDv1 peer-id string (e.g. `12D3KooW…`), the same form
22
- * `PeerId.toString()` produces everywhere else in this codebase. Compare against that,
23
- * never against a multiaddr, a public-key hash, or a base64 encoding.
24
- * - **What a caller observes**: a stream reset. Denial is deliberately *not* reported on the
25
- * wire — telling an unauthorized peer "you are not a member" confirms membership state to
26
- * exactly the party the embedder decided not to trust, and the four protocols have four
27
- * different response shapes with no common error frame. The denial is instead loud on the
28
- * *denying* node: it is logged with the peer id, protocol and reason, and the stream is
29
- * aborted with an {@link UnauthorizedInboundStreamError} carrying
30
- * {@link INBOUND_STREAM_UNAUTHORIZED_CODE}, so local diagnostics can tell a denial apart
31
- * from a transport fault.
32
- * - **Cost**: the predicate sits in the hot path of every inbound stream, ahead of the work
33
- * that stream would do. Embedders are expected to make it cheap — an in-memory set lookup —
34
- * and to memoize anything that would otherwise hit storage or the network per stream.
35
- *
36
- * NOTE: denial is stateless and unthrottled — a denied peer may reopen streams as fast as
37
- * libp2p's per-connection `maxInboundStreams` allows, and nothing here records the denial. That
38
- * is fine while the predicate is an in-memory lookup. If a denied peer ever shows up as load, the
39
- * fix is upstream of this module, not inside it: feed denials into `PeerReputationService`, or
40
- * refuse the peer at the connection level with `NodeOptions.connectionGater`.
41
- */
42
-
43
- /**
44
- * Predicate deciding whether `remotePeerId` may open `protocol` on this node.
45
- *
46
- * @param remotePeerId the dialing peer's `PeerId.toString()` (base58btc / CIDv1, `12D3KooW…`)
47
- * @param protocol the full protocol id the stream was opened on, e.g.
48
- * `/optimystic/<network>/repo/1.0.0`
49
- * @returns `true` to allow. Anything else — `false`, a throw, or a rejection — denies.
50
- */
51
- export type AuthorizeInboundStream = (remotePeerId: string, protocol: string) => Promise<boolean> | boolean;
52
-
53
- /** Stable error code carried by {@link UnauthorizedInboundStreamError}. */
54
- export const INBOUND_STREAM_UNAUTHORIZED_CODE = 'ERR_INBOUND_STREAM_UNAUTHORIZED';
55
-
56
- /**
57
- * The reason an inbound stream was aborted by the authorization gate. Distinct from a
58
- * transport fault so the denying node's logs and any local abort-reason inspection can tell
59
- * the two apart; the remote only ever sees the stream reset (see the module doc).
60
- */
61
- export class UnauthorizedInboundStreamError extends Error {
62
- readonly code = INBOUND_STREAM_UNAUTHORIZED_CODE;
63
- constructor(remotePeerId: string, protocol: string, reason: string) {
64
- super(`inbound stream denied: peer=${remotePeerId} protocol=${protocol} reason=${reason}`);
65
- this.name = 'UnauthorizedInboundStreamError';
66
- }
67
- }
68
-
69
- /**
70
- * How long the predicate may take before the stream is denied. A hanging predicate would
71
- * otherwise pin an inbound stream slot indefinitely; timing out is the fail-closed reading of
72
- * "we could not establish that this peer is allowed".
73
- */
74
- export const DEFAULT_INBOUND_AUTHORIZATION_TIMEOUT_MS = 5_000;
75
-
76
- /** The slice of a service's init that configures this gate. Mixed into all four service inits. */
77
- export interface InboundStreamAuthorizationInit {
78
- /**
79
- * Optional predicate consulted once per inbound stream, before any decoding or execution.
80
- * Absent → no check at all. See {@link AuthorizeInboundStream}.
81
- */
82
- authorizeInboundStream?: AuthorizeInboundStream;
83
- /**
84
- * Deadline for {@link InboundStreamAuthorizationInit.authorizeInboundStream}; expiry denies
85
- * the stream. Default {@link DEFAULT_INBOUND_AUTHORIZATION_TIMEOUT_MS}.
86
- */
87
- authorizeInboundStreamTimeoutMs?: number;
88
- }
89
-
90
- /** The part of a libp2p `Stream` this gate needs: the ability to tear it down. */
91
- interface AbortableStream {
92
- abort: (err: Error) => void;
93
- }
94
-
95
- /** Log sink shape shared by the component loggers and the `debug` loggers the services use. */
96
- export type AuthorizationLog = (message: string, ...args: unknown[]) => void;
97
-
98
- /** Marker resolved by the deadline race when the predicate has not settled in time. */
99
- const TIMED_OUT = Symbol('inbound-authorization-timeout');
100
-
101
- /**
102
- * A configured authorization gate for one service's protocol. Constructed only when the
103
- * embedder supplied a predicate, so a service holding `undefined` runs the original code path.
104
- */
105
- export class InboundStreamAuthorization {
106
- constructor(
107
- private readonly authorize: AuthorizeInboundStream,
108
- private readonly protocol: string,
109
- private readonly timeoutMs: number,
110
- private readonly log: AuthorizationLog
111
- ) { }
112
-
113
- /**
114
- * Consult the predicate for one inbound stream and abort the stream if it is not allowed.
115
- *
116
- * @returns `true` when the stream was denied — the caller must return immediately without
117
- * decoding anything — and `false` when it may proceed.
118
- */
119
- async deny(stream: AbortableStream, remotePeerId: string | undefined): Promise<boolean> {
120
- // No identifiable remote → the predicate cannot be asked, so we cannot establish
121
- // authorization. Fail closed rather than fall through to execution.
122
- if (remotePeerId === undefined) {
123
- return this.abort(stream, '<unidentified>', 'no remote peer id on the inbound connection');
124
- }
125
- const verdict = await this.decide(remotePeerId);
126
- return verdict.allowed ? false : this.abort(stream, remotePeerId, verdict.reason);
127
- }
128
-
129
- /** Run the predicate under its deadline, converting every failure mode into a denial. */
130
- private async decide(remotePeerId: string): Promise<{ allowed: true } | { allowed: false, reason: string }> {
131
- let timer: ReturnType<typeof setTimeout> | undefined;
132
- try {
133
- const verdict = this.authorize(remotePeerId, this.protocol);
134
- // A synchronous predicate needs no timer at all.
135
- if (typeof verdict === 'boolean') {
136
- return verdict ? { allowed: true } : { allowed: false, reason: 'predicate returned false' };
137
- }
138
- const decision = await Promise.race([
139
- verdict,
140
- new Promise<typeof TIMED_OUT>(resolve => { timer = setTimeout(() => resolve(TIMED_OUT), this.timeoutMs); })
141
- ]);
142
- if (decision === TIMED_OUT) {
143
- return { allowed: false, reason: `predicate did not settle within ${this.timeoutMs}ms` };
144
- }
145
- return decision === true ? { allowed: true } : { allowed: false, reason: 'predicate returned false' };
146
- } catch (err) {
147
- // A throwing predicate is a bug in the embedder, not permission to proceed: log it
148
- // (never swallow) and deny.
149
- this.log('authorization predicate threw for peer=%s protocol=%s - %o', remotePeerId, this.protocol, err);
150
- return { allowed: false, reason: `predicate threw: ${err instanceof Error ? err.message : String(err)}` };
151
- } finally {
152
- if (timer !== undefined) clearTimeout(timer);
153
- }
154
- }
155
-
156
- /** Log the denial and tear the stream down. Always returns `true` (denied). */
157
- private abort(stream: AbortableStream, remotePeerId: string, reason: string): true {
158
- const error = new UnauthorizedInboundStreamError(remotePeerId, this.protocol, reason);
159
- this.log('inbound stream denied peer=%s protocol=%s reason=%s', remotePeerId, this.protocol, reason);
160
- try {
161
- stream.abort(error);
162
- } catch (err) {
163
- // The stream may already be torn down; the denial itself still stands.
164
- this.log('aborting a denied stream failed peer=%s protocol=%s - %o', remotePeerId, this.protocol, err);
165
- }
166
- return true;
167
- }
168
- }
169
-
170
- /**
171
- * Build the gate for a service, or `undefined` when the embedder supplied no predicate.
172
- *
173
- * Returning `undefined` (rather than an always-allow gate) is what makes the absent-predicate
174
- * case genuinely free: the call site guards on the field and never awaits.
175
- */
176
- export function createInboundStreamAuthorization(
177
- init: InboundStreamAuthorizationInit,
178
- protocol: string,
179
- log: AuthorizationLog
180
- ): InboundStreamAuthorization | undefined {
181
- if (init.authorizeInboundStream === undefined) {
182
- return undefined;
183
- }
184
- return new InboundStreamAuthorization(
185
- init.authorizeInboundStream,
186
- protocol,
187
- init.authorizeInboundStreamTimeoutMs ?? DEFAULT_INBOUND_AUTHORIZATION_TIMEOUT_MS,
188
- log
189
- );
190
- }
1
+ /**
2
+ * Optional, embedder-supplied authorization for inbound protocol streams.
3
+ *
4
+ * The `repo`, `cluster`, `sync` and `block-transfer` services otherwise go straight from
5
+ * "stream opened" to "decode and execute", so any peer that can open a connection can issue
6
+ * database operations. An application that owns a *private* database (only nodes it admitted
7
+ * may read or write) needs a seam ahead of decoding; this is that seam.
8
+ *
9
+ * Contract:
10
+ *
11
+ * - **Absent predicate → today's behavior exactly.** Every service holds
12
+ * `InboundStreamAuthorization | undefined`; when it is `undefined` the handler never
13
+ * awaits anything extra and no code on this path runs.
14
+ * - **Supplied predicate → fail closed.** Only a literal `true` allows the stream. `false`,
15
+ * a throw, a rejection, a timeout, or an unidentifiable remote peer all deny, and denial
16
+ * aborts the stream *before* any frame is decoded or any operation executed.
17
+ * - **Called once per inbound stream**, not once per operation — which is equivalent here
18
+ * because all four protocols are strictly one request per stream (each handler's generator
19
+ * `return`s after the first response, so a second queued frame is never read).
20
+ * - **Peer id encoding**: the predicate receives `connection.remotePeer.toString()` — the
21
+ * libp2p base58btc/CIDv1 peer-id string (e.g. `12D3KooW…`), the same form
22
+ * `PeerId.toString()` produces everywhere else in this codebase. Compare against that,
23
+ * never against a multiaddr, a public-key hash, or a base64 encoding.
24
+ * - **What a caller observes**: a stream reset. Denial is deliberately *not* reported on the
25
+ * wire — telling an unauthorized peer "you are not a member" confirms membership state to
26
+ * exactly the party the embedder decided not to trust, and the four protocols have four
27
+ * different response shapes with no common error frame. The denial is instead loud on the
28
+ * *denying* node: it is logged with the peer id, protocol and reason, and the stream is
29
+ * aborted with an {@link UnauthorizedInboundStreamError} carrying
30
+ * {@link INBOUND_STREAM_UNAUTHORIZED_CODE}, so local diagnostics can tell a denial apart
31
+ * from a transport fault.
32
+ * - **Cost**: the predicate sits in the hot path of every inbound stream, ahead of the work
33
+ * that stream would do. Embedders are expected to make it cheap — an in-memory set lookup —
34
+ * and to memoize anything that would otherwise hit storage or the network per stream.
35
+ *
36
+ * NOTE: denial is stateless and unthrottled — a denied peer may reopen streams as fast as
37
+ * libp2p's per-connection `maxInboundStreams` allows, and nothing here records the denial. That
38
+ * is fine while the predicate is an in-memory lookup. If a denied peer ever shows up as load, the
39
+ * fix is upstream of this module, not inside it: feed denials into `PeerReputationService`, or
40
+ * refuse the peer at the connection level with `NodeOptions.connectionGater`.
41
+ */
42
+
43
+ /**
44
+ * Predicate deciding whether `remotePeerId` may open `protocol` on this node.
45
+ *
46
+ * @param remotePeerId the dialing peer's `PeerId.toString()` (base58btc / CIDv1, `12D3KooW…`)
47
+ * @param protocol the full protocol id the stream was opened on, e.g.
48
+ * `/optimystic/<network>/repo/1.0.0`
49
+ * @returns `true` to allow. Anything else — `false`, a throw, or a rejection — denies.
50
+ */
51
+ export type AuthorizeInboundStream = (remotePeerId: string, protocol: string) => Promise<boolean> | boolean;
52
+
53
+ /** Stable error code carried by {@link UnauthorizedInboundStreamError}. */
54
+ export const INBOUND_STREAM_UNAUTHORIZED_CODE = 'ERR_INBOUND_STREAM_UNAUTHORIZED';
55
+
56
+ /**
57
+ * The reason an inbound stream was aborted by the authorization gate. Distinct from a
58
+ * transport fault so the denying node's logs and any local abort-reason inspection can tell
59
+ * the two apart; the remote only ever sees the stream reset (see the module doc).
60
+ */
61
+ export class UnauthorizedInboundStreamError extends Error {
62
+ readonly code = INBOUND_STREAM_UNAUTHORIZED_CODE;
63
+ constructor(remotePeerId: string, protocol: string, reason: string) {
64
+ super(`inbound stream denied: peer=${remotePeerId} protocol=${protocol} reason=${reason}`);
65
+ this.name = 'UnauthorizedInboundStreamError';
66
+ }
67
+ }
68
+
69
+ /**
70
+ * How long the predicate may take before the stream is denied. A hanging predicate would
71
+ * otherwise pin an inbound stream slot indefinitely; timing out is the fail-closed reading of
72
+ * "we could not establish that this peer is allowed".
73
+ */
74
+ export const DEFAULT_INBOUND_AUTHORIZATION_TIMEOUT_MS = 5_000;
75
+
76
+ /** The slice of a service's init that configures this gate. Mixed into all four service inits. */
77
+ export interface InboundStreamAuthorizationInit {
78
+ /**
79
+ * Optional predicate consulted once per inbound stream, before any decoding or execution.
80
+ * Absent → no check at all. See {@link AuthorizeInboundStream}.
81
+ */
82
+ authorizeInboundStream?: AuthorizeInboundStream;
83
+ /**
84
+ * Deadline for {@link InboundStreamAuthorizationInit.authorizeInboundStream}; expiry denies
85
+ * the stream. Default {@link DEFAULT_INBOUND_AUTHORIZATION_TIMEOUT_MS}.
86
+ */
87
+ authorizeInboundStreamTimeoutMs?: number;
88
+ }
89
+
90
+ /** The part of a libp2p `Stream` this gate needs: the ability to tear it down. */
91
+ interface AbortableStream {
92
+ abort: (err: Error) => void;
93
+ }
94
+
95
+ /** Log sink shape shared by the component loggers and the `debug` loggers the services use. */
96
+ export type AuthorizationLog = (message: string, ...args: unknown[]) => void;
97
+
98
+ /** Marker resolved by the deadline race when the predicate has not settled in time. */
99
+ const TIMED_OUT = Symbol('inbound-authorization-timeout');
100
+
101
+ /**
102
+ * A configured authorization gate for one service's protocol. Constructed only when the
103
+ * embedder supplied a predicate, so a service holding `undefined` runs the original code path.
104
+ */
105
+ export class InboundStreamAuthorization {
106
+ constructor(
107
+ private readonly authorize: AuthorizeInboundStream,
108
+ private readonly protocol: string,
109
+ private readonly timeoutMs: number,
110
+ private readonly log: AuthorizationLog
111
+ ) { }
112
+
113
+ /**
114
+ * Consult the predicate for one inbound stream and abort the stream if it is not allowed.
115
+ *
116
+ * @returns `true` when the stream was denied — the caller must return immediately without
117
+ * decoding anything — and `false` when it may proceed.
118
+ */
119
+ async deny(stream: AbortableStream, remotePeerId: string | undefined): Promise<boolean> {
120
+ // No identifiable remote → the predicate cannot be asked, so we cannot establish
121
+ // authorization. Fail closed rather than fall through to execution.
122
+ if (remotePeerId === undefined) {
123
+ return this.abort(stream, '<unidentified>', 'no remote peer id on the inbound connection');
124
+ }
125
+ const verdict = await this.decide(remotePeerId);
126
+ return verdict.allowed ? false : this.abort(stream, remotePeerId, verdict.reason);
127
+ }
128
+
129
+ /** Run the predicate under its deadline, converting every failure mode into a denial. */
130
+ private async decide(remotePeerId: string): Promise<{ allowed: true } | { allowed: false, reason: string }> {
131
+ let timer: ReturnType<typeof setTimeout> | undefined;
132
+ try {
133
+ const verdict = this.authorize(remotePeerId, this.protocol);
134
+ // A synchronous predicate needs no timer at all.
135
+ if (typeof verdict === 'boolean') {
136
+ return verdict ? { allowed: true } : { allowed: false, reason: 'predicate returned false' };
137
+ }
138
+ const decision = await Promise.race([
139
+ verdict,
140
+ new Promise<typeof TIMED_OUT>(resolve => { timer = setTimeout(() => resolve(TIMED_OUT), this.timeoutMs); })
141
+ ]);
142
+ if (decision === TIMED_OUT) {
143
+ return { allowed: false, reason: `predicate did not settle within ${this.timeoutMs}ms` };
144
+ }
145
+ return decision === true ? { allowed: true } : { allowed: false, reason: 'predicate returned false' };
146
+ } catch (err) {
147
+ // A throwing predicate is a bug in the embedder, not permission to proceed: log it
148
+ // (never swallow) and deny.
149
+ this.log('authorization predicate threw for peer=%s protocol=%s - %o', remotePeerId, this.protocol, err);
150
+ return { allowed: false, reason: `predicate threw: ${err instanceof Error ? err.message : String(err)}` };
151
+ } finally {
152
+ if (timer !== undefined) clearTimeout(timer);
153
+ }
154
+ }
155
+
156
+ /** Log the denial and tear the stream down. Always returns `true` (denied). */
157
+ private abort(stream: AbortableStream, remotePeerId: string, reason: string): true {
158
+ const error = new UnauthorizedInboundStreamError(remotePeerId, this.protocol, reason);
159
+ this.log('inbound stream denied peer=%s protocol=%s reason=%s', remotePeerId, this.protocol, reason);
160
+ try {
161
+ stream.abort(error);
162
+ } catch (err) {
163
+ // The stream may already be torn down; the denial itself still stands.
164
+ this.log('aborting a denied stream failed peer=%s protocol=%s - %o', remotePeerId, this.protocol, err);
165
+ }
166
+ return true;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Build the gate for a service, or `undefined` when the embedder supplied no predicate.
172
+ *
173
+ * Returning `undefined` (rather than an always-allow gate) is what makes the absent-predicate
174
+ * case genuinely free: the call site guards on the field and never awaits.
175
+ */
176
+ export function createInboundStreamAuthorization(
177
+ init: InboundStreamAuthorizationInit,
178
+ protocol: string,
179
+ log: AuthorizationLog
180
+ ): InboundStreamAuthorization | undefined {
181
+ if (init.authorizeInboundStream === undefined) {
182
+ return undefined;
183
+ }
184
+ return new InboundStreamAuthorization(
185
+ init.authorizeInboundStream,
186
+ protocol,
187
+ init.authorizeInboundStreamTimeoutMs ?? DEFAULT_INBOUND_AUTHORIZATION_TIMEOUT_MS,
188
+ log
189
+ );
190
+ }
package/src/index.ts CHANGED
@@ -1,47 +1,52 @@
1
- export * from "./cluster/client.js";
2
- export * from "./cluster/cluster-repo.js";
3
- export * from "./cluster/commit-cert.js";
4
- export * from "./cluster/service.js";
5
- export * from "./cluster/rebalance-monitor.js";
6
- export * from "./cluster/spread-on-churn.js";
7
- export * from "./cluster/block-transfer.js";
8
- export * from "./cluster/block-transfer-service.js";
9
- export * from "./inbound-authorization.js";
10
- export * from "./protocol-client.js";
11
- export * from "./repo/client.js";
12
- export * from "./repo/cluster-coordinator.js";
13
- export * from "./repo/coordinator-repo.js";
14
- export * from "./repo/service.js";
15
- export * from "./storage/block-storage.js";
16
- export * from "./storage/raw-store-driver.js";
17
- export * from "./storage/kv-raw-storage.js";
18
- export * from "./storage/memory-store-driver.js";
19
- export * from "./storage/memory-storage.js";
20
- export * from "./storage/i-block-storage.js";
21
- export * from "./storage/i-raw-storage.js";
22
- export * from "./storage/struct.js";
23
- export * from "./storage/storage-repo.js";
24
- export * from "./storage/restoration-coordinator.js";
25
- export * from "./storage/ring-selector.js";
26
- export * from "./storage/storage-monitor.js";
27
- export * from "./storage/arachnode-fret-adapter.js";
28
- export * from "./sync/protocol.js";
29
- export * from "./sync/client.js";
30
- export * from "./sync/service.js";
31
- export * from "./it-utility.js";
32
- export * from "./libp2p-key-network.js";
33
- export * from "./libp2p-node.js";
34
- export * from "./routing/responsibility.js";
35
- export * from "./routing/libp2p-known-peers.js";
36
- export * from "./network/network-manager-service.js";
37
- export * from "./network/get-network-manager.js";
38
- export * from "./reputation/index.js";
39
- export * from "./dispute/index.js";
40
- export * from "./cohort-topic/index.js";
41
- export * from "./matchmaking/index.js";
42
- export * from "./reactivity/index.js";
43
- export * from "./cluster/i-transaction-state-store.js";
44
- export * from "./cluster/memory-transaction-state-store.js";
45
- export * from "./cluster/persistent-transaction-state-store.js";
46
- export * from "./storage/i-kv-store.js";
47
- export * from "./storage/memory-kv-store.js";
1
+ export * from "./cluster/client.js";
2
+ export * from "./cluster/cluster-repo.js";
3
+ export * from "./cluster/cluster-policy.js";
4
+ export * from "./cluster/commit-cert.js";
5
+ export * from "./cluster/service.js";
6
+ export * from "./cluster/rebalance-monitor.js";
7
+ export * from "./cluster/spread-on-churn.js";
8
+ export * from "./cluster/block-transfer.js";
9
+ export * from "./cluster/block-transfer-service.js";
10
+ export * from "./inbound-authorization.js";
11
+ export * from "./protocol-client.js";
12
+ export * from "./repo/client.js";
13
+ export * from "./repo/cluster-coordinator.js";
14
+ export * from "./repo/coordinator-repo.js";
15
+ export * from "./repo/service.js";
16
+ export * from "./storage/block-storage.js";
17
+ export * from "./storage/raw-store-driver.js";
18
+ export * from "./storage/kv-raw-storage.js";
19
+ export * from "./storage/shared-cache-pool.js";
20
+ export * from "./storage/cached-store-driver.js";
21
+ export * from "./storage/cached-raw-storage.js";
22
+ export * from "./storage/memory-store-driver.js";
23
+ export * from "./storage/memory-storage.js";
24
+ export * from "./storage/i-block-storage.js";
25
+ export * from "./storage/i-raw-storage.js";
26
+ export * from "./storage/struct.js";
27
+ export * from "./storage/storage-repo.js";
28
+ export * from "./storage/restoration-coordinator.js";
29
+ export * from "./storage/ring-selector.js";
30
+ export * from "./storage/storage-monitor.js";
31
+ export * from "./storage/arachnode-fret-adapter.js";
32
+ export * from "./sync/protocol.js";
33
+ export * from "./sync/client.js";
34
+ export * from "./sync/service.js";
35
+ export * from "./it-utility.js";
36
+ export * from "./libp2p-key-network.js";
37
+ export * from "./libp2p-node.js";
38
+ export * from "./optimystic-node.js";
39
+ export * from "./routing/responsibility.js";
40
+ export * from "./routing/libp2p-known-peers.js";
41
+ export * from "./network/network-manager-service.js";
42
+ export * from "./network/get-network-manager.js";
43
+ export * from "./reputation/index.js";
44
+ export * from "./dispute/index.js";
45
+ export * from "./cohort-topic/index.js";
46
+ export * from "./matchmaking/index.js";
47
+ export * from "./reactivity/index.js";
48
+ export * from "./cluster/i-transaction-state-store.js";
49
+ export * from "./cluster/memory-transaction-state-store.js";
50
+ export * from "./cluster/persistent-transaction-state-store.js";
51
+ export * from "./storage/i-kv-store.js";
52
+ export * from "./storage/memory-kv-store.js";