@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
@@ -1,198 +1,178 @@
1
- import type { ComponentLogger, Connection, Startable } from '@libp2p/interface';
2
- import type { IRepo } from '@optimystic/db-core';
3
- import { buildSyncProtocol, type SyncRequest, type SyncResponse } from './protocol.js';
4
- import { pipe } from 'it-pipe';
5
- import { toString as u8ToString } from 'uint8arrays/to-string';
6
- import * as lp from 'it-length-prefixed';
7
- import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
8
- import type { Uint8ArrayList } from 'uint8arraylist';
9
- import { createInboundStreamAuthorization, type InboundStreamAuthorization, type InboundStreamAuthorizationInit } from '../inbound-authorization.js';
10
-
11
- export interface SyncServiceInit extends InboundStreamAuthorizationInit {
12
- protocolPrefix?: string;
13
- }
14
-
15
- export interface SyncServiceComponents {
16
- logger: ComponentLogger;
17
- registrar: { handle: (...args: any[]) => Promise<void>, unhandle: (...args: any[]) => Promise<void> };
18
- repo: IRepo;
19
- }
20
-
21
- type Logger = ReturnType<ComponentLogger['forComponent']>;
22
-
23
- /**
24
- * Service for handling incoming sync requests from other cluster peers.
25
- *
26
- * Listens on the sync protocol and responds to block requests by:
27
- * 1. Extracting the block from local storage
28
- * 2. Building a BlockArchive with requested revisions
29
- * 3. Sending the response back to the requester
30
- *
31
- * This is the server-side of the block restoration mechanism.
32
- */
33
- export class SyncService implements Startable {
34
- private running = false;
35
- private readonly log: Logger;
36
- private readonly protocol: string;
37
- private readonly repo: IRepo;
38
- private readonly registrar: { handle: (...args: any[]) => Promise<void>, unhandle: (...args: any[]) => Promise<void> };
39
- /** Optional embedder authorization gate; `undefined` (the default) means no check runs. */
40
- private readonly authorization: InboundStreamAuthorization | undefined;
41
-
42
- constructor(
43
- components: SyncServiceComponents,
44
- init: SyncServiceInit = {}
45
- ) {
46
- this.log = components.logger.forComponent('db-p2p:sync-service');
47
- this.protocol = buildSyncProtocol(init.protocolPrefix ?? '');
48
- this.repo = components.repo;
49
- this.registrar = components.registrar;
50
- this.authorization = createInboundStreamAuthorization(init, this.protocol, (msg, ...args) => this.log.error(msg, ...args));
51
- }
52
-
53
- async start(): Promise<void> {
54
- if (this.running) return;
55
-
56
- await this.registrar.handle(this.protocol, this.handleSyncRequest.bind(this));
57
-
58
- this.running = true;
59
- this.log('Sync service started on protocol %s', this.protocol);
60
- }
61
-
62
- async stop(): Promise<void> {
63
- if (!this.running) return;
64
- await this.registrar.unhandle(this.protocol);
65
- this.running = false;
66
- this.log('Sync service stopped');
67
- }
68
-
69
- /**
70
- * Handle an incoming sync request stream.
71
- * Uses a streaming pipeline (like the repo service) to process the
72
- * request and yield a response immediately — avoids a read/write deadlock.
73
- */
74
- private handleSyncRequest(stream: any, connection?: Connection): void {
75
- const self = this;
76
- // The registrar handler receives the stream (or, in an older shape, a `{ stream }`
77
- // wrapper) directly; resolve it once so the authorization abort, the pipeline and the
78
- // error path all act on the same object.
79
- const actualStream = stream.stream ?? stream;
80
- void (async () => {
81
- try {
82
- // Authorization runs before ANY decoding or execution. Guarded on the field so a
83
- // node without a predicate keeps the original path untouched.
84
- if (self.authorization && await self.authorization.deny(actualStream, connection?.remotePeer?.toString())) return;
85
- const processStream = async function* (source: AsyncIterable<Uint8ArrayList> | Iterable<Uint8ArrayList>) {
86
- for await (const msg of source) {
87
- const json = u8ToString(msg.subarray(), 'utf8');
88
- const request = JSON.parse(json) as SyncRequest;
89
-
90
- self.log(
91
- '[Ring Zulu] Received sync request for block %s revision %s',
92
- request.blockId,
93
- request.rev ?? 'latest'
94
- );
95
-
96
- const archive = await self.buildArchive(
97
- request.blockId,
98
- request.rev,
99
- request.includePending,
100
- request.maxRevisions
101
- );
102
-
103
- const response: SyncResponse = archive
104
- ? { success: true, archive, responderId: stream.id ?? stream.stream?.id }
105
- : { success: false, error: 'Block not found in local storage' };
106
-
107
- self.log(
108
- '[Ring Zulu] %s sync request for block %s',
109
- response.success ? 'Fulfilled' : 'Failed',
110
- request.blockId
111
- );
112
-
113
- yield new TextEncoder().encode(JSON.stringify(response));
114
- return;
115
- }
116
- };
117
-
118
- // Use the same streaming pipeline pattern as the repo service.
119
- const responses = pipe(
120
- actualStream,
121
- (source: any) => lp.decode(source, { maxDataLength: MAX_CONTROL_MESSAGE_BYTES }),
122
- processStream,
123
- (source: any) => lp.encode(source)
124
- );
125
- for await (const chunk of responses) {
126
- actualStream.send(chunk);
127
- }
128
- await actualStream.close();
129
- } catch (error) {
130
- self.log.error('Error handling sync request:', error);
131
- try { actualStream.abort(error instanceof Error ? error : new Error(String(error))); } catch { /* ignore */ }
132
- }
133
- })();
134
- }
135
-
136
- /**
137
- * Build a block archive from local storage.
138
- *
139
- * @param blockId - Block to retrieve
140
- * @param rev - Optional specific revision
141
- * @param includePending - Whether to include pending transactions
142
- * @param maxRevisions - Maximum number of revisions to include
143
- * @returns BlockArchive if found, undefined otherwise
144
- */
145
- private async buildArchive(
146
- blockId: string,
147
- rev?: number,
148
- _includePending?: boolean,
149
- _maxRevisions?: number
150
- ): Promise<import('../storage/struct.js').BlockArchive | undefined> {
151
- try {
152
- // Get the block from local storage
153
- const context = rev !== undefined
154
- ? { rev, committed: [], pending: [] }
155
- : undefined;
156
-
157
- const result = await this.repo.get({
158
- blockIds: [blockId],
159
- context
160
- }, { skipClusterFetch: true } as any);
161
-
162
- const blockResult = result[blockId];
163
- if (!blockResult || !blockResult.state.latest) {
164
- return undefined;
165
- }
166
-
167
- const latest = blockResult.state.latest;
168
-
169
- // Return minimal archive with just the requested block
170
- const archive: import('../storage/struct.js').BlockArchive = {
171
- blockId,
172
- revisions: {
173
- [latest.rev]: {
174
- action: {
175
- actionId: latest.actionId,
176
- transform: { insert: blockResult.block }
177
- },
178
- block: blockResult.block
179
- }
180
- },
181
- range: [latest.rev, latest.rev + 1]
182
- };
183
-
184
- return archive;
185
- } catch (error) {
186
- this.log.error('Error building archive for block %s:', blockId, error);
187
- return undefined;
188
- }
189
- }
190
- }
191
-
192
- /**
193
- * Factory function for creating a SyncService.
194
- * Follows the libp2p service pattern.
195
- */
196
- export const syncService = (init: SyncServiceInit = {}) =>
197
- (components: SyncServiceComponents) => new SyncService(components, init);
198
-
1
+ import type { ComponentLogger, Connection, Startable } from '@libp2p/interface';
2
+ import { buildSyncProtocol, type SyncRequest, type SyncResponse } from './protocol.js';
3
+ import { pipe } from 'it-pipe';
4
+ import { toString as u8ToString } from 'uint8arrays/to-string';
5
+ import * as lp from 'it-length-prefixed';
6
+ import { MAX_CONTROL_MESSAGE_BYTES } from '../protocol-limits.js';
7
+ import type { Uint8ArrayList } from 'uint8arraylist';
8
+ import { createInboundStreamAuthorization, type InboundStreamAuthorization, type InboundStreamAuthorizationInit } from '../inbound-authorization.js';
9
+ import { serveBlockArchive, type ArchiveServingRepo } from '../storage/block-archive.js';
10
+ import { registerProtocolHandler } from '../network/register-protocol-handler.js';
11
+ import type { BlockArchive } from '../storage/struct.js';
12
+
13
+ export interface SyncServiceInit extends InboundStreamAuthorizationInit {
14
+ protocolPrefix?: string;
15
+ }
16
+
17
+ export interface SyncServiceComponents {
18
+ logger: ComponentLogger;
19
+ registrar: { handle: (...args: any[]) => Promise<void>, unhandle: (...args: any[]) => Promise<void> };
20
+ /**
21
+ * The local store this node answers repair fetches out of. Declared as `ArchiveServingRepo`
22
+ * rather than bare `IRepo` so the seam SAYS that the served archive's commit proof is read off
23
+ * this object: a wiring that hands over a delegating proxy without the accessor serves every
24
+ * archive proof-less, which is invisible to any test that reads a real `StorageRepo` directly.
25
+ * The accessor stays optional — a plain `IRepo` embedder serves the pre-proof archive, which
26
+ * every consumer still handles.
27
+ */
28
+ repo: ArchiveServingRepo;
29
+ }
30
+
31
+ type Logger = ReturnType<ComponentLogger['forComponent']>;
32
+
33
+ /**
34
+ * Service for handling incoming sync requests from other cluster peers.
35
+ *
36
+ * Listens on the sync protocol and responds to block requests by:
37
+ * 1. Extracting the block from local storage
38
+ * 2. Building a BlockArchive with requested revisions
39
+ * 3. Sending the response back to the requester
40
+ *
41
+ * This is the server-side of the block restoration mechanism.
42
+ */
43
+ export class SyncService implements Startable {
44
+ private running = false;
45
+ private readonly log: Logger;
46
+ private readonly protocol: string;
47
+ private readonly repo: ArchiveServingRepo;
48
+ private readonly registrar: { handle: (...args: any[]) => Promise<void>, unhandle: (...args: any[]) => Promise<void> };
49
+ /** Optional embedder authorization gate; `undefined` (the default) means no check runs. */
50
+ private readonly authorization: InboundStreamAuthorization | undefined;
51
+
52
+ constructor(
53
+ components: SyncServiceComponents,
54
+ init: SyncServiceInit = {}
55
+ ) {
56
+ this.log = components.logger.forComponent('db-p2p:sync-service');
57
+ this.protocol = buildSyncProtocol(init.protocolPrefix ?? '');
58
+ this.repo = components.repo;
59
+ this.registrar = components.registrar;
60
+ this.authorization = createInboundStreamAuthorization(init, this.protocol, (msg, ...args) => this.log.error(msg, ...args));
61
+ }
62
+
63
+ async start(): Promise<void> {
64
+ if (this.running) return;
65
+
66
+ await registerProtocolHandler(this.registrar, this.protocol, this.handleSyncRequest.bind(this));
67
+
68
+ this.running = true;
69
+ this.log('Sync service started on protocol %s', this.protocol);
70
+ }
71
+
72
+ async stop(): Promise<void> {
73
+ if (!this.running) return;
74
+ await this.registrar.unhandle(this.protocol);
75
+ this.running = false;
76
+ this.log('Sync service stopped');
77
+ }
78
+
79
+ /**
80
+ * Handle an incoming sync request stream.
81
+ * Uses a streaming pipeline (like the repo service) to process the
82
+ * request and yield a response immediately avoids a read/write deadlock.
83
+ */
84
+ private handleSyncRequest(stream: any, connection?: Connection): void {
85
+ const self = this;
86
+ // The registrar handler receives the stream (or, in an older shape, a `{ stream }`
87
+ // wrapper) directly; resolve it once so the authorization abort, the pipeline and the
88
+ // error path all act on the same object.
89
+ const actualStream = stream.stream ?? stream;
90
+ void (async () => {
91
+ try {
92
+ // Authorization runs before ANY decoding or execution. Guarded on the field so a
93
+ // node without a predicate keeps the original path untouched.
94
+ if (self.authorization && await self.authorization.deny(actualStream, connection?.remotePeer?.toString())) return;
95
+ const processStream = async function* (source: AsyncIterable<Uint8ArrayList> | Iterable<Uint8ArrayList>) {
96
+ for await (const msg of source) {
97
+ const json = u8ToString(msg.subarray(), 'utf8');
98
+ const request = JSON.parse(json) as SyncRequest;
99
+
100
+ self.log(
101
+ '[Ring Zulu] Received sync request for block %s revision %s',
102
+ request.blockId,
103
+ request.rev ?? 'latest'
104
+ );
105
+
106
+ const archive = await self.buildArchive(
107
+ request.blockId,
108
+ request.rev,
109
+ request.includePending,
110
+ request.maxRevisions
111
+ );
112
+
113
+ const response: SyncResponse = archive
114
+ ? { success: true, archive, responderId: stream.id ?? stream.stream?.id }
115
+ : { success: false, error: 'Block not found in local storage' };
116
+
117
+ self.log(
118
+ '[Ring Zulu] %s sync request for block %s',
119
+ response.success ? 'Fulfilled' : 'Failed',
120
+ request.blockId
121
+ );
122
+
123
+ yield new TextEncoder().encode(JSON.stringify(response));
124
+ return;
125
+ }
126
+ };
127
+
128
+ // Use the same streaming pipeline pattern as the repo service.
129
+ const responses = pipe(
130
+ actualStream,
131
+ (source: any) => lp.decode(source, { maxDataLength: MAX_CONTROL_MESSAGE_BYTES }),
132
+ processStream,
133
+ (source: any) => lp.encode(source)
134
+ );
135
+ for await (const chunk of responses) {
136
+ actualStream.send(chunk);
137
+ }
138
+ await actualStream.close();
139
+ } catch (error) {
140
+ self.log.error('Error handling sync request:', error);
141
+ try { actualStream.abort(error instanceof Error ? error : new Error(String(error))); } catch { /* ignore */ }
142
+ }
143
+ })();
144
+ }
145
+
146
+ /**
147
+ * Build a block archive from local storage — what a repair fetch on the other side receives.
148
+ * Shape and repo read both live in `storage/block-archive.ts`, shared with every other site that
149
+ * serves or fakes one, so a peer's answer cannot mean one thing here and another there.
150
+ *
151
+ * @param blockId - Block to retrieve
152
+ * @param rev - Optional specific revision
153
+ * @param includePending - Whether to include pending transactions
154
+ * @param maxRevisions - Maximum number of revisions to include
155
+ * @returns BlockArchive if found, undefined otherwise
156
+ */
157
+ private async buildArchive(
158
+ blockId: string,
159
+ rev?: number,
160
+ _includePending?: boolean,
161
+ _maxRevisions?: number
162
+ ): Promise<BlockArchive | undefined> {
163
+ try {
164
+ return await serveBlockArchive(this.repo, blockId, rev);
165
+ } catch (error) {
166
+ this.log.error('Error building archive for block %s:', blockId, error);
167
+ return undefined;
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Factory function for creating a SyncService.
174
+ * Follows the libp2p service pattern.
175
+ */
176
+ export const syncService = (init: SyncServiceInit = {}) =>
177
+ (components: SyncServiceComponents) => new SyncService(components, init);
178
+