@bsv/overlay 0.1.27 → 0.2.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.
- package/dist/cjs/package.json +3 -3
- package/dist/cjs/src/Engine.js +95 -13
- package/dist/cjs/src/Engine.js.map +1 -1
- package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
- package/dist/cjs/src/GASP/OverlayGASPStorage.js +1 -1
- package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
- package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/Engine.js +93 -12
- package/dist/esm/src/Engine.js.map +1 -1
- package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
- package/dist/esm/src/GASP/OverlayGASPStorage.js +1 -1
- package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
- package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
- package/dist/esm/src/storage/knex/migrations/2024-05-18-001-initial.js.map +1 -1
- package/dist/esm/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -1
- package/dist/esm/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
- package/dist/esm/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/Engine.d.ts.map +1 -1
- package/dist/types/src/GASP/OverlayGASPRemote.d.ts +1 -1
- package/dist/types/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
- package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
- package/dist/types/src/LookupService.d.ts +67 -48
- package/dist/types/src/LookupService.d.ts.map +1 -1
- package/dist/types/src/Output.d.ts +2 -2
- package/dist/types/src/Output.d.ts.map +1 -1
- package/dist/types/src/storage/Storage.d.ts.map +1 -1
- package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
- package/dist/types/src/storage/knex/all-migrations.d.ts +2 -2
- package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
- package/dist/types/src/storage/knex/migrations/2024-05-18-001-initial.d.ts.map +1 -1
- package/dist/types/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts.map +1 -1
- package/dist/types/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -1
- package/dist/types/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/Engine.ts +139 -61
- package/src/GASP/OverlayGASPRemote.ts +6 -6
- package/src/GASP/OverlayGASPStorage.ts +17 -17
- package/src/LookupService.ts +112 -49
- package/src/Output.ts +1 -1
- package/src/__tests/Engine.test.ts +38 -29
- package/src/storage/Storage.ts +0 -1
- package/src/storage/knex/KnexStorage.ts +12 -12
- package/src/storage/knex/all-migrations.ts +1 -1
- package/src/storage/knex/migrations/2024-05-18-001-initial.ts +2 -2
- package/src/storage/knex/migrations/2024-07-10-001-block-height.ts +2 -2
- package/src/storage/knex/migrations/2024-07-17-001-transactions.ts +2 -2
- package/src/storage/knex/migrations/2024-07-18-001-indexes.ts +2 -2
package/src/Engine.ts
CHANGED
|
@@ -47,7 +47,7 @@ export class Engine {
|
|
|
47
47
|
* @param {OverlayBroadcastFacilitator} overlayBroadcastFacilitator - Facilitator for propagation to other Overlay Services.
|
|
48
48
|
* @param {typeof console} logger - The place where log entries are written.
|
|
49
49
|
*/
|
|
50
|
-
constructor(
|
|
50
|
+
constructor (
|
|
51
51
|
public managers: { [key: string]: TopicManager },
|
|
52
52
|
public lookupServices: { [key: string]: LookupService },
|
|
53
53
|
public storage: Storage,
|
|
@@ -76,14 +76,14 @@ export class Engine {
|
|
|
76
76
|
if (managerName === 'tm_ship' && this.shipTrackers !== undefined && this.syncConfiguration[managerName] !== false) {
|
|
77
77
|
// Combine tm_ship trackers with preexisting entries if any
|
|
78
78
|
const combinedSet = new Set([
|
|
79
|
-
...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName]
|
|
79
|
+
...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName] : []),
|
|
80
80
|
...this.shipTrackers
|
|
81
81
|
])
|
|
82
82
|
this.syncConfiguration[managerName] = Array.from(combinedSet)
|
|
83
83
|
} else if (managerName === 'tm_slap' && this.slapTrackers !== undefined && this.syncConfiguration[managerName] !== false) {
|
|
84
84
|
// Combine tm_slap trackers with preexisting entries if any
|
|
85
85
|
const combinedSet = new Set([
|
|
86
|
-
...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName]
|
|
86
|
+
...(Array.isArray(this.syncConfiguration[managerName]) ? this.syncConfiguration[managerName] : []),
|
|
87
87
|
...this.slapTrackers
|
|
88
88
|
])
|
|
89
89
|
this.syncConfiguration[managerName] = Array.from(combinedSet)
|
|
@@ -97,13 +97,13 @@ export class Engine {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// Helper functions for logging timings
|
|
100
|
-
private startTime(label: string): void {
|
|
100
|
+
private startTime (label: string): void {
|
|
101
101
|
if (this.logTime) {
|
|
102
102
|
this.logger.time(`${this.logPrefix} ${label}`)
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
private endTime(label: string): void {
|
|
106
|
+
private endTime (label: string): void {
|
|
107
107
|
if (this.logTime) {
|
|
108
108
|
this.logger.timeEnd(`${this.logPrefix} ${label}`)
|
|
109
109
|
}
|
|
@@ -114,12 +114,12 @@ export class Engine {
|
|
|
114
114
|
* @param {TaggedBEEF} taggedBEEF - The transaction to process
|
|
115
115
|
* @param {function(STEAK): void} [onSTEAKReady] - Optional callback function invoked when the STEAK is ready.
|
|
116
116
|
* @param {string} mode — Indicates the submission behavior, whether historical or current. Historical transactions are not broadcast or propagated.
|
|
117
|
-
*
|
|
117
|
+
*
|
|
118
118
|
* The optional callback function should be used to get STEAK when ready, and avoid waiting for broadcast and transaction propagation to complete.
|
|
119
|
-
*
|
|
119
|
+
*
|
|
120
120
|
* @returns {Promise<STEAK>} The submitted transaction execution acknowledgement
|
|
121
121
|
*/
|
|
122
|
-
async submit(taggedBEEF: TaggedBEEF, onSteakReady?: (steak: STEAK) => void, mode: 'historical-tx' | 'current-tx' = 'current-tx'): Promise<STEAK> {
|
|
122
|
+
async submit (taggedBEEF: TaggedBEEF, onSteakReady?: (steak: STEAK) => void, mode: 'historical-tx' | 'current-tx' = 'current-tx'): Promise<STEAK> {
|
|
123
123
|
for (const t of taggedBEEF.topics) {
|
|
124
124
|
if (this.managers[t] === undefined || this.managers[t] === null) {
|
|
125
125
|
throw new Error(`This server does not support this topic: ${t}`)
|
|
@@ -181,7 +181,53 @@ export class Engine {
|
|
|
181
181
|
await this.storage.markUTXOAsSpent(output.txid, output.outputIndex, topic)
|
|
182
182
|
await Promise.all(Object.values(this.lookupServices).map(async l => {
|
|
183
183
|
try {
|
|
184
|
-
|
|
184
|
+
if (typeof l.outputSpent === 'function') {
|
|
185
|
+
if (l.spendNotificationMode === 'txid') {
|
|
186
|
+
await l.outputSpent({
|
|
187
|
+
mode: 'txid',
|
|
188
|
+
spendingTxid: txid,
|
|
189
|
+
txid: output.txid,
|
|
190
|
+
outputIndex: output.outputIndex,
|
|
191
|
+
topic
|
|
192
|
+
})
|
|
193
|
+
} else if (l.spendNotificationMode === 'script') {
|
|
194
|
+
const inputIndex = tx.inputs.findIndex(i => {
|
|
195
|
+
let realSource = i.sourceTXID
|
|
196
|
+
if (!realSource) {
|
|
197
|
+
realSource = i.sourceTransaction?.id('hex')
|
|
198
|
+
}
|
|
199
|
+
return realSource === output.txid && i.sourceOutputIndex === output.outputIndex
|
|
200
|
+
})
|
|
201
|
+
if (!inputIndex) {
|
|
202
|
+
throw new Error('Could not find input index')
|
|
203
|
+
}
|
|
204
|
+
await l.outputSpent({
|
|
205
|
+
mode: 'script',
|
|
206
|
+
spendingTxid: txid,
|
|
207
|
+
inputIndex,
|
|
208
|
+
sequenceNumber: tx.inputs[inputIndex].sequence ?? 0xffffffff,
|
|
209
|
+
unlockingScript: tx.inputs[inputIndex].unlockingScript!,
|
|
210
|
+
txid: output.txid,
|
|
211
|
+
outputIndex: output.outputIndex,
|
|
212
|
+
topic
|
|
213
|
+
})
|
|
214
|
+
} else if (l.spendNotificationMode === 'whole-tx') {
|
|
215
|
+
await l.outputSpent({
|
|
216
|
+
mode: 'whole-tx',
|
|
217
|
+
spendingAtomicBEEF: tx.toAtomicBEEF(),
|
|
218
|
+
txid: output.txid,
|
|
219
|
+
outputIndex: output.outputIndex,
|
|
220
|
+
topic
|
|
221
|
+
})
|
|
222
|
+
} else { // none
|
|
223
|
+
await l.outputSpent({
|
|
224
|
+
mode: 'none',
|
|
225
|
+
txid: output.txid,
|
|
226
|
+
outputIndex: output.outputIndex,
|
|
227
|
+
topic
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
}
|
|
185
231
|
} catch (error) {
|
|
186
232
|
this.logger.error('Error in lookup service for outputSpent:', error)
|
|
187
233
|
}
|
|
@@ -244,7 +290,7 @@ export class Engine {
|
|
|
244
290
|
|
|
245
291
|
const outputsToMarkStale: Array<{
|
|
246
292
|
txid: string
|
|
247
|
-
previousOutputIndex: number
|
|
293
|
+
previousOutputIndex: number
|
|
248
294
|
inputIndex: number
|
|
249
295
|
}> = []
|
|
250
296
|
|
|
@@ -262,12 +308,13 @@ export class Engine {
|
|
|
262
308
|
|
|
263
309
|
// For each of the previous UTXOs for this topic, if the UTXO was not included in the list of UTXOs identified for retention, then it will be marked as stale.
|
|
264
310
|
for (const inputIndex of previousCoins) {
|
|
265
|
-
const previousTXID = tx.inputs[inputIndex].sourceTXID
|
|
311
|
+
const previousTXID = tx.inputs[inputIndex].sourceTXID ?? tx.inputs[inputIndex].sourceTransaction?.id('hex')
|
|
312
|
+
if (typeof previousTXID !== 'string') continue
|
|
266
313
|
const previousOutputIndex = tx.inputs[inputIndex].sourceOutputIndex
|
|
267
314
|
if (!admissibleOutputs.coinsToRetain.includes(inputIndex)) {
|
|
268
315
|
outputsToMarkStale.push({
|
|
269
316
|
txid: previousTXID,
|
|
270
|
-
previousOutputIndex
|
|
317
|
+
previousOutputIndex,
|
|
271
318
|
inputIndex
|
|
272
319
|
})
|
|
273
320
|
} else {
|
|
@@ -294,12 +341,13 @@ export class Engine {
|
|
|
294
341
|
// Handle admittance and notification of incoming UTXOs
|
|
295
342
|
const newUTXOs: Array<{ txid: string, outputIndex: number }> = []
|
|
296
343
|
await Promise.all(outputsToAdmit.map(async outputIndex => {
|
|
344
|
+
if (typeof tx.outputs[outputIndex].satoshis !== 'number') return
|
|
297
345
|
this.startTime(`insertNewOutput_${txid.substring(0, 10)}`)
|
|
298
346
|
await this.storage.insertOutput({
|
|
299
347
|
txid,
|
|
300
348
|
outputIndex,
|
|
301
349
|
outputScript: tx.outputs[outputIndex].lockingScript.toBinary(),
|
|
302
|
-
satoshis: tx.outputs[outputIndex].satoshis
|
|
350
|
+
satoshis: tx.outputs[outputIndex].satoshis,
|
|
303
351
|
topic,
|
|
304
352
|
spent: false,
|
|
305
353
|
beef: taggedBEEF.beef,
|
|
@@ -310,7 +358,31 @@ export class Engine {
|
|
|
310
358
|
newUTXOs.push({ txid, outputIndex })
|
|
311
359
|
|
|
312
360
|
this.startTime(`notifyLookupService${txid.substring(0, 10)}`)
|
|
313
|
-
await Promise.all(Object.values(this.lookupServices).map(async l =>
|
|
361
|
+
await Promise.all(Object.values(this.lookupServices).map(async l => {
|
|
362
|
+
if (l.admissionMode === 'locking-script') {
|
|
363
|
+
if (
|
|
364
|
+
typeof tx.outputs[outputIndex].lockingScript !== 'object' ||
|
|
365
|
+
typeof tx.outputs[outputIndex].satoshis !== 'number'
|
|
366
|
+
) {
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
await l.outputAdmittedByTopic({
|
|
370
|
+
mode: 'locking-script',
|
|
371
|
+
txid,
|
|
372
|
+
outputIndex,
|
|
373
|
+
lockingScript: tx.outputs[outputIndex].lockingScript,
|
|
374
|
+
satoshis: tx.outputs[outputIndex].satoshis,
|
|
375
|
+
topic
|
|
376
|
+
})
|
|
377
|
+
} else {
|
|
378
|
+
await l.outputAdmittedByTopic({
|
|
379
|
+
mode: 'whole-tx',
|
|
380
|
+
atomicBEEF: tx.toAtomicBEEF(),
|
|
381
|
+
outputIndex,
|
|
382
|
+
topic
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
}))
|
|
314
386
|
this.endTime(`notifyLookupService${txid.substring(0, 10)}`)
|
|
315
387
|
}))
|
|
316
388
|
|
|
@@ -376,7 +448,7 @@ export class Engine {
|
|
|
376
448
|
* @param LookupQuestion — The question to ask the Overlay Services Engine
|
|
377
449
|
* @returns The answer to the question
|
|
378
450
|
*/
|
|
379
|
-
async lookup(lookupQuestion: LookupQuestion): Promise<LookupAnswer> {
|
|
451
|
+
async lookup (lookupQuestion: LookupQuestion): Promise<LookupAnswer> {
|
|
380
452
|
// Validate a lookup service for the provider is found
|
|
381
453
|
const lookupService = this.lookupServices[lookupQuestion.service]
|
|
382
454
|
if (lookupService === undefined || lookupService === null) throw new Error(`Lookup service not found for provider: ${lookupQuestion.service} `)
|
|
@@ -417,7 +489,7 @@ export class Engine {
|
|
|
417
489
|
}
|
|
418
490
|
|
|
419
491
|
/**
|
|
420
|
-
* Ensures alignment between the current SHIP/SLAP advertisements and the
|
|
492
|
+
* Ensures alignment between the current SHIP/SLAP advertisements and the
|
|
421
493
|
* configured Topic Managers and Lookup Services in the engine.
|
|
422
494
|
*
|
|
423
495
|
* This method performs the following actions:
|
|
@@ -435,8 +507,13 @@ export class Engine {
|
|
|
435
507
|
* @throws Will throw an error if there are issues during the advertisement synchronization process.
|
|
436
508
|
* @returns {Promise<void>} A promise that resolves when the synchronization process is complete.
|
|
437
509
|
*/
|
|
438
|
-
async syncAdvertisements(): Promise<void> {
|
|
439
|
-
if (
|
|
510
|
+
async syncAdvertisements (): Promise<void> {
|
|
511
|
+
if (
|
|
512
|
+
this.advertiser === undefined ||
|
|
513
|
+
typeof this.hostingURL !== 'string' ||
|
|
514
|
+
this.hostingURL.length < 1 ||
|
|
515
|
+
!this.isValidUrl(this.hostingURL)
|
|
516
|
+
) {
|
|
440
517
|
return
|
|
441
518
|
}
|
|
442
519
|
const advertiser = this.advertiser
|
|
@@ -496,7 +573,7 @@ export class Engine {
|
|
|
496
573
|
*
|
|
497
574
|
* @throws Error if the overlay service engine is not configured for topical synchronization.
|
|
498
575
|
*/
|
|
499
|
-
async startGASPSync(): Promise<void> {
|
|
576
|
+
async startGASPSync (): Promise<void> {
|
|
500
577
|
if (this.syncConfiguration === undefined) {
|
|
501
578
|
throw new Error('Overlay Service Engine not configured for topical synchronization!')
|
|
502
579
|
}
|
|
@@ -590,7 +667,7 @@ export class Engine {
|
|
|
590
667
|
* @param topic - The topic for which UTXOs are being requested.
|
|
591
668
|
* @returns A promise that resolves to a GASPInitialResponse containing the list of UTXOs and the provided min block height.
|
|
592
669
|
*/
|
|
593
|
-
async provideForeignSyncResponse(initialRequest: GASPInitialRequest, topic: string): Promise<GASPInitialResponse> {
|
|
670
|
+
async provideForeignSyncResponse (initialRequest: GASPInitialRequest, topic: string): Promise<GASPInitialResponse> {
|
|
594
671
|
const UTXOs = await this.storage.findUTXOsForTopic(topic, initialRequest.since)
|
|
595
672
|
|
|
596
673
|
return {
|
|
@@ -613,7 +690,7 @@ export class Engine {
|
|
|
613
690
|
* @returns A promise that resolves to a GASPNode containing the raw transaction and other optional data.
|
|
614
691
|
* @throws An error if no output is found for the given transaction ID and output index.
|
|
615
692
|
*/
|
|
616
|
-
async provideForeignGASPNode(graphID: string, txid: string, outputIndex: number): Promise<GASPNode> {
|
|
693
|
+
async provideForeignGASPNode (graphID: string, txid: string, outputIndex: number): Promise<GASPNode> {
|
|
617
694
|
const hydrator = async (output: Output | null): Promise<GASPNode> => {
|
|
618
695
|
if (output?.beef === undefined) {
|
|
619
696
|
throw new Error('No matching output found!')
|
|
@@ -691,7 +768,7 @@ export class Engine {
|
|
|
691
768
|
*
|
|
692
769
|
* @returns {Promise<Output | undefined>} - A promise that resolves to the output history if found, or undefined if not.
|
|
693
770
|
*/
|
|
694
|
-
async getUTXOHistory(
|
|
771
|
+
async getUTXOHistory (
|
|
695
772
|
output: Output,
|
|
696
773
|
historySelector?: ((beef: number[], outputIndex: number, currentDepth: number) => Promise<boolean>) | number,
|
|
697
774
|
currentDepth = 0
|
|
@@ -772,7 +849,7 @@ export class Engine {
|
|
|
772
849
|
* @param output - The UTXO to be deleted.
|
|
773
850
|
* @returns {Promise<void>} - A promise that resolves when the deletion process is complete.
|
|
774
851
|
*/
|
|
775
|
-
private async deleteUTXODeep(output: Output): Promise<void> {
|
|
852
|
+
private async deleteUTXODeep (output: Output): Promise<void> {
|
|
776
853
|
try {
|
|
777
854
|
// Delete the current output IFF there are no references to it
|
|
778
855
|
if (output.consumedBy.length === 0) {
|
|
@@ -781,7 +858,7 @@ export class Engine {
|
|
|
781
858
|
// Notify the lookup services of the UTXO being deleted
|
|
782
859
|
for (const l of Object.values(this.lookupServices)) {
|
|
783
860
|
try {
|
|
784
|
-
await l.
|
|
861
|
+
await l.outputNoLongerRetainedInHistory?.(
|
|
785
862
|
output.txid,
|
|
786
863
|
output.outputIndex,
|
|
787
864
|
output.topic
|
|
@@ -828,7 +905,7 @@ export class Engine {
|
|
|
828
905
|
* @param txid BE hex string double hash of transaction proven by proof.
|
|
829
906
|
* @param proof for txid
|
|
830
907
|
*/
|
|
831
|
-
private updateInputProofs(tx: Transaction, txid: string, proof: MerklePath): void {
|
|
908
|
+
private updateInputProofs (tx: Transaction, txid: string, proof: MerklePath): void {
|
|
832
909
|
if (tx.merklePath !== undefined) {
|
|
833
910
|
// Update the merkle path to handle potential reorgs
|
|
834
911
|
tx.merklePath = proof
|
|
@@ -839,7 +916,8 @@ export class Engine {
|
|
|
839
916
|
} else {
|
|
840
917
|
for (const input of tx.inputs) {
|
|
841
918
|
// All inputs must have sourceTransactions
|
|
842
|
-
const stx = input.sourceTransaction
|
|
919
|
+
const stx = input.sourceTransaction
|
|
920
|
+
if (typeof stx !== 'object') continue
|
|
843
921
|
this.updateInputProofs(stx, txid, proof)
|
|
844
922
|
}
|
|
845
923
|
}
|
|
@@ -852,7 +930,7 @@ export class Engine {
|
|
|
852
930
|
* @param txid - The txid for which proof is a valid merkle path.
|
|
853
931
|
* @param proof - The merklePath proving txid is a mined transaction hash
|
|
854
932
|
*/
|
|
855
|
-
private async updateMerkleProof(output: Output, txid: string, proof: MerklePath): Promise<void> {
|
|
933
|
+
private async updateMerkleProof (output: Output, txid: string, proof: MerklePath): Promise<void> {
|
|
856
934
|
if (output.beef === undefined) {
|
|
857
935
|
throw new Error('Output must have associated transaction BEEF!')
|
|
858
936
|
}
|
|
@@ -886,7 +964,7 @@ export class Engine {
|
|
|
886
964
|
* @param proof - Merkle proof containing the Merkle path and other relevant data to verify the transaction.
|
|
887
965
|
* @param blockHeight - The block height associated with the incoming merkle proof.
|
|
888
966
|
*/
|
|
889
|
-
async handleNewMerkleProof(txid: string, proof: MerklePath, blockHeight?: number): Promise<void> {
|
|
967
|
+
async handleNewMerkleProof (txid: string, proof: MerklePath, blockHeight?: number): Promise<void> {
|
|
890
968
|
const outputs = await this.storage.findOutputsForTransaction(txid, true)
|
|
891
969
|
|
|
892
970
|
if (outputs === undefined || outputs.length === 0) {
|
|
@@ -909,21 +987,21 @@ export class Engine {
|
|
|
909
987
|
* @public
|
|
910
988
|
* @returns {Promise<Record<string, { name: string; shortDescription: string; iconURL?: string; version?: string; informationURL?: string; }>>} - Supported topic managers and their metadata
|
|
911
989
|
*/
|
|
912
|
-
async listTopicManagers(): Promise<Record<string, {
|
|
913
|
-
name: string
|
|
914
|
-
shortDescription: string
|
|
915
|
-
iconURL?: string
|
|
916
|
-
version?: string
|
|
917
|
-
informationURL?: string
|
|
990
|
+
async listTopicManagers (): Promise<Record<string, {
|
|
991
|
+
name: string
|
|
992
|
+
shortDescription: string
|
|
993
|
+
iconURL?: string
|
|
994
|
+
version?: string
|
|
995
|
+
informationURL?: string
|
|
918
996
|
}>> {
|
|
919
|
-
|
|
920
|
-
name: string
|
|
921
|
-
shortDescription: string
|
|
922
|
-
iconURL?: string
|
|
923
|
-
version?: string
|
|
924
|
-
informationURL?: string
|
|
997
|
+
const result: Record<string, {
|
|
998
|
+
name: string
|
|
999
|
+
shortDescription: string
|
|
1000
|
+
iconURL?: string
|
|
1001
|
+
version?: string
|
|
1002
|
+
informationURL?: string
|
|
925
1003
|
}> = {}
|
|
926
|
-
for (
|
|
1004
|
+
for (const t in this.managers) {
|
|
927
1005
|
try {
|
|
928
1006
|
result[t] = await this.managers[t].getMetaData()
|
|
929
1007
|
} catch (e) {
|
|
@@ -942,21 +1020,21 @@ export class Engine {
|
|
|
942
1020
|
* @public
|
|
943
1021
|
* @returns {Promise<Record<string, { name: string; shortDescription: string; iconURL?: string; version?: string; informationURL?: string; }>>} - Supported lookup services and their metadata
|
|
944
1022
|
*/
|
|
945
|
-
async listLookupServiceProviders(): Promise<Record<string, {
|
|
946
|
-
name: string
|
|
947
|
-
shortDescription: string
|
|
948
|
-
iconURL?: string
|
|
949
|
-
version?: string
|
|
950
|
-
informationURL?: string
|
|
1023
|
+
async listLookupServiceProviders (): Promise<Record<string, {
|
|
1024
|
+
name: string
|
|
1025
|
+
shortDescription: string
|
|
1026
|
+
iconURL?: string
|
|
1027
|
+
version?: string
|
|
1028
|
+
informationURL?: string
|
|
951
1029
|
}>> {
|
|
952
|
-
|
|
953
|
-
name: string
|
|
954
|
-
shortDescription: string
|
|
955
|
-
iconURL?: string
|
|
956
|
-
version?: string
|
|
957
|
-
informationURL?: string
|
|
1030
|
+
const result: Record<string, {
|
|
1031
|
+
name: string
|
|
1032
|
+
shortDescription: string
|
|
1033
|
+
iconURL?: string
|
|
1034
|
+
version?: string
|
|
1035
|
+
informationURL?: string
|
|
958
1036
|
}> = {}
|
|
959
|
-
for (
|
|
1037
|
+
for (const ls in this.lookupServices) {
|
|
960
1038
|
try {
|
|
961
1039
|
result[ls] = await this.lookupServices[ls].getMetaData()
|
|
962
1040
|
} catch (e) {
|
|
@@ -975,7 +1053,7 @@ export class Engine {
|
|
|
975
1053
|
* @public
|
|
976
1054
|
* @returns {Promise<string>} - the documentation for the topic manager
|
|
977
1055
|
*/
|
|
978
|
-
async getDocumentationForTopicManager(manager: any): Promise<string> {
|
|
1056
|
+
async getDocumentationForTopicManager (manager: any): Promise<string> {
|
|
979
1057
|
const documentation = await this.managers[manager]?.getDocumentation?.()
|
|
980
1058
|
return documentation !== undefined ? documentation : 'No documentation found!'
|
|
981
1059
|
}
|
|
@@ -985,7 +1063,7 @@ export class Engine {
|
|
|
985
1063
|
* @public
|
|
986
1064
|
* @returns {Promise<string>} - the documentation for the lookup service
|
|
987
1065
|
*/
|
|
988
|
-
async getDocumentationForLookupServiceProvider(provider: any): Promise<string> {
|
|
1066
|
+
async getDocumentationForLookupServiceProvider (provider: any): Promise<string> {
|
|
989
1067
|
const documentation = await this.lookupServices[provider]?.getDocumentation?.()
|
|
990
1068
|
return documentation !== undefined ? documentation : 'No documentation found!'
|
|
991
1069
|
}
|
|
@@ -1000,12 +1078,12 @@ export class Engine {
|
|
|
1000
1078
|
* @param url - The URL string to validate
|
|
1001
1079
|
* @returns {boolean} - Returns `false` if the URL violates any of the conditions `true` otherwise
|
|
1002
1080
|
*/
|
|
1003
|
-
private isValidUrl(url: string): boolean {
|
|
1081
|
+
private isValidUrl (url: string): boolean {
|
|
1004
1082
|
try {
|
|
1005
1083
|
const parsedUrl = new URL(url)
|
|
1006
1084
|
|
|
1007
1085
|
// Disallow http:
|
|
1008
|
-
if (parsedUrl.protocol ===
|
|
1086
|
+
if (parsedUrl.protocol === 'http:') {
|
|
1009
1087
|
return false
|
|
1010
1088
|
}
|
|
1011
1089
|
|
|
@@ -1021,9 +1099,9 @@ export class Engine {
|
|
|
1021
1099
|
const nonRoutableIpv4Patterns = [
|
|
1022
1100
|
/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // Loopback IPs
|
|
1023
1101
|
/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // 10.x.x.x private IPs
|
|
1024
|
-
/^192\.168\.\d{1,3}\.\d{1,3}$/,
|
|
1102
|
+
/^192\.168\.\d{1,3}\.\d{1,3}$/, // 192.168.x.x private IPs
|
|
1025
1103
|
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/, // 172.16.x.x to 172.31.x.x private IPs
|
|
1026
|
-
/^0\.0\.0\.0$/
|
|
1104
|
+
/^0\.0\.0\.0$/ // Non-routable address
|
|
1027
1105
|
]
|
|
1028
1106
|
|
|
1029
1107
|
// Check for IPv4 matches
|
|
@@ -1032,7 +1110,7 @@ export class Engine {
|
|
|
1032
1110
|
}
|
|
1033
1111
|
|
|
1034
1112
|
// Check for non-routable IPv6 addresses explicitly
|
|
1035
|
-
if (ipAddress ===
|
|
1113
|
+
if (ipAddress === '[::1]') {
|
|
1036
1114
|
return false
|
|
1037
1115
|
}
|
|
1038
1116
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { GASPInitialReply, GASPInitialRequest, GASPInitialResponse, GASPNode, GASPNodeResponse, GASPRemote } from
|
|
1
|
+
import { GASPInitialReply, GASPInitialRequest, GASPInitialResponse, GASPNode, GASPNodeResponse, GASPRemote } from '@bsv/gasp'
|
|
2
2
|
|
|
3
3
|
export class OverlayGASPRemote implements GASPRemote {
|
|
4
|
-
constructor(public endpointURL: string, public topic: string) { }
|
|
4
|
+
constructor (public endpointURL: string, public topic: string) { }
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Given an outgoing initial request, sends the request to the foreign instance and obtains their initial response.
|
|
8
8
|
* @param request
|
|
9
9
|
* @returns
|
|
10
10
|
*/
|
|
11
|
-
async getInitialResponse(request: GASPInitialRequest): Promise<GASPInitialResponse> {
|
|
11
|
+
async getInitialResponse (request: GASPInitialRequest): Promise<GASPInitialResponse> {
|
|
12
12
|
// Send out an HTTP request to the URL (current host for topic)
|
|
13
13
|
// Include the topic in the request
|
|
14
14
|
// Parse out response and return correct format
|
|
@@ -50,7 +50,7 @@ export class OverlayGASPRemote implements GASPRemote {
|
|
|
50
50
|
* @param metadata
|
|
51
51
|
* @returns
|
|
52
52
|
*/
|
|
53
|
-
async requestNode(graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
|
|
53
|
+
async requestNode (graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
|
|
54
54
|
// Send an HTTP request with the provided info and get back a gaspNode
|
|
55
55
|
const url = `${this.endpointURL}/requestForeignGASPNode`
|
|
56
56
|
const body = {
|
|
@@ -95,13 +95,13 @@ export class OverlayGASPRemote implements GASPRemote {
|
|
|
95
95
|
// ---- Now optional methods ----
|
|
96
96
|
|
|
97
97
|
// When are only syncing to them
|
|
98
|
-
async getInitialReply(response: GASPInitialResponse): Promise<GASPInitialReply> {
|
|
98
|
+
async getInitialReply (response: GASPInitialResponse): Promise<GASPInitialReply> {
|
|
99
99
|
throw new Error('Function not supported!')
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// Only used when supporting bidirectional sync.
|
|
103
103
|
// Overlay services does not support this.
|
|
104
|
-
async submitNode(node: GASPNode): Promise<void | GASPNodeResponse> {
|
|
104
|
+
async submitNode (node: GASPNode): Promise<void | GASPNodeResponse> {
|
|
105
105
|
throw new Error('Node submission not supported!')
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -22,14 +22,14 @@ export interface GraphNode {
|
|
|
22
22
|
export class OverlayGASPStorage implements GASPStorage {
|
|
23
23
|
readonly temporaryGraphNodeRefs: Record<string, GraphNode> = {}
|
|
24
24
|
|
|
25
|
-
constructor(public topic: string, public engine: Engine, public maxNodesInGraph?: number) { }
|
|
25
|
+
constructor (public topic: string, public engine: Engine, public maxNodesInGraph?: number) { }
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
*
|
|
29
29
|
* @param since
|
|
30
30
|
* @returns
|
|
31
31
|
*/
|
|
32
|
-
async findKnownUTXOs(since: number): Promise<Array<{ txid: string, outputIndex: number }>> {
|
|
32
|
+
async findKnownUTXOs (since: number): Promise<Array<{ txid: string, outputIndex: number }>> {
|
|
33
33
|
const UTXOs = await this.engine.storage.findUTXOsForTopic(this.topic, since)
|
|
34
34
|
return UTXOs.map(output => ({
|
|
35
35
|
txid: output.txid,
|
|
@@ -39,13 +39,13 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* For a given txid and output index, returns the associated transaction, a merkle proof if the transaction is in a block, and metadata if if requested. If no metadata is requested, metadata hashes on inputs are not returned.
|
|
42
|
-
* @param graphID
|
|
43
|
-
* @param txid
|
|
44
|
-
* @param outputIndex
|
|
45
|
-
* @param metadata
|
|
46
|
-
* @returns
|
|
42
|
+
* @param graphID
|
|
43
|
+
* @param txid
|
|
44
|
+
* @param outputIndex
|
|
45
|
+
* @param metadata
|
|
46
|
+
* @returns
|
|
47
47
|
*/
|
|
48
|
-
async hydrateGASPNode(graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
|
|
48
|
+
async hydrateGASPNode (graphID: string, txid: string, outputIndex: number, metadata: boolean): Promise<GASPNode> {
|
|
49
49
|
const output = await this.engine.storage.findOutput(txid, outputIndex, undefined, undefined, true)
|
|
50
50
|
|
|
51
51
|
if (output?.beef === undefined) {
|
|
@@ -72,7 +72,7 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
72
72
|
* @param tx The node for which needed inputs should be found.
|
|
73
73
|
* @returns A promise for a mapping of requested input transactions and whether metadata should be provided for each.
|
|
74
74
|
*/
|
|
75
|
-
async findNeededInputs(tx: GASPNode): Promise<GASPNodeResponse | undefined> {
|
|
75
|
+
async findNeededInputs (tx: GASPNode): Promise<GASPNodeResponse | undefined> {
|
|
76
76
|
// If there is no Merkle proof, we always need the inputs
|
|
77
77
|
const response: GASPNodeResponse = {
|
|
78
78
|
requestedInputs: {}
|
|
@@ -120,7 +120,7 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
120
120
|
* Ensures that no inputs are requested from foreign nodes before sending any GASP response
|
|
121
121
|
* Also terminates graphs if the response would be empty.
|
|
122
122
|
*/
|
|
123
|
-
private async stripAlreadyKnownInputs(response: GASPNodeResponse | undefined): Promise<GASPNodeResponse | undefined> {
|
|
123
|
+
private async stripAlreadyKnownInputs (response: GASPNodeResponse | undefined): Promise<GASPNodeResponse | undefined> {
|
|
124
124
|
if (typeof response === 'undefined') {
|
|
125
125
|
return response
|
|
126
126
|
}
|
|
@@ -144,7 +144,7 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
144
144
|
* @param spentBy Unless this is the same node identified by the graph ID, denotes the TXID and input index for the node which spent this one, in 36-byte format.
|
|
145
145
|
* @throws If the node cannot be appended to the graph, either because the graph ID is for a graph the recipient does not want or because the graph has grown to be too large before being finalized.
|
|
146
146
|
*/
|
|
147
|
-
async appendToGraph(tx: GASPNode, spentBy?: string | undefined): Promise<void> {
|
|
147
|
+
async appendToGraph (tx: GASPNode, spentBy?: string | undefined): Promise<void> {
|
|
148
148
|
if (this.maxNodesInGraph !== undefined && Object.keys(this.temporaryGraphNodeRefs).length >= this.maxNodesInGraph) {
|
|
149
149
|
throw new Error('The max number of nodes in transaction graph has been reached!')
|
|
150
150
|
}
|
|
@@ -194,7 +194,7 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
194
194
|
* @param graphID The TXID and output index (in 36-byte format) for the UTXO at the tip of this graph.
|
|
195
195
|
* @throws If the graph is not well-anchored, according to the rules of Bitcoin or the rules of the Overlay Topic Manager.
|
|
196
196
|
*/
|
|
197
|
-
async validateGraphAnchor(graphID: string): Promise<void> {
|
|
197
|
+
async validateGraphAnchor (graphID: string): Promise<void> {
|
|
198
198
|
const rootNode = this.temporaryGraphNodeRefs[graphID]
|
|
199
199
|
if (rootNode === undefined) {
|
|
200
200
|
throw new Error(`Graph node with ID ${graphID} not found`)
|
|
@@ -246,7 +246,7 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
246
246
|
* Deletes all data associated with a temporary graph that has failed to sync, if the graph exists.
|
|
247
247
|
* @param graphID The TXID and output index (in 36-byte format) for the UTXO at the tip of this graph.
|
|
248
248
|
*/
|
|
249
|
-
async discardGraph(graphID: string): Promise<void> {
|
|
249
|
+
async discardGraph (graphID: string): Promise<void> {
|
|
250
250
|
for (const [nodeId, graphRef] of Object.entries(this.temporaryGraphNodeRefs)) {
|
|
251
251
|
if (graphRef.graphID === graphID) {
|
|
252
252
|
// Delete child node
|
|
@@ -260,7 +260,7 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
260
260
|
* Finalizes a graph, solidifying the new UTXO and its ancestors so that it will appear in the list of known UTXOs.
|
|
261
261
|
* @param graphID The TXID and output index (in 36-byte format) for the UTXO at the root of this graph.
|
|
262
262
|
*/
|
|
263
|
-
async finalizeGraph(graphID: string): Promise<void> {
|
|
263
|
+
async finalizeGraph (graphID: string): Promise<void> {
|
|
264
264
|
const beefs = this.computeOrderedBEEFsForGraph(graphID)
|
|
265
265
|
|
|
266
266
|
// Submit all historical BEEFs in order, finalizing the graph for the current UTXO
|
|
@@ -277,11 +277,11 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
277
277
|
* @param {string} graphID — The ID of the graph for which BEEFs are required
|
|
278
278
|
* @returns Ordered BEEFs for the graph
|
|
279
279
|
*/
|
|
280
|
-
private computeOrderedBEEFsForGraph(graphID: string): number[][] {
|
|
280
|
+
private computeOrderedBEEFsForGraph (graphID: string): number[][] {
|
|
281
281
|
const beefs: number[][] = []
|
|
282
282
|
const hydrator = (node: GraphNode): void => {
|
|
283
283
|
const currentBEEF = this.getBEEFForNode(node)
|
|
284
|
-
if (beefs.
|
|
284
|
+
if (!beefs.includes(currentBEEF)) {
|
|
285
285
|
beefs.unshift(currentBEEF)
|
|
286
286
|
}
|
|
287
287
|
|
|
@@ -305,7 +305,7 @@ export class OverlayGASPStorage implements GASPStorage {
|
|
|
305
305
|
* @param node Graph node for which BEEF is needed.
|
|
306
306
|
* @returns BEEF array, including all proofs on inputs.
|
|
307
307
|
*/
|
|
308
|
-
private getBEEFForNode(node: GraphNode): number[] {
|
|
308
|
+
private getBEEFForNode (node: GraphNode): number[] {
|
|
309
309
|
// Given a node, hydrate its merkle proof or all inputs, returning a reference to the hydrated node's Transaction object
|
|
310
310
|
const hydrator = (node: GraphNode): Transaction => {
|
|
311
311
|
const tx = Transaction.fromHex(node.rawTx)
|