@bsv/overlay 2.2.1 → 2.2.7

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 (151) hide show
  1. package/LICENSE.txt +57 -27
  2. package/README.md +102 -269
  3. package/dist/cjs/mod.d.ts +15 -0
  4. package/dist/cjs/mod.d.ts.map +1 -0
  5. package/dist/cjs/mod.js +4 -1
  6. package/dist/cjs/mod.js.map +1 -1
  7. package/dist/cjs/package.json +41 -13
  8. package/dist/cjs/src/Advertisement.d.ts +9 -0
  9. package/dist/cjs/src/Advertisement.d.ts.map +1 -0
  10. package/dist/cjs/src/Advertiser.d.ts +36 -0
  11. package/dist/cjs/src/Advertiser.d.ts.map +1 -0
  12. package/dist/cjs/src/BASM.d.ts +110 -0
  13. package/dist/cjs/src/BASM.d.ts.map +1 -0
  14. package/dist/cjs/src/BASM.js.map +1 -1
  15. package/dist/cjs/src/BASMRemote.d.ts +14 -0
  16. package/dist/cjs/src/BASMRemote.d.ts.map +1 -0
  17. package/dist/cjs/src/BASMRemote.js.map +1 -1
  18. package/dist/cjs/src/Engine.d.ts +377 -0
  19. package/dist/cjs/src/Engine.d.ts.map +1 -0
  20. package/dist/cjs/src/Engine.js +416 -417
  21. package/dist/cjs/src/Engine.js.map +1 -1
  22. package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts +24 -0
  23. package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts.map +1 -0
  24. package/dist/cjs/src/GASP/OverlayGASPRemote.js +7 -10
  25. package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
  26. package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts +103 -0
  27. package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts.map +1 -0
  28. package/dist/cjs/src/GASP/OverlayGASPStorage.js +32 -30
  29. package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
  30. package/dist/cjs/src/LookupFormula.d.ts +29 -0
  31. package/dist/cjs/src/LookupFormula.d.ts.map +1 -0
  32. package/dist/cjs/src/LookupService.d.ts +85 -0
  33. package/dist/cjs/src/LookupService.d.ts.map +1 -0
  34. package/dist/cjs/src/Output.d.ts +32 -0
  35. package/dist/cjs/src/Output.d.ts.map +1 -0
  36. package/dist/cjs/src/SafeLog.d.ts +14 -0
  37. package/dist/cjs/src/SafeLog.d.ts.map +1 -0
  38. package/dist/cjs/src/SafeLog.js +44 -0
  39. package/dist/cjs/src/SafeLog.js.map +1 -0
  40. package/{src/SyncConfiguration.ts → dist/cjs/src/SyncConfiguration.d.ts} +2 -1
  41. package/dist/cjs/src/SyncConfiguration.d.ts.map +1 -0
  42. package/dist/cjs/src/TopicManager.d.ts +35 -0
  43. package/dist/cjs/src/TopicManager.d.ts.map +1 -0
  44. package/dist/cjs/src/storage/Storage.d.ts +237 -0
  45. package/dist/cjs/src/storage/Storage.d.ts.map +1 -0
  46. package/dist/cjs/src/storage/knex/KnexStorage.d.ts +69 -0
  47. package/dist/cjs/src/storage/knex/KnexStorage.d.ts.map +1 -0
  48. package/dist/cjs/src/storage/knex/KnexStorage.js +7 -10
  49. package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
  50. package/dist/cjs/src/storage/knex/all-migrations.d.ts +11 -0
  51. package/dist/cjs/src/storage/knex/all-migrations.d.ts.map +1 -0
  52. package/dist/cjs/src/storage/knex/all-migrations.js.map +1 -1
  53. package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.d.ts +4 -0
  54. package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.d.ts.map +1 -0
  55. package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.js.map +1 -1
  56. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts +4 -0
  57. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts.map +1 -0
  58. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -1
  59. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts +4 -0
  60. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -0
  61. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
  62. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts +4 -0
  63. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts.map +1 -0
  64. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -1
  65. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.d.ts +4 -0
  66. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.d.ts.map +1 -0
  67. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.js.map +1 -1
  68. package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.d.ts +4 -0
  69. package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.d.ts.map +1 -0
  70. package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.js.map +1 -1
  71. package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.d.ts +4 -0
  72. package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.d.ts.map +1 -0
  73. package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.js.map +1 -1
  74. package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.d.ts +9 -0
  75. package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.d.ts.map +1 -0
  76. package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.js.map +1 -1
  77. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts +4 -0
  78. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts.map +1 -0
  79. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.js.map +1 -1
  80. package/dist/esm/mod.js +2 -1
  81. package/dist/esm/mod.js.map +1 -1
  82. package/dist/esm/src/BASMRemote.js.map +1 -1
  83. package/dist/esm/src/Engine.js +394 -395
  84. package/dist/esm/src/Engine.js.map +1 -1
  85. package/dist/esm/src/GASP/OverlayGASPRemote.js +2 -2
  86. package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
  87. package/dist/esm/src/GASP/OverlayGASPStorage.js +21 -18
  88. package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
  89. package/dist/esm/src/SafeLog.js +40 -0
  90. package/dist/esm/src/SafeLog.js.map +1 -0
  91. package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
  92. package/dist/types/mod.d.ts +5 -4
  93. package/dist/types/mod.d.ts.map +1 -1
  94. package/dist/types/src/BASMRemote.d.ts.map +1 -1
  95. package/dist/types/src/Engine.d.ts +15 -0
  96. package/dist/types/src/Engine.d.ts.map +1 -1
  97. package/dist/types/src/GASP/OverlayGASPRemote.d.ts +2 -2
  98. package/dist/types/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
  99. package/dist/types/src/GASP/OverlayGASPStorage.d.ts +3 -1
  100. package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
  101. package/dist/types/src/SafeLog.d.ts +14 -0
  102. package/dist/types/src/SafeLog.d.ts.map +1 -0
  103. package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
  104. package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
  105. package/package.json +85 -28
  106. package/dist/cjs/tsconfig.cjs.tsbuildinfo +0 -1
  107. package/dist/esm/tsconfig.esm.tsbuildinfo +0 -1
  108. package/dist/types/tsconfig.types.tsbuildinfo +0 -1
  109. package/docs/API.md +0 -944
  110. package/docs/BRC-136-BASM.md +0 -327
  111. package/docs/README.md +0 -16
  112. package/docs/Synchronization.md +0 -212
  113. package/docs/concepts/00-overview.md +0 -85
  114. package/docs/concepts/01-best-practices.md +0 -202
  115. package/docs/concepts/02-query-performance.md +0 -345
  116. package/docs/concepts/03-database-monitoring.md +0 -211
  117. package/docs/concepts/04-pagination-example.md +0 -186
  118. package/docs/concepts/05-recommendations-summary.md +0 -158
  119. package/docs/concepts/README.md +0 -20
  120. package/docs/examples/README.md +0 -15
  121. package/docs/examples/gs-wip.md +0 -111
  122. package/docs/internal/README.md +0 -11
  123. package/mod.ts +0 -40
  124. package/src/Advertisement.ts +0 -8
  125. package/src/Advertiser.ts +0 -41
  126. package/src/BASM.ts +0 -208
  127. package/src/BASMRemote.ts +0 -54
  128. package/src/Engine.ts +0 -2194
  129. package/src/GASP/OverlayGASPRemote.ts +0 -108
  130. package/src/GASP/OverlayGASPStorage.ts +0 -384
  131. package/src/LookupFormula.ts +0 -31
  132. package/src/LookupService.ts +0 -136
  133. package/src/Output.ts +0 -31
  134. package/src/TopicManager.ts +0 -40
  135. package/src/__tests/BASM.test.ts +0 -44
  136. package/src/__tests/BASMChain.test.ts +0 -295
  137. package/src/__tests/Engine.test.ts +0 -1381
  138. package/src/__tests/OverlayGASPRemote.test.ts +0 -135
  139. package/src/__tests/OverlayGASPStorage.test.ts +0 -205
  140. package/src/storage/Storage.ts +0 -260
  141. package/src/storage/knex/KnexStorage.ts +0 -651
  142. package/src/storage/knex/all-migrations.ts +0 -32
  143. package/src/storage/knex/migrations/2024-05-18-001-initial.ts +0 -30
  144. package/src/storage/knex/migrations/2024-07-10-001-block-height.ts +0 -15
  145. package/src/storage/knex/migrations/2024-07-17-001-transactions.ts +0 -64
  146. package/src/storage/knex/migrations/2024-07-18-001-indexes.ts +0 -30
  147. package/src/storage/knex/migrations/2025-05-28-001-enlarge.ts +0 -42
  148. package/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.ts +0 -46
  149. package/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.ts +0 -24
  150. package/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.ts +0 -18
  151. package/src/storage/knex/migrations/2026-05-29-001-brc136-basm.ts +0 -123
@@ -1,651 +0,0 @@
1
- import {
2
- Storage,
3
- type AppliedTransaction,
4
- type AppliedTransactionProofUpdate,
5
- type StoredTransactionRecord,
6
- type UnprovenAppliedTransactionCandidate
7
- } from '../Storage.js'
8
- import { Knex } from 'knex'
9
- import type { Output } from '../../Output.js'
10
- import { Transaction } from '@bsv/sdk'
11
- import {
12
- BASM_ZERO_HASH,
13
- type AdmittedTxRef,
14
- type RawTransactionRecord,
15
- type TopicAnchorTip,
16
- type TopicBlockAnchor,
17
- extractMerkleProofMetadata
18
- } from '../../BASM.js'
19
-
20
- const OUTPUT_SELECT_FIELDS = [
21
- 'outputs.txid',
22
- 'outputs.outputIndex',
23
- 'outputs.outputScript',
24
- 'outputs.topic',
25
- 'outputs.satoshis',
26
- 'outputs.outputsConsumed',
27
- 'outputs.spent',
28
- 'outputs.consumedBy',
29
- 'outputs.blockHeight',
30
- 'outputs.score'
31
- ] as const
32
-
33
- export class KnexStorage implements Storage {
34
- knex: Knex
35
-
36
- constructor (knex: Knex) {
37
- this.knex = knex
38
- }
39
-
40
- private parseOutputRelations(
41
- value: string | Array<{ txid: string, outputIndex: number }>
42
- ): Array<{ txid: string, outputIndex: number }> {
43
- if (Array.isArray(value)) {
44
- return value
45
- }
46
- return JSON.parse(value)
47
- }
48
-
49
- private parseOutputRecord(
50
- row: any,
51
- includeBEEF: boolean,
52
- beefOverride?: number[]
53
- ): Output {
54
- return {
55
- ...row,
56
- outputScript: Array.from(row.outputScript),
57
- beef: (() => {
58
- if (!includeBEEF) return undefined
59
- if (beefOverride != null) return beefOverride
60
- return row.beef != null ? Array.from(row.beef) : undefined
61
- })(),
62
- spent: Boolean(row.spent),
63
- outputsConsumed: this.parseOutputRelations(row.outputsConsumed),
64
- consumedBy: this.parseOutputRelations(row.consumedBy)
65
- }
66
- }
67
-
68
- private binaryToNumberArray(value: any): number[] | undefined {
69
- if (value === undefined || value === null) {
70
- return undefined
71
- }
72
- return Array.from(Buffer.from(value))
73
- }
74
-
75
- private binaryToHex(value: any): string | undefined {
76
- const array = this.binaryToNumberArray(value)
77
- if (array === undefined) {
78
- return undefined
79
- }
80
- return Buffer.from(array).toString('hex')
81
- }
82
-
83
- private transactionRecordFromBEEF(txid: string, beef: number[]): StoredTransactionRecord {
84
- const tx = Transaction.fromBEEF(beef)
85
- const metadata = extractMerkleProofMetadata(txid, tx.merklePath)
86
- return {
87
- txid,
88
- beef: tx.merklePath === undefined ? beef : tx.toAtomicBEEF(),
89
- rawTx: Array.from(tx.toBinary()),
90
- merklePath: tx.merklePath?.toBinary(),
91
- blockHeight: metadata?.blockHeight,
92
- blockIndex: metadata?.blockIndex,
93
- merkleRoot: metadata?.merkleRoot
94
- }
95
- }
96
-
97
- private async fetchTransactionBeefMap(txids: string[]): Promise<Map<string, number[]>> {
98
- if (txids.length === 0) {
99
- return new Map<string, number[]>()
100
- }
101
-
102
- const rows = await this.knex('transactions')
103
- .whereIn('txid', txids)
104
- .select(['txid', 'beef'])
105
-
106
- const beefByTxid = new Map<string, number[]>()
107
- for (const row of rows) {
108
- if (row.beef !== undefined && row.beef !== null) {
109
- beefByTxid.set(row.txid, Array.from(row.beef))
110
- }
111
- }
112
- return beefByTxid
113
- }
114
-
115
- async findOutput (txid: string, outputIndex: number, topic?: string, spent?: boolean, includeBEEF: boolean = false): Promise<Output | null> {
116
- const search: {
117
- 'outputs.txid': string
118
- 'outputs.outputIndex': number
119
- 'outputs.topic'?: string
120
- 'outputs.spent'?: boolean
121
- } = {
122
- 'outputs.txid': txid,
123
- 'outputs.outputIndex': outputIndex
124
- }
125
- if (topic !== undefined) search['outputs.topic'] = topic
126
- if (spent !== undefined) search['outputs.spent'] = spent
127
-
128
- const query = this.knex('outputs').where(search)
129
- const selectFields: string[] = [...OUTPUT_SELECT_FIELDS]
130
-
131
- if (includeBEEF) {
132
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
133
- query.leftJoin('transactions', 'outputs.txid', 'transactions.txid')
134
- selectFields.push('transactions.beef')
135
- }
136
-
137
- const output = await query.select(selectFields).first()
138
-
139
- if (output === undefined || output === null) {
140
- return null
141
- }
142
-
143
- return this.parseOutputRecord(output, includeBEEF)
144
- }
145
-
146
- async findOutputsByOutpoints (
147
- outpoints: Array<{ txid: string, outputIndex: number }>,
148
- includeBEEF: boolean = false
149
- ): Promise<Output[]> {
150
- if (outpoints.length === 0) {
151
- return []
152
- }
153
-
154
- const deduped = new Map<string, { txid: string, outputIndex: number }>()
155
- for (const outpoint of outpoints) {
156
- deduped.set(`${outpoint.txid}:${outpoint.outputIndex}`, outpoint)
157
- }
158
-
159
- const rows = await this.knex('outputs')
160
- .whereIn(
161
- ['outputs.txid', 'outputs.outputIndex'],
162
- Array.from(deduped.values()).map(outpoint => [outpoint.txid, outpoint.outputIndex])
163
- )
164
- .select([...OUTPUT_SELECT_FIELDS])
165
-
166
- if (rows === undefined || rows.length === 0) {
167
- return []
168
- }
169
-
170
- if (!includeBEEF) {
171
- return rows.map(row => this.parseOutputRecord(row, false))
172
- }
173
-
174
- const txids = Array.from(new Set(rows.map(row => row.txid)))
175
- const beefByTxid = await this.fetchTransactionBeefMap(txids)
176
- return rows.map(row => this.parseOutputRecord(row, true, beefByTxid.get(row.txid)))
177
- }
178
-
179
- async findOutputsForTransaction (txid: string, includeBEEF: boolean = false): Promise<Output[]> {
180
- const outputs = await this.knex('outputs')
181
- .where({ 'outputs.txid': txid })
182
- .select([...OUTPUT_SELECT_FIELDS])
183
-
184
- if (outputs === undefined || outputs.length === 0) {
185
- return []
186
- }
187
-
188
- if (!includeBEEF) {
189
- return outputs.map(output => this.parseOutputRecord(output, false))
190
- }
191
-
192
- const beefByTxid = await this.fetchTransactionBeefMap([txid])
193
- return outputs.map(output => this.parseOutputRecord(output, true, beefByTxid.get(output.txid)))
194
- }
195
-
196
- async findUTXOsForTopic (topic: string, since?: number, limit?: number, includeBEEF: boolean = false): Promise<Output[]> {
197
- // Base query to get outputs
198
- const query = this.knex('outputs').where({ 'outputs.topic': topic, 'outputs.spent': false })
199
-
200
- // If provided, additionally filters UTXOs by score
201
- if (since !== undefined && since > 0) {
202
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
203
- query.andWhere('outputs.score', '>=', since)
204
- }
205
-
206
- // Sort by score
207
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
208
- query.orderBy('outputs.score', 'asc')
209
-
210
- // Apply limit if specified
211
- if (limit !== undefined && limit > 0) {
212
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
213
- query.limit(limit)
214
- }
215
-
216
- const outputs = await query.select([...OUTPUT_SELECT_FIELDS])
217
-
218
- if (outputs === undefined || outputs.length === 0) {
219
- return []
220
- }
221
-
222
- if (!includeBEEF) {
223
- return outputs.map(output => this.parseOutputRecord(output, false))
224
- }
225
-
226
- const txids = Array.from(new Set(outputs.map(output => output.txid)))
227
- const beefByTxid = await this.fetchTransactionBeefMap(txids)
228
- return outputs.map(output => this.parseOutputRecord(output, true, beefByTxid.get(output.txid)))
229
- }
230
-
231
- async deleteOutput (txid: string, outputIndex: number, topic: string): Promise<void> {
232
- await this.knex.transaction(async trx => {
233
- // Delete the specific output
234
- await trx('outputs').where({ txid, outputIndex, topic }).del()
235
-
236
- // Check how many outputs reference the same transaction
237
- const remainingOutputs = await trx('outputs').where({ txid }).count('* as count').first()
238
-
239
- if (remainingOutputs !== undefined && Number(remainingOutputs.count) === 0) {
240
- // If no more outputs reference the transaction, delete the beef
241
- await trx('transactions').where({ txid }).del()
242
- }
243
- })
244
- }
245
-
246
- async insertOutput (output: Output): Promise<void> {
247
- await this.knex.transaction(async trx => {
248
- const existing = await trx('outputs').where({
249
- txid: output.txid,
250
- outputIndex: Number(output.outputIndex),
251
- topic: output.topic
252
- }).first()
253
-
254
- if (existing === undefined || existing === null) {
255
- await trx('outputs').insert({
256
- txid: output.txid,
257
- outputIndex: Number(output.outputIndex),
258
- outputScript: Buffer.from(output.outputScript),
259
- topic: output.topic,
260
- satoshis: Number(output.satoshis),
261
- outputsConsumed: JSON.stringify(output.outputsConsumed),
262
- consumedBy: JSON.stringify(output.consumedBy),
263
- spent: output.spent,
264
- score: output.score,
265
- blockHeight: output.blockHeight
266
- })
267
- }
268
-
269
- if (output.beef !== undefined) {
270
- const record = this.transactionRecordFromBEEF(output.txid, output.beef)
271
- const transactionRecord: Record<string, any> = {
272
- txid: output.txid,
273
- updatedAt: new Date()
274
- }
275
- if (record.beef !== undefined) transactionRecord.beef = Buffer.from(record.beef)
276
- if (record.rawTx !== undefined) transactionRecord.rawTx = Buffer.from(record.rawTx)
277
- if (record.merklePath !== undefined) transactionRecord.merklePath = Buffer.from(record.merklePath)
278
- if (record.blockHeight !== undefined) transactionRecord.blockHeight = record.blockHeight
279
- if (record.blockIndex !== undefined) transactionRecord.blockIndex = record.blockIndex
280
- if (record.merkleRoot !== undefined) transactionRecord.merkleRoot = record.merkleRoot
281
-
282
- const mergeRecord = { ...transactionRecord }
283
- delete mergeRecord.txid
284
- await trx('transactions').insert(transactionRecord).onConflict('txid').merge(mergeRecord)
285
- }
286
- })
287
- }
288
-
289
- async markUTXOAsSpent (txid: string, outputIndex: number, topic?: string): Promise<void> {
290
- await this.knex('outputs').where({
291
- txid,
292
- outputIndex,
293
- topic
294
- }).update('spent', true)
295
- }
296
-
297
- async updateConsumedBy (txid: string, outputIndex: number, topic: string, consumedBy: Array<{ txid: string, outputIndex: number }>): Promise<void> {
298
- await this.knex('outputs').where({
299
- txid,
300
- outputIndex,
301
- topic
302
- }).update('consumedBy', JSON.stringify(consumedBy))
303
- }
304
-
305
- async updateTransactionBEEF (txid: string, beef: number[]): Promise<void> {
306
- await this.upsertTransactionRecord(this.transactionRecordFromBEEF(txid, beef))
307
- }
308
-
309
- async updateOutputBlockHeight (txid: string, outputIndex: number, topic: string, blockHeight: number): Promise<void> {
310
- await this.knex('outputs').where({
311
- txid,
312
- outputIndex,
313
- topic
314
- }).update('blockHeight', blockHeight)
315
- }
316
-
317
- async upsertTransactionRecord(record: StoredTransactionRecord): Promise<void> {
318
- const insert: Record<string, any> = {
319
- txid: record.txid,
320
- updatedAt: new Date()
321
- }
322
- if (record.beef !== undefined) insert.beef = Buffer.from(record.beef)
323
- if (record.rawTx !== undefined) insert.rawTx = Buffer.from(record.rawTx)
324
- if (record.merklePath !== undefined) insert.merklePath = Buffer.from(record.merklePath)
325
- if (record.blockHeight !== undefined) insert.blockHeight = record.blockHeight
326
- if (record.blockHash !== undefined) insert.blockHash = record.blockHash
327
- if (record.blockIndex !== undefined) insert.blockIndex = record.blockIndex
328
- if (record.merkleRoot !== undefined) insert.merkleRoot = record.merkleRoot
329
-
330
- const merge = { ...insert }
331
- delete merge.txid
332
-
333
- await this.knex('transactions')
334
- .insert(insert)
335
- .onConflict('txid')
336
- .merge(merge)
337
- }
338
-
339
- async updateAppliedTransactionProof(record: AppliedTransactionProofUpdate): Promise<void> {
340
- await this.knex('applied_transactions')
341
- .where({ txid: record.txid, topic: record.topic })
342
- .update({
343
- blockHeight: record.blockHeight,
344
- blockHash: record.blockHash,
345
- blockIndex: record.blockIndex,
346
- merkleRoot: record.merkleRoot,
347
- proven: true
348
- })
349
-
350
- if (record.blockHeight !== undefined) {
351
- await this.knex('outputs')
352
- .where({ txid: record.txid, topic: record.topic })
353
- .update({ blockHeight: record.blockHeight })
354
- }
355
- }
356
-
357
- async insertAppliedTransaction (tx: AppliedTransaction): Promise<void> {
358
- await this.knex('applied_transactions').insert({
359
- txid: tx.txid,
360
- topic: tx.topic,
361
- blockHeight: tx.blockHeight,
362
- blockHash: tx.blockHash,
363
- blockIndex: tx.blockIndex,
364
- merkleRoot: tx.merkleRoot,
365
- firstSeenHeight: tx.firstSeenHeight,
366
- proven: tx.proven ?? false
367
- })
368
- }
369
-
370
- async doesAppliedTransactionExist (tx: { txid: string, topic: string }): Promise<boolean> {
371
- const result = await this.knex('applied_transactions')
372
- .where({ txid: tx.txid, topic: tx.topic })
373
- .select(this.knex.raw('1'))
374
- .first()
375
-
376
- return !!result
377
- }
378
-
379
- async updateLastInteraction (host: string, topic: string, since: number): Promise<void> {
380
- await this.knex('host_sync_state')
381
- .insert({ host, topic, since })
382
- .onConflict(['host', 'topic'])
383
- .merge({ since })
384
- }
385
-
386
- async getLastInteraction (host: string, topic: string): Promise<number> {
387
- const result = await this.knex('host_sync_state')
388
- .where({ host, topic })
389
- .select('since')
390
- .first()
391
-
392
- return result ? result.since : 0
393
- }
394
-
395
- async findAdmittedTransactionsForBlock(topic: string, blockHeight: number, blockHash?: string): Promise<AdmittedTxRef[]> {
396
- let query = this.knex('applied_transactions')
397
- .where({ topic, blockHeight, proven: true })
398
- .whereNotNull('blockIndex')
399
-
400
- if (blockHash !== undefined) {
401
- query = query.andWhere({ blockHash })
402
- }
403
-
404
- const rows = await query
405
- .select(['txid', 'blockIndex'])
406
- .orderBy('blockIndex', 'asc')
407
-
408
- return rows.map(row => ({
409
- txid: row.txid,
410
- blockIndex: Number(row.blockIndex)
411
- }))
412
- }
413
-
414
- async upsertTopicBlockAnchor(anchor: TopicBlockAnchor): Promise<void> {
415
- await this.knex('topic_block_anchors')
416
- .insert({
417
- topic: anchor.topic,
418
- blockHeight: anchor.blockHeight,
419
- blockHash: anchor.blockHash,
420
- basmRoot: anchor.basmRoot,
421
- admittedCount: anchor.admittedCount,
422
- tac: anchor.tac,
423
- updatedAt: new Date()
424
- })
425
- .onConflict(['topic', 'blockHeight'])
426
- .merge({
427
- blockHash: anchor.blockHash,
428
- basmRoot: anchor.basmRoot,
429
- admittedCount: anchor.admittedCount,
430
- tac: anchor.tac,
431
- updatedAt: new Date()
432
- })
433
- }
434
-
435
- async findTopicBlockAnchor(topic: string, blockHeight: number, blockHash?: string): Promise<TopicBlockAnchor | undefined> {
436
- let query = this.knex('topic_block_anchors').where({ topic, blockHeight })
437
- if (blockHash !== undefined) {
438
- query = query.andWhere({ blockHash })
439
- }
440
- const row = await query.first()
441
- if (row === undefined) {
442
- return undefined
443
- }
444
- return {
445
- topic: row.topic,
446
- blockHeight: Number(row.blockHeight),
447
- blockHash: row.blockHash,
448
- basmRoot: row.basmRoot,
449
- admittedCount: Number(row.admittedCount),
450
- tac: row.tac
451
- }
452
- }
453
-
454
- async findTopicBlockAnchors(topic: string, fromHeight: number, toHeight: number): Promise<TopicBlockAnchor[]> {
455
- const rows = await this.knex('topic_block_anchors')
456
- .where({ topic })
457
- .andWhere('blockHeight', '>=', fromHeight)
458
- .andWhere('blockHeight', '<=', toHeight)
459
- .select(['topic', 'blockHeight', 'blockHash', 'basmRoot', 'admittedCount', 'tac'])
460
- .orderBy('blockHeight', 'asc')
461
-
462
- return rows.map(row => ({
463
- topic: row.topic,
464
- blockHeight: Number(row.blockHeight),
465
- blockHash: row.blockHash,
466
- basmRoot: row.basmRoot,
467
- admittedCount: Number(row.admittedCount),
468
- tac: row.tac
469
- }))
470
- }
471
-
472
- async findTopicAnchorTip(topic: string): Promise<TopicAnchorTip | undefined> {
473
- const row = await this.knex('topic_block_anchors')
474
- .where({ topic })
475
- .orderBy('blockHeight', 'desc')
476
- .first()
477
-
478
- if (row === undefined) {
479
- return {
480
- topic,
481
- blockHeight: -1,
482
- tac: BASM_ZERO_HASH
483
- }
484
- }
485
-
486
- return {
487
- topic: row.topic,
488
- blockHeight: Number(row.blockHeight),
489
- blockHash: row.blockHash,
490
- basmRoot: row.basmRoot,
491
- admittedCount: Number(row.admittedCount),
492
- tac: row.tac
493
- }
494
- }
495
-
496
- async findRawTransactions(txids: string[]): Promise<RawTransactionRecord[]> {
497
- if (txids.length === 0) return []
498
- const rows = await this.knex('transactions')
499
- .whereIn('txid', txids)
500
- .select(['txid', 'rawTx', 'beef'])
501
-
502
- const records: RawTransactionRecord[] = []
503
- for (const row of rows) {
504
- let rawTx = this.binaryToHex(row.rawTx)
505
- if (rawTx === undefined) {
506
- const beef = this.binaryToNumberArray(row.beef)
507
- if (beef !== undefined) {
508
- rawTx = Transaction.fromBEEF(beef, row.txid).toHex()
509
- }
510
- }
511
- if (rawTx !== undefined) {
512
- records.push({ txid: row.txid, rawTx })
513
- }
514
- }
515
- return records
516
- }
517
-
518
- async findTransactionMerklePaths(txids: string[]): Promise<Array<{
519
- txid: string
520
- merklePath: string
521
- blockHeight?: number
522
- blockHash?: string
523
- blockIndex?: number
524
- merkleRoot?: string
525
- }>> {
526
- if (txids.length === 0) return []
527
- const rows = await this.knex('transactions')
528
- .whereIn('txid', txids)
529
- .select(['txid', 'merklePath', 'blockHeight', 'blockHash', 'blockIndex', 'merkleRoot', 'beef'])
530
-
531
- const records: Array<{
532
- txid: string
533
- merklePath: string
534
- blockHeight?: number
535
- blockHash?: string
536
- blockIndex?: number
537
- merkleRoot?: string
538
- }> = []
539
-
540
- for (const row of rows) {
541
- let merklePath = this.binaryToHex(row.merklePath)
542
- if (merklePath === undefined) {
543
- const beef = this.binaryToNumberArray(row.beef)
544
- if (beef !== undefined) {
545
- merklePath = Transaction.fromBEEF(beef, row.txid).merklePath?.toHex()
546
- }
547
- }
548
- if (merklePath !== undefined) {
549
- records.push({
550
- txid: row.txid,
551
- merklePath,
552
- blockHeight: row.blockHeight === undefined || row.blockHeight === null ? undefined : Number(row.blockHeight),
553
- blockHash: row.blockHash,
554
- blockIndex: row.blockIndex === undefined || row.blockIndex === null ? undefined : Number(row.blockIndex),
555
- merkleRoot: row.merkleRoot
556
- })
557
- }
558
- }
559
-
560
- return records
561
- }
562
-
563
- async findUnprovenAppliedTransactions(cutoffHeight: number, topic?: string): Promise<UnprovenAppliedTransactionCandidate[]> {
564
- let query = this.knex('applied_transactions')
565
- .where(builder => {
566
- builder.where({ proven: false }).orWhereNull('proven')
567
- })
568
- .whereNotNull('firstSeenHeight')
569
- .andWhere('firstSeenHeight', '<=', cutoffHeight)
570
- .select(['txid', 'topic', 'firstSeenHeight'])
571
-
572
- if (topic !== undefined) {
573
- query = query.andWhere({ topic })
574
- }
575
-
576
- const rows = await query
577
- const candidates: UnprovenAppliedTransactionCandidate[] = []
578
-
579
- for (const row of rows) {
580
- const outputs = await this.knex('outputs')
581
- .where({ txid: row.txid, topic: row.topic })
582
- .select(['txid', 'outputIndex'])
583
-
584
- candidates.push({
585
- txid: row.txid,
586
- topic: row.topic,
587
- firstSeenHeight: row.firstSeenHeight === undefined || row.firstSeenHeight === null ? undefined : Number(row.firstSeenHeight),
588
- outputs: outputs.map(output => ({
589
- txid: output.txid,
590
- outputIndex: Number(output.outputIndex)
591
- }))
592
- })
593
- }
594
-
595
- return candidates
596
- }
597
-
598
- async deleteAppliedTransaction(txid: string, topic: string): Promise<void> {
599
- await this.knex('applied_transactions').where({ txid, topic }).del()
600
- }
601
-
602
- async findProvenAppliedTransactionsByBlockHash(blockHash: string): Promise<Array<{ txid: string, topic: string, blockHeight: number }>> {
603
- const rows = await this.knex('applied_transactions')
604
- .where({ blockHash, proven: true })
605
- .whereNotNull('blockHeight')
606
- .select(['txid', 'topic', 'blockHeight'])
607
-
608
- return rows.map(row => ({
609
- txid: row.txid,
610
- topic: row.topic,
611
- blockHeight: Number(row.blockHeight)
612
- }))
613
- }
614
-
615
- async findProvenAppliedTransactionsInRange(fromHeight: number, toHeight: number, topic?: string): Promise<Array<{ txid: string, topic: string, blockHeight: number, blockHash?: string, merkleRoot?: string }>> {
616
- let query = this.knex('applied_transactions')
617
- .where({ proven: true })
618
- .andWhere('blockHeight', '>=', fromHeight)
619
- .andWhere('blockHeight', '<=', toHeight)
620
-
621
- if (topic !== undefined) {
622
- query = query.andWhere({ topic })
623
- }
624
-
625
- const rows = await query.select(['txid', 'topic', 'blockHeight', 'blockHash', 'merkleRoot'])
626
-
627
- return rows.map(row => ({
628
- txid: row.txid,
629
- topic: row.topic,
630
- blockHeight: Number(row.blockHeight),
631
- blockHash: row.blockHash ?? undefined,
632
- merkleRoot: row.merkleRoot ?? undefined
633
- }))
634
- }
635
-
636
- async demoteAppliedTransactionToUnproven(txid: string, topic: string): Promise<void> {
637
- await this.knex('applied_transactions')
638
- .where({ txid, topic })
639
- .update({
640
- blockHeight: null,
641
- blockHash: null,
642
- blockIndex: null,
643
- merkleRoot: null,
644
- proven: false
645
- })
646
-
647
- await this.knex('outputs')
648
- .where({ txid, topic })
649
- .update({ blockHeight: null })
650
- }
651
- }
@@ -1,32 +0,0 @@
1
- import { Knex } from 'knex'
2
- import { up as initialSchemaUp, down as initialSchemaDown } from './migrations/2024-05-18-001-initial.js'
3
- import { up as addBlockHeightColumnUp, down as addBlockHeightColumnDown } from './migrations/2024-07-10-001-block-height.js'
4
- import { up as addTransactionsTableUp, down as addTransactionsTableDown } from './migrations/2024-07-17-001-transactions.js'
5
- import { up as addedIndexesUp, down as addedIndexesDown } from './migrations/2024-07-18-001-indexes.js'
6
- import { up as enlargeUp, down as enlargeDown } from './migrations/2025-05-28-001-enlarge.js'
7
- import { up as gaspPaginationSupportUp, down as gaspPaginationSupportDown } from './migrations/2025-06-25-001-gasp-pagination-support.js'
8
- import { up as fixScoreColumnTypeUp, down as fixScoreColumnTypeDown } from './migrations/2025-07-22-001-fix-score-column-type.js'
9
- import { up as utxoLookupIndexUp, down as utxoLookupIndexDown } from './migrations/2025-11-11-001-utxo-lookup-index.js'
10
- import { up as brc136BasmUp, down as brc136BasmDown } from './migrations/2026-05-29-001-brc136-basm.js'
11
-
12
- /**
13
- * An array of all migrations, in order.
14
- */
15
- interface Migration {
16
- up: (knex: Knex) => Promise<void>
17
- down: (knex: Knex) => Promise<void>
18
- }
19
-
20
- const allMigrations: Migration[] = [
21
- { up: initialSchemaUp, down: initialSchemaDown },
22
- { up: addBlockHeightColumnUp, down: addBlockHeightColumnDown },
23
- { up: addTransactionsTableUp, down: addTransactionsTableDown },
24
- { up: addedIndexesUp, down: addedIndexesDown },
25
- { up: enlargeUp, down: enlargeDown },
26
- { up: gaspPaginationSupportUp, down: gaspPaginationSupportDown },
27
- { up: fixScoreColumnTypeUp, down: fixScoreColumnTypeDown },
28
- { up: utxoLookupIndexUp, down: utxoLookupIndexDown },
29
- { up: brc136BasmUp, down: brc136BasmDown }
30
- ]
31
-
32
- export default allMigrations