@bsv/overlay 2.3.1 → 2.6.1

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 (201) hide show
  1. package/README.md +166 -0
  2. package/dist/cjs/mod.d.ts +3 -0
  3. package/dist/cjs/mod.d.ts.map +1 -1
  4. package/dist/cjs/mod.js +8 -1
  5. package/dist/cjs/mod.js.map +1 -1
  6. package/dist/cjs/package.json +10 -4
  7. package/dist/cjs/src/BASM.d.ts +6 -0
  8. package/dist/cjs/src/BASM.d.ts.map +1 -1
  9. package/dist/cjs/src/BASM.js +21 -8
  10. package/dist/cjs/src/BASM.js.map +1 -1
  11. package/dist/cjs/src/BASMRemote.d.ts +8 -1
  12. package/dist/cjs/src/BASMRemote.d.ts.map +1 -1
  13. package/dist/cjs/src/BASMRemote.js +179 -13
  14. package/dist/cjs/src/BASMRemote.js.map +1 -1
  15. package/dist/cjs/src/BASMValidation.d.ts +27 -0
  16. package/dist/cjs/src/BASMValidation.d.ts.map +1 -0
  17. package/dist/cjs/src/BASMValidation.js +108 -0
  18. package/dist/cjs/src/BASMValidation.js.map +1 -0
  19. package/dist/cjs/src/DiscoveryAdvertisementValidation.d.ts +11 -0
  20. package/dist/cjs/src/DiscoveryAdvertisementValidation.d.ts.map +1 -0
  21. package/dist/cjs/src/DiscoveryAdvertisementValidation.js +60 -0
  22. package/dist/cjs/src/DiscoveryAdvertisementValidation.js.map +1 -0
  23. package/dist/cjs/src/Engine.d.ts +37 -13
  24. package/dist/cjs/src/Engine.d.ts.map +1 -1
  25. package/dist/cjs/src/Engine.js +1713 -336
  26. package/dist/cjs/src/Engine.js.map +1 -1
  27. package/dist/cjs/src/EngineAdmission.d.ts +70 -0
  28. package/dist/cjs/src/EngineAdmission.d.ts.map +1 -0
  29. package/dist/cjs/src/EngineAdmission.js +297 -0
  30. package/dist/cjs/src/EngineAdmission.js.map +1 -0
  31. package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts +4 -3
  32. package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
  33. package/dist/cjs/src/GASP/OverlayGASPRemote.js +27 -43
  34. package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
  35. package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts +22 -16
  36. package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
  37. package/dist/cjs/src/GASP/OverlayGASPStorage.js +206 -57
  38. package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
  39. package/dist/cjs/src/RemoteSecurity.d.ts +50 -0
  40. package/dist/cjs/src/RemoteSecurity.d.ts.map +1 -0
  41. package/dist/cjs/src/RemoteSecurity.js +301 -0
  42. package/dist/cjs/src/RemoteSecurity.js.map +1 -0
  43. package/dist/cjs/src/storage/AdmissionStorage.d.ts +185 -0
  44. package/dist/cjs/src/storage/AdmissionStorage.d.ts.map +1 -0
  45. package/dist/cjs/src/storage/AdmissionStorage.js +93 -0
  46. package/dist/cjs/src/storage/AdmissionStorage.js.map +1 -0
  47. package/dist/cjs/src/storage/RecoveryContract.d.ts +50 -0
  48. package/dist/cjs/src/storage/RecoveryContract.d.ts.map +1 -0
  49. package/dist/cjs/src/storage/RecoveryContract.js +35 -0
  50. package/dist/cjs/src/storage/RecoveryContract.js.map +1 -0
  51. package/dist/cjs/src/storage/Storage.d.ts +32 -19
  52. package/dist/cjs/src/storage/Storage.d.ts.map +1 -1
  53. package/dist/cjs/src/storage/Storage.js +5 -0
  54. package/dist/cjs/src/storage/Storage.js.map +1 -1
  55. package/dist/cjs/src/storage/knex/KnexStorage.d.ts +8 -8
  56. package/dist/cjs/src/storage/knex/KnexStorage.d.ts.map +1 -1
  57. package/dist/cjs/src/storage/knex/KnexStorage.js +98 -61
  58. package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
  59. package/dist/cjs/src/storage/knex/all-migrations.d.ts.map +1 -1
  60. package/dist/cjs/src/storage/knex/all-migrations.js +5 -1
  61. package/dist/cjs/src/storage/knex/all-migrations.js.map +1 -1
  62. package/dist/cjs/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.d.ts +4 -0
  63. package/dist/cjs/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.d.ts.map +1 -0
  64. package/dist/cjs/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.js +36 -0
  65. package/dist/cjs/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.js.map +1 -0
  66. package/dist/cjs/src/storage/knex/migrations/2026-09-20-001-spent-by.d.ts +5 -0
  67. package/dist/cjs/src/storage/knex/migrations/2026-09-20-001-spent-by.d.ts.map +1 -0
  68. package/dist/cjs/src/storage/knex/migrations/2026-09-20-001-spent-by.js +16 -0
  69. package/dist/cjs/src/storage/knex/migrations/2026-09-20-001-spent-by.js.map +1 -0
  70. package/dist/cjs/src/storage/mongo/MongoAdmissionPlan.d.ts +18 -0
  71. package/dist/cjs/src/storage/mongo/MongoAdmissionPlan.d.ts.map +1 -0
  72. package/dist/cjs/src/storage/mongo/MongoAdmissionPlan.js +229 -0
  73. package/dist/cjs/src/storage/mongo/MongoAdmissionPlan.js.map +1 -0
  74. package/dist/cjs/src/storage/mongo/MongoAdmissionStorage.d.ts +112 -0
  75. package/dist/cjs/src/storage/mongo/MongoAdmissionStorage.d.ts.map +1 -0
  76. package/dist/cjs/src/storage/mongo/MongoAdmissionStorage.js +675 -0
  77. package/dist/cjs/src/storage/mongo/MongoAdmissionStorage.js.map +1 -0
  78. package/dist/cjs/src/storage/mongo/MongoOverlayStorage.d.ts +71 -0
  79. package/dist/cjs/src/storage/mongo/MongoOverlayStorage.d.ts.map +1 -0
  80. package/dist/cjs/src/storage/mongo/MongoOverlayStorage.js +453 -0
  81. package/dist/cjs/src/storage/mongo/MongoOverlayStorage.js.map +1 -0
  82. package/dist/cjs/src/storage/mongo/MongoPayloadStore.d.ts +134 -0
  83. package/dist/cjs/src/storage/mongo/MongoPayloadStore.d.ts.map +1 -0
  84. package/dist/cjs/src/storage/mongo/MongoPayloadStore.js +756 -0
  85. package/dist/cjs/src/storage/mongo/MongoPayloadStore.js.map +1 -0
  86. package/dist/cjs/src/storage/mongo/MongoReadGuards.d.ts +44 -0
  87. package/dist/cjs/src/storage/mongo/MongoReadGuards.d.ts.map +1 -0
  88. package/dist/cjs/src/storage/mongo/MongoReadGuards.js +135 -0
  89. package/dist/cjs/src/storage/mongo/MongoReadGuards.js.map +1 -0
  90. package/dist/cjs/src/storage/mongo/MongoSchema.d.ts +67 -0
  91. package/dist/cjs/src/storage/mongo/MongoSchema.d.ts.map +1 -0
  92. package/dist/cjs/src/storage/mongo/MongoSchema.js +743 -0
  93. package/dist/cjs/src/storage/mongo/MongoSchema.js.map +1 -0
  94. package/dist/cjs/src/storage/mongo/MongoTransactionRunner.d.ts +69 -0
  95. package/dist/cjs/src/storage/mongo/MongoTransactionRunner.d.ts.map +1 -0
  96. package/dist/cjs/src/storage/mongo/MongoTransactionRunner.js +368 -0
  97. package/dist/cjs/src/storage/mongo/MongoTransactionRunner.js.map +1 -0
  98. package/dist/cjs/src/storage/mongo.d.ts +9 -0
  99. package/dist/cjs/src/storage/mongo.d.ts.map +1 -0
  100. package/dist/cjs/src/storage/mongo.js +25 -0
  101. package/dist/cjs/src/storage/mongo.js.map +1 -0
  102. package/dist/esm/mod.js +3 -0
  103. package/dist/esm/mod.js.map +1 -1
  104. package/dist/esm/src/BASM.js +21 -8
  105. package/dist/esm/src/BASM.js.map +1 -1
  106. package/dist/esm/src/BASMRemote.js +176 -12
  107. package/dist/esm/src/BASMRemote.js.map +1 -1
  108. package/dist/esm/src/BASMValidation.js +95 -0
  109. package/dist/esm/src/BASMValidation.js.map +1 -0
  110. package/dist/esm/src/DiscoveryAdvertisementValidation.js +57 -0
  111. package/dist/esm/src/DiscoveryAdvertisementValidation.js.map +1 -0
  112. package/dist/esm/src/Engine.js +1698 -326
  113. package/dist/esm/src/Engine.js.map +1 -1
  114. package/dist/esm/src/EngineAdmission.js +280 -0
  115. package/dist/esm/src/EngineAdmission.js.map +1 -0
  116. package/dist/esm/src/GASP/OverlayGASPRemote.js +27 -43
  117. package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
  118. package/dist/esm/src/GASP/OverlayGASPStorage.js +200 -56
  119. package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
  120. package/dist/esm/src/RemoteSecurity.js +279 -0
  121. package/dist/esm/src/RemoteSecurity.js.map +1 -0
  122. package/dist/esm/src/storage/AdmissionStorage.js +85 -0
  123. package/dist/esm/src/storage/AdmissionStorage.js.map +1 -0
  124. package/dist/esm/src/storage/RecoveryContract.js +31 -0
  125. package/dist/esm/src/storage/RecoveryContract.js.map +1 -0
  126. package/dist/esm/src/storage/Storage.js +4 -1
  127. package/dist/esm/src/storage/Storage.js.map +1 -1
  128. package/dist/esm/src/storage/knex/KnexStorage.js +98 -61
  129. package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
  130. package/dist/esm/src/storage/knex/all-migrations.js +5 -1
  131. package/dist/esm/src/storage/knex/all-migrations.js.map +1 -1
  132. package/dist/esm/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.js +32 -0
  133. package/dist/esm/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.js.map +1 -0
  134. package/dist/esm/src/storage/knex/migrations/2026-09-20-001-spent-by.js +12 -0
  135. package/dist/esm/src/storage/knex/migrations/2026-09-20-001-spent-by.js.map +1 -0
  136. package/dist/esm/src/storage/mongo/MongoAdmissionPlan.js +217 -0
  137. package/dist/esm/src/storage/mongo/MongoAdmissionPlan.js.map +1 -0
  138. package/dist/esm/src/storage/mongo/MongoAdmissionStorage.js +673 -0
  139. package/dist/esm/src/storage/mongo/MongoAdmissionStorage.js.map +1 -0
  140. package/dist/esm/src/storage/mongo/MongoOverlayStorage.js +451 -0
  141. package/dist/esm/src/storage/mongo/MongoOverlayStorage.js.map +1 -0
  142. package/dist/esm/src/storage/mongo/MongoPayloadStore.js +747 -0
  143. package/dist/esm/src/storage/mongo/MongoPayloadStore.js.map +1 -0
  144. package/dist/esm/src/storage/mongo/MongoReadGuards.js +129 -0
  145. package/dist/esm/src/storage/mongo/MongoReadGuards.js.map +1 -0
  146. package/dist/esm/src/storage/mongo/MongoSchema.js +730 -0
  147. package/dist/esm/src/storage/mongo/MongoSchema.js.map +1 -0
  148. package/dist/esm/src/storage/mongo/MongoTransactionRunner.js +371 -0
  149. package/dist/esm/src/storage/mongo/MongoTransactionRunner.js.map +1 -0
  150. package/dist/esm/src/storage/mongo.js +9 -0
  151. package/dist/esm/src/storage/mongo.js.map +1 -0
  152. package/dist/types/mod.d.ts +3 -0
  153. package/dist/types/mod.d.ts.map +1 -1
  154. package/dist/types/src/BASM.d.ts +6 -0
  155. package/dist/types/src/BASM.d.ts.map +1 -1
  156. package/dist/types/src/BASMRemote.d.ts +8 -1
  157. package/dist/types/src/BASMRemote.d.ts.map +1 -1
  158. package/dist/types/src/BASMValidation.d.ts +27 -0
  159. package/dist/types/src/BASMValidation.d.ts.map +1 -0
  160. package/dist/types/src/DiscoveryAdvertisementValidation.d.ts +11 -0
  161. package/dist/types/src/DiscoveryAdvertisementValidation.d.ts.map +1 -0
  162. package/dist/types/src/Engine.d.ts +37 -13
  163. package/dist/types/src/Engine.d.ts.map +1 -1
  164. package/dist/types/src/EngineAdmission.d.ts +70 -0
  165. package/dist/types/src/EngineAdmission.d.ts.map +1 -0
  166. package/dist/types/src/GASP/OverlayGASPRemote.d.ts +4 -3
  167. package/dist/types/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
  168. package/dist/types/src/GASP/OverlayGASPStorage.d.ts +22 -16
  169. package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
  170. package/dist/types/src/RemoteSecurity.d.ts +50 -0
  171. package/dist/types/src/RemoteSecurity.d.ts.map +1 -0
  172. package/dist/types/src/storage/AdmissionStorage.d.ts +185 -0
  173. package/dist/types/src/storage/AdmissionStorage.d.ts.map +1 -0
  174. package/dist/types/src/storage/RecoveryContract.d.ts +50 -0
  175. package/dist/types/src/storage/RecoveryContract.d.ts.map +1 -0
  176. package/dist/types/src/storage/Storage.d.ts +32 -19
  177. package/dist/types/src/storage/Storage.d.ts.map +1 -1
  178. package/dist/types/src/storage/knex/KnexStorage.d.ts +8 -8
  179. package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
  180. package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
  181. package/dist/types/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.d.ts +4 -0
  182. package/dist/types/src/storage/knex/migrations/2026-09-17-001-topical-uniqueness.d.ts.map +1 -0
  183. package/dist/types/src/storage/knex/migrations/2026-09-20-001-spent-by.d.ts +5 -0
  184. package/dist/types/src/storage/knex/migrations/2026-09-20-001-spent-by.d.ts.map +1 -0
  185. package/dist/types/src/storage/mongo/MongoAdmissionPlan.d.ts +18 -0
  186. package/dist/types/src/storage/mongo/MongoAdmissionPlan.d.ts.map +1 -0
  187. package/dist/types/src/storage/mongo/MongoAdmissionStorage.d.ts +112 -0
  188. package/dist/types/src/storage/mongo/MongoAdmissionStorage.d.ts.map +1 -0
  189. package/dist/types/src/storage/mongo/MongoOverlayStorage.d.ts +71 -0
  190. package/dist/types/src/storage/mongo/MongoOverlayStorage.d.ts.map +1 -0
  191. package/dist/types/src/storage/mongo/MongoPayloadStore.d.ts +134 -0
  192. package/dist/types/src/storage/mongo/MongoPayloadStore.d.ts.map +1 -0
  193. package/dist/types/src/storage/mongo/MongoReadGuards.d.ts +44 -0
  194. package/dist/types/src/storage/mongo/MongoReadGuards.d.ts.map +1 -0
  195. package/dist/types/src/storage/mongo/MongoSchema.d.ts +67 -0
  196. package/dist/types/src/storage/mongo/MongoSchema.d.ts.map +1 -0
  197. package/dist/types/src/storage/mongo/MongoTransactionRunner.d.ts +69 -0
  198. package/dist/types/src/storage/mongo/MongoTransactionRunner.d.ts.map +1 -0
  199. package/dist/types/src/storage/mongo.d.ts +9 -0
  200. package/dist/types/src/storage/mongo.d.ts.map +1 -0
  201. package/package.json +11 -5
package/README.md CHANGED
@@ -79,6 +79,52 @@ The root entry point exports:
79
79
  imports remain available through the documented package export map, but new
80
80
  applications should prefer the root entry point wherever possible.
81
81
 
82
+ ## Optional persistence capability
83
+
84
+ `AdmissionStorage` defines an additive v1 atomic admission contract for future
85
+ adapters. `storageHasAdmission(storage)` reports whether the optional
86
+ `admission` field is present. `getAdmissionStorage(storage)` detects an explicit provider with both
87
+ commit and reconciliation methods. Existing Knex and injected legacy adapters
88
+ remain supported; their individual methods do not imply atomic submission.
89
+ When `getAdmissionStorage(storage)` observes a complete `overlay-admission-v1`
90
+ provider, `Engine.submit` builds an admission plan and returns the saved STEAK
91
+ only after majority commit. The SQL/Knex path and its early STEAK callback are
92
+ unchanged; that callback is not a durable commit receipt.
93
+
94
+ The contract separates local commit, index visibility and propagation. It binds
95
+ operation identity to verified transaction, topic/policy and off-chain context;
96
+ uses ready payload references and outbox intents; and fences recovery by both
97
+ chain epoch and topic history generation. Helper functions and shared fixtures
98
+ pin exact integers, deterministic identity, leases and cursor eligibility.
99
+ See the [persistence specification](https://github.com/bsv-blockchain/ts-stack/blob/main/specs/overlay/persistence-v1.md).
100
+ No consumer migration, storage-default change or database migration is included
101
+ in this release candidate.
102
+
103
+ ## Optional Mongo foundation
104
+
105
+ The package also contains an opt-in MongoDB foundation for schema bootstrap,
106
+ content-addressed payload publication, reference guards, payload collection,
107
+ and an explicit `AdmissionStorage` adapter. Mongo is not the default Engine
108
+ storage selection; importing `@bsv/overlay` alone does not load MongoDB.
109
+
110
+ Applications using a Mongo deep entry point install the optional peer first:
111
+
112
+ ```sh
113
+ npm install @bsv/overlay mongodb@^7.5.0
114
+ ```
115
+
116
+ The initial entry points are
117
+ `@bsv/overlay/storage/mongo/MongoSchema`,
118
+ `@bsv/overlay/storage/mongo/MongoPayloadStore`,
119
+ `@bsv/overlay/storage/mongo/MongoAdmissionStorage`, and
120
+ `@bsv/overlay/storage/mongo/MongoOverlayStorage`. They require an explicitly
121
+ operated unsharded replica set; the supported deployment profile is three
122
+ members. Payload publication makes GridFS bytes physically `published` before
123
+ the guarded payload row becomes `ready`; caller-session reference and GC
124
+ operations share that row guard. See the [Mongo v1
125
+ foundation](https://github.com/bsv-blockchain/ts-stack/blob/main/specs/overlay/mongo-v1.md)
126
+ for operational bounds, recovery rules, and the opt-in admission path.
127
+
82
128
  ## Runtime and package formats
83
129
 
84
130
  The package supports both module systems:
@@ -117,9 +163,129 @@ For production deployments:
117
163
  - avoid logging raw secrets, authorization headers, or unbounded payloads;
118
164
  - monitor readiness, proof acquisition, synchronization, and unproven state.
119
165
 
166
+ Run every `KnexStorageMigrations` migration before serving traffic. The topical
167
+ uniqueness migration deliberately stops when duplicate `(txid, outputIndex,
168
+ topic)` output rows or `(txid, topic)` applied-transaction rows already exist;
169
+ operators must inspect and reconcile those records rather than letting a
170
+ migration discard security-relevant state. The engine serializes submissions
171
+ inside one process, while database uniqueness and conditional unspent updates
172
+ enforce the same spend/admission boundary across processes.
173
+
174
+ Prefer roll-forward after these migrations. Older package versions do not know
175
+ their migration names, so a bare image rollback can fail migration-list
176
+ validation. To restore an older version, stop writes and either use the new
177
+ migration source to reverse `spentBy` and topical uniqueness in reverse order
178
+ after exporting and reconciling every `spent`/`spentBy` association, or restore
179
+ coordinated pre-migration SQL and lookup-store backups. Never drop `spentBy` or
180
+ restore only one store without preserving that spend evidence.
181
+
182
+ Submission is intentionally not a single all-or-nothing transaction across
183
+ primary storage and external lookup indexes. The Engine validates before
184
+ mutation, propagates failures, and never invokes the success callback until all
185
+ attempted writes complete, but a late failure does not roll back an earlier
186
+ committed write or third-party index update. A rejected submission therefore
187
+ does not assert rollback. Operators and custom adapters must detect and
188
+ reconcile partially applied work before replaying or exposing affected state.
189
+
190
+ GASP v1 bidirectional `submitNode` omits the `spentBy` parent outpoint. A
191
+ receiver must already have the relevant parent, request it in a subsequent
192
+ sync round, or reject the graph; it must never infer the edge from the child
193
+ alone. Pull-only operation avoids that assumption. The Overlay pull adapter
194
+ binds topic, graph, raw transaction, output, parent edge, proof, resource
195
+ limits, and historical spend state before finalization.
196
+
197
+ Remote GASP/BASM peers and propagation/header providers are separate network
198
+ authorities. Production adapters accept credential-free public HTTPS, pin DNS
199
+ addresses to the requested origin, reject redirects, bound streamed bodies and
200
+ deadlines, and correlate every transaction, topic, height, index, and proof to
201
+ its request. Private or HTTP targets belong only in explicit isolated local
202
+ development configurations.
203
+
204
+ Reorg event streams are hints rather than independent chain-state authority.
205
+ Before demoting a proven admission for a reported orphaned block, the Engine
206
+ requires the configured canonical header resolver to return a different hash
207
+ for that exact height. An unavailable resolver or a hash that is still
208
+ canonical rejects the event before any durable mutation.
209
+
210
+ SHIP tracker answers are discovery hints, not trusted routing authority. Before
211
+ using a discovered endpoint for GASP, the Engine now requires a canonical
212
+ identity-linked advertisement signature, a one-satoshi token, exact BEEF/TXID
213
+ correlation, and the requested topic. The endpoint still passes through the
214
+ same public-HTTPS and DNS-pinning controls as an explicitly configured peer.
215
+
216
+ Custom `Storage`, `LookupService`, `TopicManager`, advertiser, chain-tracker,
217
+ and header-resolver implementations are trusted local components, but their
218
+ runtime results are still checked before the Engine mutates or returns state.
219
+ Storage queries must return only records bound to the requested outpoint,
220
+ topic, height, block hash, and score window. Lookup formulas use a default
221
+ 1,000-result limit and an unconditional 100,000-result safety ceiling; history
222
+ depth, context bytes, stored BEEF, graph fan-out, and aggregate traversal work
223
+ are also bounded. `maxLookupResults: -1` disables the operator-selected lower
224
+ limit, not the hard safety ceiling. BASM anchor stores must return strictly
225
+ ordered, request-bound, canonical hash records.
226
+
227
+ Public component metadata is copied through a bounded own-data-property schema;
228
+ names, descriptions, versions, and HTTP(S) links that are malformed, accessor
229
+ backed, or oversized fall back to a minimal local registry description.
230
+ Component Markdown documentation is limited to 1 MiB before it crosses the
231
+ Engine/HTTP boundary.
232
+
120
233
  `@bsv/overlay-express` supplies these standard HTTP controls while preserving
121
234
  public protocol access by default.
122
235
 
236
+ ### BASM peer validation and current recovery limits
237
+
238
+ BASM uses the current BRC-136 ordered admitted subset and block-anchored TAC.
239
+ The five existing JSON POST routes remain compatible; empty tips remain
240
+ `{ topic, blockHeight: -1, tac: <zero hash> }`. Unsupported storage capabilities
241
+ are errors, not empty histories. The remote client validates response shape,
242
+ topic/height/hash binding, ordered unique admitted positions, contiguous
243
+ returned ranges, and complete proof/raw response ID sets before use.
244
+
245
+ `BASMRemote` retains its injectable third `fetch` argument and accepts optional
246
+ limits as a fourth argument. Defaults are 64 MiB per decoded response, 8 MiB
247
+ per proof, 32 MiB per raw transaction, 100,000 admitted entries, 1,000 requested
248
+ txids, 1,024 requested anchor heights, and 30 seconds per request including its
249
+ body. Aggregate response limits also apply to hex-encoded transactions. These
250
+ are configurable local acceptance limits, not consensus rules. Standard fetch
251
+ bodies are bounded while streaming; legacy injected `text()` implementations
252
+ are checked after buffering. Classified errors expose `code`, including
253
+ `BASM_UNSUPPORTED`, `BASM_RESOURCE_LIMIT`, and `BASM_TIMEOUT`.
254
+
255
+ Reconciliation requires a canonical header resolver as well as a ChainTracker.
256
+ An optional `TopicAnchorHeader.blockTransactionCount` must come independently
257
+ from the trusted canonical provider and refer to that exact `blockHash`.
258
+ It enables full-block count/index bounds and odd-duplication checks. The sync
259
+ report's `positionValidation` is `canonical-count` only when that evidence was
260
+ available for every checked proof; legacy providers yield `encoded-offset-only`.
261
+ A Merkle root plus an encoded offset alone cannot disambiguate Bitcoin's
262
+ duplicate-last-leaf position ambiguity. No provider is required to add the
263
+ field, and the engine does not download full blocks to infer it.
264
+
265
+ Forward sync pages now contain at most 1,000 anchors to fit the standard HTTP
266
+ server. Proof height, requested original index, canonical hash/root, raw byte
267
+ identity, TAC continuity, and repeated peer anchors are checked before historical
268
+ submission. Claimed admitted-list indices are bound to the compound path whenever
269
+ a remote list is used as evidence, including when every remote txid is already
270
+ local. Inclusion uses the chain tracker root/height check rather than
271
+ `MerklePath.verify`, which also enforces coinbase 100-block spendability.
272
+ Because inclusion is proven independently, admission submits in the
273
+ `historical-tx-no-spv` mode so `Transaction.verify` does not re-apply that
274
+ coinbase rule; the public `historical-tx` mode keeps full SPV verification.
275
+ Admission still applies the local TopicManager, and because every admitted
276
+ transaction carries its extracted Merkle path, neither network broadcast nor
277
+ overlay propagation occurs. Automatic BASM sync remains disabled by default.
278
+
279
+ This is bounded protocol hardening, not durable recovery. An empty local node
280
+ whose topic genesis precedes the recent bootstrap window now refuses the
281
+ untrusted TAC prefix; this intentionally replaces the old unchecked tail
282
+ behavior. A block above 1,000 admitted entries reaches a request-limit error
283
+ until proof/raw chunking is implemented. Equal-height/local-ahead divergence,
284
+ whole-target bootstrap, durable cursors/leases, atomic revision fencing, and
285
+ truthful per-topic agreement status remain required follow-up work. A successful
286
+ legacy report does not establish global completeness, current unspentness, or
287
+ durable recovery completion. See [BASM details](./docs/BRC-136-BASM.md).
288
+
123
289
  ## Development
124
290
 
125
291
  From the repository root:
package/dist/cjs/mod.d.ts CHANGED
@@ -3,6 +3,9 @@ export { serializeErrorForLog, serializeLogValue } from './src/SafeLog.js';
3
3
  export type * from './src/LookupService.js';
4
4
  export type { TopicAdmittanceContext, TopicManager } from './src/TopicManager.js';
5
5
  export type { Storage, AppliedTransaction } from './src/storage/Storage.js';
6
+ export { storageHasAdmission } from './src/storage/Storage.js';
7
+ export * from './src/storage/AdmissionStorage.js';
8
+ export * from './src/storage/RecoveryContract.js';
6
9
  export type { Output } from './src/Output.js';
7
10
  export type { TaggedBEEF, STEAK, LookupQuestion, LookupAnswer, AdmittanceInstructions } from '@bsv/sdk';
8
11
  export type { LookupFormula } from './src/LookupFormula.js';
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../mod.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAC1E,mBAAmB,wBAAwB,CAAA;AAC3C,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAGjF,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAC3E,YAAY,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,YAAY,EACV,UAAU,EACV,KAAK,EACL,cAAc,EACd,YAAY,EACZ,sBAAsB,EACvB,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,EACL,cAAc,EACd,eAAe,EACf,UAAU,EACV,0BAA0B,EAC3B,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,yBAAyB,EACzB,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,gBAAgB,EACjB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAA;AAC/D,OAAO,KAAK,qBAAqB,MAAM,sCAAsC,CAAA"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../mod.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAC1E,mBAAmB,wBAAwB,CAAA;AAC3C,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAGjF,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,cAAc,mCAAmC,CAAA;AACjD,cAAc,mCAAmC,CAAA;AACjD,YAAY,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,YAAY,EACV,UAAU,EACV,KAAK,EACL,cAAc,EACd,YAAY,EACZ,sBAAsB,EACvB,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACxE,OAAO,EACL,cAAc,EACd,eAAe,EACf,UAAU,EACV,0BAA0B,EAC3B,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,yBAAyB,EACzB,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,gBAAgB,EACjB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAA;AAC/D,OAAO,KAAK,qBAAqB,MAAM,sCAAsC,CAAA"}
package/dist/cjs/mod.js CHANGED
@@ -15,6 +15,9 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
18
21
  var __importStar = (this && this.__importStar) || (function () {
19
22
  var ownKeys = function(o) {
20
23
  ownKeys = Object.getOwnPropertyNames || function (o) {
@@ -33,13 +36,17 @@ var __importStar = (this && this.__importStar) || (function () {
33
36
  };
34
37
  })();
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.KnexStorageMigrations = exports.KnexStorage = exports.extractMerkleProofMetadata = exports.computeTac = exports.computeBasmRoot = exports.BASM_ZERO_HASH = exports.serializeLogValue = exports.serializeErrorForLog = exports.Engine = void 0;
39
+ exports.KnexStorageMigrations = exports.KnexStorage = exports.extractMerkleProofMetadata = exports.computeTac = exports.computeBasmRoot = exports.BASM_ZERO_HASH = exports.storageHasAdmission = exports.serializeLogValue = exports.serializeErrorForLog = exports.Engine = void 0;
37
40
  // Fundamentals
38
41
  var Engine_js_1 = require("./src/Engine.js");
39
42
  Object.defineProperty(exports, "Engine", { enumerable: true, get: function () { return Engine_js_1.Engine; } });
40
43
  var SafeLog_js_1 = require("./src/SafeLog.js");
41
44
  Object.defineProperty(exports, "serializeErrorForLog", { enumerable: true, get: function () { return SafeLog_js_1.serializeErrorForLog; } });
42
45
  Object.defineProperty(exports, "serializeLogValue", { enumerable: true, get: function () { return SafeLog_js_1.serializeLogValue; } });
46
+ var Storage_js_1 = require("./src/storage/Storage.js");
47
+ Object.defineProperty(exports, "storageHasAdmission", { enumerable: true, get: function () { return Storage_js_1.storageHasAdmission; } });
48
+ __exportStar(require("./src/storage/AdmissionStorage.js"), exports);
49
+ __exportStar(require("./src/storage/RecoveryContract.js"), exports);
43
50
  var BASM_js_1 = require("./src/BASM.js");
44
51
  Object.defineProperty(exports, "BASM_ZERO_HASH", { enumerable: true, get: function () { return BASM_js_1.BASM_ZERO_HASH; } });
45
52
  Object.defineProperty(exports, "computeBasmRoot", { enumerable: true, get: function () { return BASM_js_1.computeBasmRoot; } });
@@ -1 +1 @@
1
- {"version":3,"file":"mod.js","sourceRoot":"","sources":["../../mod.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,eAAe;AACf,6CAAwC;AAA/B,mGAAA,MAAM,OAAA;AACf,+CAA0E;AAAjE,kHAAA,oBAAoB,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAiBhD,yCAKsB;AAJpB,yGAAA,cAAc,OAAA;AACd,0GAAA,eAAe,OAAA;AACf,qGAAA,UAAU,OAAA;AACV,qHAAA,0BAA0B,OAAA;AAqB5B,0BAA0B;AAC1B,oEAA+D;AAAtD,6GAAA,WAAW,OAAA;AACpB,QAAY,qBAAqB,iEAA4C"}
1
+ {"version":3,"file":"mod.js","sourceRoot":"","sources":["../../mod.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,eAAe;AACf,6CAAwC;AAA/B,mGAAA,MAAM,OAAA;AACf,+CAA0E;AAAjE,kHAAA,oBAAoB,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAMhD,uDAA8D;AAArD,iHAAA,mBAAmB,OAAA;AAC5B,oEAAiD;AACjD,oEAAiD;AAYjD,yCAKsB;AAJpB,yGAAA,cAAc,OAAA;AACd,0GAAA,eAAe,OAAA;AACf,qGAAA,UAAU,OAAA;AACV,qHAAA,0BAA0B,OAAA;AAqB5B,0BAA0B;AAC1B,oEAA+D;AAAtD,6GAAA,WAAW,OAAA;AACpB,QAAY,qBAAqB,iEAA4C"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/overlay",
3
- "version": "2.3.1",
3
+ "version": "2.6.1",
4
4
  "sideEffects": false,
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -26,7 +26,7 @@
26
26
  "format:check": "pnpm --workspace-root exec prettier --check \"packages/overlays/overlay/{README.md,jest.config.js,mod.ts,package.json,tsconfig*.json}\"",
27
27
  "lint": "oxlint mod.ts src jest.config.js --deny-warnings",
28
28
  "lint:ci": "pnpm lint",
29
- "pack:check": "pnpm build && node ../../../scripts/check-package-artifact.mjs . --exports BASM_ZERO_HASH,Engine,KnexStorage,KnexStorageMigrations,computeBasmRoot,computeTac,extractMerkleProofMetadata,serializeErrorForLog,serializeLogValue --entry-exports \"./storage=;./storage/knex/KnexStorage=KnexStorage\"",
29
+ "pack:check": "pnpm build && node ../../../scripts/check-package-artifact.mjs . --exports admissionSemanticDigest,canAdvanceGaspCursor,getAdmissionStorage,isRecoveryLeaseCurrent,isReplaySafeProjection,parseStorageUint64,parseStorageOutputIndex,storageHasAdmission,BASM_ZERO_HASH,Engine,KnexStorage,KnexStorageMigrations,computeBasmRoot,computeTac,extractMerkleProofMetadata,serializeErrorForLog,serializeLogValue --entry-exports \"./storage=storageHasAdmission;./storage/knex/KnexStorage=KnexStorage\"",
30
30
  "typecheck": "tsc --project tsconfig.base.json --noEmit --incremental false",
31
31
  "build": "tsc -b && tsconfig-to-dual-package tsconfig.cjs.json",
32
32
  "dev": "tsc -b -w",
@@ -62,18 +62,24 @@
62
62
  "ts-jest": "^29.4.12",
63
63
  "ts2md": "^0.2.8",
64
64
  "tsconfig-to-dual-package": "^1.2.0",
65
- "typescript": "npm:@typescript/typescript6@6.0.2"
65
+ "typescript": "npm:@typescript/typescript6@6.0.2",
66
+ "mongodb": "7.5.0",
67
+ "mongodb-memory-server": "^11.2.0"
66
68
  },
67
69
  "dependencies": {
68
70
  "@bsv/gasp": "workspace:^",
69
71
  "knex": "^3.3.0"
70
72
  },
71
73
  "peerDependencies": {
72
- "@bsv/sdk": "^2.1.6"
74
+ "@bsv/sdk": "^2.1.6",
75
+ "mongodb": "^7.5.0"
73
76
  },
74
77
  "peerDependenciesMeta": {
75
78
  "@bsv/sdk": {
76
79
  "optional": false
80
+ },
81
+ "mongodb": {
82
+ "optional": true
77
83
  }
78
84
  },
79
85
  "typesVersions": {
@@ -35,6 +35,8 @@ export interface TopicAnchorHeader {
35
35
  blockHeight: number;
36
36
  blockHash: string;
37
37
  merkleRoot?: string;
38
+ /** Independently obtained full block transaction count, bound to this blockHash. */
39
+ blockTransactionCount?: number;
38
40
  }
39
41
  export type TopicAnchorHeaderResolver = (blockHeight: number) => Promise<TopicAnchorHeader | undefined>;
40
42
  export interface TopicAnchorRangeRequest {
@@ -87,6 +89,10 @@ export interface BASMPeerSyncReport {
87
89
  checkedHeights: number[];
88
90
  missingTxids: string[];
89
91
  fetchedTxCount: number;
92
+ /** Weakest position evidence used by this attempt; absent when no proof was checked. */
93
+ positionValidation?: 'canonical-count' | 'encoded-offset-only';
94
+ /** Present for classified peer protocol or transport failures. */
95
+ errorCode?: string;
90
96
  message?: string;
91
97
  }
92
98
  type AdmittedTxLike = string | AdmittedTxRef;
@@ -1 +1 @@
1
- {"version":3,"file":"BASM.d.ts","sourceRoot":"","sources":["../../../src/BASM.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BASM.d.ts","sourceRoot":"","sources":["../../../src/BASM.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ exports.computeTac = computeTac;
6
6
  exports.extractMerkleProofMetadata = extractMerkleProofMetadata;
7
7
  const node_crypto_1 = require("node:crypto");
8
8
  exports.BASM_ZERO_HASH = '0000000000000000000000000000000000000000000000000000000000000000';
9
+ const MAX_BASM_ROOT_LEAVES = 100000;
9
10
  function sha256d(buffer) {
10
11
  const first = (0, node_crypto_1.createHash)('sha256').update(buffer).digest();
11
12
  return (0, node_crypto_1.createHash)('sha256').update(first).digest();
@@ -23,15 +24,27 @@ function internalToDisplayHex(hash) {
23
24
  return Buffer.from(hash).reverse().toString('hex');
24
25
  }
25
26
  function normalizeAdmittedTxids(admitted) {
26
- return admitted
27
- .map((item, originalIndex) => {
28
- if (typeof item === 'string') {
29
- return { txid: item, blockIndex: originalIndex };
27
+ if (!Array.isArray(admitted) || admitted.length > MAX_BASM_ROOT_LEAVES) {
28
+ throw new TypeError(`BASM admission sets are capped at ${MAX_BASM_ROOT_LEAVES} entries`);
29
+ }
30
+ const normalized = admitted.map((item, originalIndex) => {
31
+ const candidate = typeof item === 'string' ? { txid: item, blockIndex: originalIndex } : item;
32
+ if (typeof candidate !== 'object' ||
33
+ candidate === null ||
34
+ typeof candidate.txid !== 'string' ||
35
+ !Number.isSafeInteger(candidate.blockIndex) ||
36
+ candidate.blockIndex < 0) {
37
+ throw new TypeError(`BASM admission at index ${originalIndex} is invalid`);
30
38
  }
31
- return item;
32
- })
33
- .sort((a, b) => a.blockIndex - b.blockIndex)
34
- .map(item => item.txid.toLowerCase());
39
+ assertHashHex(candidate.txid, `BASM admission at index ${originalIndex}`);
40
+ return { txid: candidate.txid.toLowerCase(), blockIndex: candidate.blockIndex };
41
+ });
42
+ const txids = new Set(normalized.map(item => item.txid));
43
+ const blockIndexes = new Set(normalized.map(item => item.blockIndex));
44
+ if (txids.size !== normalized.length || blockIndexes.size !== normalized.length) {
45
+ throw new TypeError('BASM admission sets must contain unique txids and block indexes');
46
+ }
47
+ return normalized.sort((a, b) => a.blockIndex - b.blockIndex).map(item => item.txid);
35
48
  }
36
49
  /**
37
50
  * Computes a BRC-136 BASM root from admitted topic txids.
@@ -1 +1 @@
1
- {"version":3,"file":"BASM.js","sourceRoot":"","sources":["../../../src/BASM.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BASM.js","sourceRoot":"","sources":["../../../src/BASM.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,21 @@
1
1
  import type { AdmittedListResponse, CompoundMerklePathResponse, RawTransactionResponse, TopicAnchorRangeResponse, TopicAnchorTip } from './BASM.js';
2
+ import type { BASMRemoteLimits } from './BASMValidation.js';
3
+ export { BASMProtocolError } from './BASMValidation.js';
4
+ export type { BASMRemoteLimits } from './BASMValidation.js';
2
5
  export declare class BASMRemote {
6
+ private readonly limits;
3
7
  private readonly endpoint;
4
8
  private readonly topic;
5
9
  private readonly fetchImpl;
6
- constructor(endpoint: string, topic: string, fetchImpl?: typeof fetch);
10
+ constructor(endpoint: string, topic: string, fetchImpl?: typeof fetch, limits?: Partial<BASMRemoteLimits>);
7
11
  requestTopicAnchorTip(): Promise<TopicAnchorTip>;
8
12
  requestTopicAnchorRange(fromHeight: number, toHeight: number): Promise<TopicAnchorRangeResponse>;
9
13
  requestAdmittedList(blockHeight: number, blockHash?: string): Promise<AdmittedListResponse>;
10
14
  requestCompoundMerklePath(blockHeight: number, txids: string[]): Promise<CompoundMerklePathResponse>;
11
15
  requestRawTransactions(txids: string[]): Promise<RawTransactionResponse>;
16
+ private requireCoordinate;
17
+ private readResponse;
12
18
  private post;
19
+ private postResponse;
13
20
  }
14
21
  //# sourceMappingURL=BASMRemote.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BASMRemote.d.ts","sourceRoot":"","sources":["../../../src/BASMRemote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,cAAc,EACf,MAAM,WAAW,CAAA;AAElB,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAH5B,YACmB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,OAAO,KAA8B,EAC9D;IAEC,qBAAqB,IAAI,OAAO,CAAC,cAAc,CAAC,CAErD;IAEK,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAErG;IAEK,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAEhG;IAEK,yBAAyB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAEzG;IAEK,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAE7E;YAEa,IAAI;CAkBnB"}
1
+ {"version":3,"file":"BASMRemote.d.ts","sourceRoot":"","sources":["../../../src/BASMRemote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,cAAc,EACf,MAAM,WAAW,CAAA;AAelB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAG3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAE3D,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IAExC,YACE,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,OAAO,KAAK,EACxB,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM,EAavC;IAEK,qBAAqB,IAAI,OAAO,CAAC,cAAc,CAAC,CAErD;IAEK,uBAAuB,CAC3B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,CAAC,CA2BnC;IAEK,mBAAmB,CACvB,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC,CAiB/B;IAEK,yBAAyB,CAC7B,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,0BAA0B,CAAC,CAoBrC;IAEK,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAkC7E;IAED,OAAO,CAAC,iBAAiB;YAOX,YAAY;YA6CZ,IAAI;YAkBJ,YAAY;CA8C3B"}
@@ -1,28 +1,169 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BASMRemote = void 0;
3
+ exports.BASMRemote = exports.BASMProtocolError = void 0;
4
+ const BASMValidation_js_1 = require("./BASMValidation.js");
5
+ const RemoteSecurity_js_1 = require("./RemoteSecurity.js");
6
+ var BASMValidation_js_2 = require("./BASMValidation.js");
7
+ Object.defineProperty(exports, "BASMProtocolError", { enumerable: true, get: function () { return BASMValidation_js_2.BASMProtocolError; } });
4
8
  class BASMRemote {
5
- constructor(endpoint, topic, fetchImpl = fetch.bind(globalThis)) {
6
- this.endpoint = endpoint;
9
+ constructor(endpoint, topic, fetchImpl, limits = {}) {
10
+ (0, RemoteSecurity_js_1.assertTopic)(topic);
11
+ const secured = (0, RemoteSecurity_js_1.securePeerFetch)(endpoint, fetchImpl);
12
+ this.endpoint = secured.endpoint;
7
13
  this.topic = topic;
8
- this.fetchImpl = fetchImpl;
14
+ this.fetchImpl = secured.fetchImpl;
15
+ this.limits = Object.freeze({ ...BASMValidation_js_1.DEFAULT_BASM_REMOTE_LIMITS, ...limits });
16
+ for (const [key, value] of Object.entries(this.limits)) {
17
+ if (!Number.isSafeInteger(value) || value <= 0)
18
+ throw new TypeError(`Invalid BASM limit: ${key}`);
19
+ }
20
+ if (this.limits.timeoutMs > 2147483647)
21
+ throw new TypeError('BASM timeout exceeds timer range');
9
22
  }
10
23
  async requestTopicAnchorTip() {
11
- return await this.post('/requestTopicAnchorTip', {});
24
+ return (0, BASMValidation_js_1.basmTip)(await this.post('/requestTopicAnchorTip', {}), this.topic);
12
25
  }
13
26
  async requestTopicAnchorRange(fromHeight, toHeight) {
14
- return await this.post('/requestTopicAnchorRange', { fromHeight, toHeight });
27
+ (0, BASMValidation_js_1.basmInteger)(fromHeight, 'range start');
28
+ (0, BASMValidation_js_1.basmInteger)(toHeight, 'range end', fromHeight);
29
+ (0, BASMValidation_js_1.requireBASMLimit)(toHeight - fromHeight < this.limits.maxAnchorRange, 'BASM anchor range exceeds limit');
30
+ const obj = (0, BASMValidation_js_1.basmObject)(await this.post('/requestTopicAnchorRange', { fromHeight, toHeight }));
31
+ (0, BASMValidation_js_1.requireBASM)(obj.topic === this.topic && Array.isArray(obj.anchors), 'Invalid BASM anchor range');
32
+ (0, BASMValidation_js_1.requireBASMLimit)(obj.anchors.length <= toHeight - fromHeight + 1, 'BASM anchor count exceeds requested range');
33
+ const anchors = obj.anchors.map(value => (0, BASMValidation_js_1.basmAnchor)(value, this.topic));
34
+ let previousHeight;
35
+ for (const anchor of anchors) {
36
+ (0, BASMValidation_js_1.requireBASM)(anchor.blockHeight >= fromHeight && anchor.blockHeight <= toHeight, 'BASM anchor outside requested range');
37
+ (0, BASMValidation_js_1.requireBASM)(previousHeight === undefined || anchor.blockHeight === previousHeight + 1, 'BASM anchor range has a gap or is unordered');
38
+ previousHeight = anchor.blockHeight;
39
+ }
40
+ return { topic: this.topic, anchors };
15
41
  }
16
42
  async requestAdmittedList(blockHeight, blockHash) {
17
- return await this.post('/requestAdmittedList', { blockHeight, blockHash });
43
+ (0, BASMValidation_js_1.basmInteger)(blockHeight, 'height');
44
+ if (blockHash !== undefined)
45
+ (0, BASMValidation_js_1.basmHash)(blockHash, 'block hash');
46
+ const obj = (0, BASMValidation_js_1.basmObject)(await this.post('/requestAdmittedList', { blockHeight, blockHash }));
47
+ this.requireCoordinate(obj, blockHeight);
48
+ const responseHash = obj.blockHash === undefined ? undefined : (0, BASMValidation_js_1.basmHash)(obj.blockHash, 'block hash');
49
+ (0, BASMValidation_js_1.requireBASM)(blockHash === undefined || responseHash === undefined || responseHash === blockHash, 'BASM admitted block hash mismatch');
50
+ return {
51
+ topic: this.topic,
52
+ blockHeight,
53
+ ...(responseHash === undefined ? {} : { blockHash: responseHash }),
54
+ admitted: (0, BASMValidation_js_1.basmAdmitted)(obj.admitted, this.limits.maxAdmittedTxids)
55
+ };
18
56
  }
19
57
  async requestCompoundMerklePath(blockHeight, txids) {
20
- return await this.post('/requestCompoundMerklePath', { blockHeight, txids });
58
+ (0, BASMValidation_js_1.basmInteger)(blockHeight, 'height');
59
+ const requested = (0, BASMValidation_js_1.basmTxids)(txids, this.limits.maxRequestedTxids);
60
+ (0, BASMValidation_js_1.requireBASM)(requested.length > 0, 'At least one BASM proof txid is required');
61
+ const obj = (0, BASMValidation_js_1.basmObject)(await this.post('/requestCompoundMerklePath', { blockHeight, txids: requested }));
62
+ this.requireCoordinate(obj, blockHeight);
63
+ const returned = (0, BASMValidation_js_1.basmTxids)(obj.txids, requested.length);
64
+ const requestedSet = new Set(requested);
65
+ (0, BASMValidation_js_1.requireBASM)(returned.length === requested.length && returned.every(txid => requestedSet.has(txid)), 'BASM proof txids do not match request');
66
+ return {
67
+ topic: this.topic,
68
+ blockHeight,
69
+ txids: returned,
70
+ merklePath: (0, BASMValidation_js_1.basmHex)(obj.merklePath, 'Merkle path', this.limits.maxProofBytes)
71
+ };
21
72
  }
22
73
  async requestRawTransactions(txids) {
23
- return await this.post('/requestRawTransactions', { txids });
74
+ const requested = new Set((0, BASMValidation_js_1.basmTxids)(txids, this.limits.maxRequestedTxids));
75
+ const obj = (0, BASMValidation_js_1.basmObject)(await this.post('/requestRawTransactions', { txids: [...requested] }));
76
+ (0, BASMValidation_js_1.requireBASM)(Array.isArray(obj.transactions), 'Invalid BASM raw transactions');
77
+ (0, BASMValidation_js_1.requireBASMLimit)(obj.transactions.length <= requested.size, 'BASM raw transaction count exceeds request');
78
+ const seen = new Set();
79
+ const transactions = obj.transactions.map(value => {
80
+ const record = (0, BASMValidation_js_1.basmObject)(value);
81
+ const txid = (0, BASMValidation_js_1.basmHash)(record.txid, 'txid');
82
+ (0, BASMValidation_js_1.requireBASM)(requested.has(txid) && !seen.has(txid), 'Unexpected or duplicate BASM raw transaction');
83
+ seen.add(txid);
84
+ const rawTx = (0, BASMValidation_js_1.basmHex)(record.rawTx, 'raw transaction', this.limits.maxRawTransactionBytes);
85
+ (0, RemoteSecurity_js_1.assertRawTransactionMatches)(txid, rawTx);
86
+ return {
87
+ txid,
88
+ rawTx
89
+ };
90
+ });
91
+ const missing = (0, BASMValidation_js_1.basmTxids)(obj.missing, requested.size);
92
+ for (const txid of missing) {
93
+ (0, BASMValidation_js_1.requireBASM)(requested.has(txid) && !seen.has(txid), 'Unexpected or conflicting BASM missing txid');
94
+ seen.add(txid);
95
+ }
96
+ (0, BASMValidation_js_1.requireBASM)(seen.size === requested.size, 'BASM raw response omits requested txids');
97
+ return { transactions, missing };
98
+ }
99
+ requireCoordinate(obj, blockHeight) {
100
+ (0, BASMValidation_js_1.requireBASM)(obj.topic === this.topic && obj.blockHeight === blockHeight, 'BASM response topic or height mismatch');
101
+ }
102
+ async readResponse(response, signal) {
103
+ var _a, _b;
104
+ const advertisedLength = (_a = response.headers) === null || _a === void 0 ? void 0 : _a.get('content-length');
105
+ if (advertisedLength !== null && advertisedLength !== undefined) {
106
+ if (Number(advertisedLength) > this.limits.maxResponseBytes) {
107
+ void ((_b = response.body) === null || _b === void 0 ? void 0 : _b.cancel().catch(() => { }));
108
+ (0, BASMValidation_js_1.requireBASMLimit)(false, 'BASM response exceeds byte limit');
109
+ }
110
+ }
111
+ // Preserve injected fetch implementations that expose only text(). Real HTTP
112
+ // responses are bounded while streaming, including decoded chunked bodies.
113
+ if (response.body === undefined || response.body === null) {
114
+ const text = await response.text();
115
+ (0, BASMValidation_js_1.requireBASMLimit)(Buffer.byteLength(text) <= this.limits.maxResponseBytes, 'BASM response exceeds byte limit');
116
+ return text;
117
+ }
118
+ const reader = response.body.getReader();
119
+ const chunks = [];
120
+ let size = 0;
121
+ const cancel = () => {
122
+ void reader.cancel().catch(() => { });
123
+ };
124
+ signal.addEventListener('abort', cancel, { once: true });
125
+ try {
126
+ while (true) {
127
+ signal.throwIfAborted();
128
+ const { done, value } = await reader.read();
129
+ if (done)
130
+ break;
131
+ size += value.byteLength;
132
+ (0, BASMValidation_js_1.requireBASMLimit)(size <= this.limits.maxResponseBytes, 'BASM response exceeds byte limit');
133
+ chunks.push(value);
134
+ }
135
+ signal.throwIfAborted();
136
+ return Buffer.concat(chunks, size).toString('utf8');
137
+ }
138
+ catch (error) {
139
+ cancel();
140
+ throw error;
141
+ }
142
+ finally {
143
+ signal.removeEventListener('abort', cancel);
144
+ reader.releaseLock();
145
+ }
24
146
  }
25
147
  async post(path, body) {
148
+ const controller = new AbortController();
149
+ let timer;
150
+ const deadline = new Promise((_resolve, reject) => {
151
+ timer = setTimeout(() => {
152
+ const error = new BASMValidation_js_1.BASMProtocolError('BASM_TIMEOUT', 'BASM request timed out');
153
+ controller.abort(error);
154
+ reject(error);
155
+ }, this.limits.timeoutMs);
156
+ });
157
+ try {
158
+ return await Promise.race([this.postResponse(path, body, controller.signal), deadline]);
159
+ }
160
+ finally {
161
+ clearTimeout(timer);
162
+ controller.abort();
163
+ }
164
+ }
165
+ async postResponse(path, body, signal) {
166
+ var _a, _b, _c, _d;
26
167
  const response = await this.fetchImpl(new URL(path, this.endpoint).toString(), {
27
168
  method: 'POST',
28
169
  headers: {
@@ -30,13 +171,38 @@ class BASMRemote {
30
171
  'Content-Type': 'application/json',
31
172
  'x-bsv-topic': this.topic
32
173
  },
33
- body: JSON.stringify(body)
174
+ body: JSON.stringify(body),
175
+ signal
34
176
  });
35
- const text = await response.text();
177
+ if (signal.aborted) {
178
+ void ((_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => { }));
179
+ signal.throwIfAborted();
180
+ }
181
+ const contentType = (_c = (_b = response.headers) === null || _b === void 0 ? void 0 : _b.get) === null || _c === void 0 ? void 0 : _c.call(_b, 'content-type');
182
+ if (contentType !== null &&
183
+ contentType !== undefined &&
184
+ contentType.includes('/') &&
185
+ !/^application\/(?:[\w.-]+\+)?json(?:\s*;|$)/i.test(contentType)) {
186
+ void ((_d = response.body) === null || _d === void 0 ? void 0 : _d.cancel().catch(() => { }));
187
+ throw new TypeError('Overlay peer response is not JSON');
188
+ }
189
+ const text = await this.readResponse(response, signal);
190
+ let value;
191
+ try {
192
+ value = JSON.parse(text);
193
+ }
194
+ catch {
195
+ if (response.ok)
196
+ throw new BASMValidation_js_1.BASMProtocolError('BASM_INVALID_RESPONSE', 'Invalid BASM JSON response');
197
+ }
36
198
  if (!response.ok) {
37
- throw new Error(`BASM peer ${this.endpoint} returned ${response.status}: ${text}`);
199
+ const code = typeof value === 'object' && value !== null && 'code' in value ? value.code : undefined;
200
+ if (response.status === 501 || code === 'BASM_UNSUPPORTED') {
201
+ throw new BASMValidation_js_1.BASMProtocolError('BASM_UNSUPPORTED', `BASM peer returned HTTP ${response.status}: unsupported BASM capability`);
202
+ }
203
+ throw new BASMValidation_js_1.BASMProtocolError('BASM_HTTP_ERROR', `BASM peer returned HTTP ${response.status}`);
38
204
  }
39
- return (text.length === 0 ? {} : JSON.parse(text));
205
+ return value;
40
206
  }
41
207
  }
42
208
  exports.BASMRemote = BASMRemote;