@bsv/overlay 0.1.8 → 0.1.9

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 (80) hide show
  1. package/dist/cjs/package.json +2 -2
  2. package/dist/cjs/src/Engine.js +152 -492
  3. package/dist/cjs/src/Engine.js.map +1 -1
  4. package/dist/cjs/src/GASP/OverlayGASPRemote.js +6 -4
  5. package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
  6. package/dist/cjs/src/GASP/OverlayGASPStorage.js +152 -77
  7. package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
  8. package/dist/cjs/src/storage/knex/KnexStorage.js +106 -38
  9. package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
  10. package/dist/cjs/src/storage/knex/all-migrations.js +5 -1
  11. package/dist/cjs/src/storage/knex/all-migrations.js.map +1 -1
  12. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js +39 -0
  13. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -0
  14. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.js +34 -0
  15. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -0
  16. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  17. package/dist/esm/src/Engine.js +150 -491
  18. package/dist/esm/src/Engine.js.map +1 -1
  19. package/dist/esm/src/GASP/OverlayGASPRemote.js +7 -4
  20. package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
  21. package/dist/esm/src/GASP/OverlayGASPStorage.js +151 -77
  22. package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
  23. package/dist/esm/src/storage/knex/KnexStorage.js +106 -38
  24. package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
  25. package/dist/esm/src/storage/knex/all-migrations.js +5 -1
  26. package/dist/esm/src/storage/knex/all-migrations.js.map +1 -1
  27. package/dist/esm/src/storage/knex/migrations/2024-07-17-001-transactions.js +34 -0
  28. package/dist/esm/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -0
  29. package/dist/esm/src/storage/knex/migrations/2024-07-18-001-indexes.js +29 -0
  30. package/dist/esm/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -0
  31. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  32. package/dist/types/src/Engine.d.ts +8 -115
  33. package/dist/types/src/Engine.d.ts.map +1 -1
  34. package/dist/types/src/GASP/OverlayGASPRemote.d.ts +3 -2
  35. package/dist/types/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
  36. package/dist/types/src/GASP/OverlayGASPStorage.d.ts +29 -3
  37. package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
  38. package/dist/types/src/Output.d.ts +2 -2
  39. package/dist/types/src/Output.d.ts.map +1 -1
  40. package/dist/types/src/storage/Storage.d.ts +6 -8
  41. package/dist/types/src/storage/Storage.d.ts.map +1 -1
  42. package/dist/types/src/storage/knex/KnexStorage.d.ts +4 -4
  43. package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
  44. package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
  45. package/dist/types/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts +4 -0
  46. package/dist/types/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -0
  47. package/dist/types/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts +4 -0
  48. package/dist/types/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts.map +1 -0
  49. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  50. package/package.json +2 -2
  51. package/src/Engine.ts +158 -550
  52. package/src/GASP/OverlayGASPRemote.ts +107 -0
  53. package/src/GASP/OverlayGASPStorage.ts +331 -0
  54. package/src/Output.ts +2 -2
  55. package/src/__tests/Engine.test.ts +27 -26
  56. package/src/__tests/OverlayGASPRemote.test.ts +1 -1
  57. package/src/__tests/OverlayGASPStorage.test.ts +4 -3
  58. package/src/storage/Storage.ts +6 -8
  59. package/src/storage/knex/KnexStorage.ts +119 -41
  60. package/src/storage/knex/all-migrations.ts +10 -6
  61. package/src/storage/knex/migrations/2024-07-17-001-transactions.ts +40 -0
  62. package/src/storage/knex/migrations/2024-07-18-001-indexes.ts +30 -0
  63. package/dist/cjs/src/GASP.js +0 -275
  64. package/dist/cjs/src/GASP.js.map +0 -1
  65. package/dist/cjs/src/SHIPAdvertisement.js +0 -3
  66. package/dist/cjs/src/SHIPAdvertisement.js.map +0 -1
  67. package/dist/cjs/src/SLAPAdvertisement.js +0 -3
  68. package/dist/cjs/src/SLAPAdvertisement.js.map +0 -1
  69. package/dist/esm/src/GASP.js +0 -279
  70. package/dist/esm/src/GASP.js.map +0 -1
  71. package/dist/esm/src/SHIPAdvertisement.js +0 -2
  72. package/dist/esm/src/SHIPAdvertisement.js.map +0 -1
  73. package/dist/esm/src/SLAPAdvertisement.js +0 -2
  74. package/dist/esm/src/SLAPAdvertisement.js.map +0 -1
  75. package/dist/types/src/GASP.d.ts +0 -215
  76. package/dist/types/src/GASP.d.ts.map +0 -1
  77. package/dist/types/src/SHIPAdvertisement.d.ts +0 -9
  78. package/dist/types/src/SHIPAdvertisement.d.ts.map +0 -1
  79. package/dist/types/src/SLAPAdvertisement.d.ts +0 -9
  80. package/dist/types/src/SLAPAdvertisement.d.ts.map +0 -1
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OverlayGASPStorage = exports.OverlayGASPRemote = exports.Engine = void 0;
3
+ exports.Engine = void 0;
4
4
  const sdk_1 = require("@bsv/sdk");
5
5
  const gasp_1 = require("@bsv/gasp");
6
+ const OverlayGASPRemote_js_1 = require("./GASP/OverlayGASPRemote.js");
7
+ const OverlayGASPStorage_js_1 = require("./GASP/OverlayGASPStorage.js");
6
8
  /**
7
9
  * Am engine for running BSV Overlay Services (topic managers and lookup services).
8
10
  */
@@ -19,8 +21,10 @@ class Engine {
19
21
  * @param {string} shipTrackers - SHIP domains we know to bootstrap the system
20
22
  * @param {string} slapTrackers - SLAP domains we know to bootstrap the system
21
23
  * @param {SyncConfiguration} syncConfiguration — Configuration object describing historical synchronization of topics.
24
+ * @param {boolean} logTime - Enables / disables the timing logs for various operations in the Overlay submit route.
25
+ * @param {string} logPrefix - Supports overriding the log prefix with a custom string.
22
26
  */
23
- constructor(managers, lookupServices, storage, chainTracker, hostingURL, shipTrackers, slapTrackers, broadcaster, advertiser, syncConfiguration) {
27
+ constructor(managers, lookupServices, storage, chainTracker, hostingURL, shipTrackers, slapTrackers, broadcaster, advertiser, syncConfiguration, logTime = false, logPrefix = '[OVERLAY_ENGINE] ') {
24
28
  this.managers = managers;
25
29
  this.lookupServices = lookupServices;
26
30
  this.storage = storage;
@@ -31,6 +35,8 @@ class Engine {
31
35
  this.broadcaster = broadcaster;
32
36
  this.advertiser = advertiser;
33
37
  this.syncConfiguration = syncConfiguration;
38
+ this.logTime = logTime;
39
+ this.logPrefix = logPrefix;
34
40
  // To encourage synchronization of overlay services, the SHIP sync strategy is used by default for all overlay topics, except for 'tm_ship' and 'tm_slap'.
35
41
  // For these two topics, any existing trackers are combined with the provided shipTrackers and slapTrackers omitting any duplicates.
36
42
  if (syncConfiguration === undefined) {
@@ -64,6 +70,17 @@ class Engine {
64
70
  }
65
71
  }
66
72
  }
73
+ // Helper functions for logging timings
74
+ startTime(label) {
75
+ if (this.logTime) {
76
+ console.time(`${this.logPrefix} ${label}`);
77
+ }
78
+ }
79
+ endTime(label) {
80
+ if (this.logTime) {
81
+ console.timeEnd(`${this.logPrefix} ${label}`);
82
+ }
83
+ }
67
84
  /**
68
85
  * Submits a transaction for processing by Overlay Services.
69
86
  * @param {TaggedBEEF} taggedBEEF - The transaction to process
@@ -75,7 +92,7 @@ class Engine {
75
92
  * @returns {Promise<STEAK>} The submitted transaction execution acknowledgement
76
93
  */
77
94
  async submit(taggedBEEF, onSteakReady, mode = 'current-tx') {
78
- var _a, _b;
95
+ var _a;
79
96
  for (const t of taggedBEEF.topics) {
80
97
  if (this.managers[t] === undefined || this.managers[t] === null) {
81
98
  throw new Error(`This server does not support this topic: ${t}`);
@@ -84,70 +101,95 @@ class Engine {
84
101
  // Validate the transaction SPV information
85
102
  const tx = sdk_1.Transaction.fromBEEF(taggedBEEF.beef);
86
103
  const txid = tx.id('hex');
87
- const txValid = await tx.verify(this.chainTracker); // Note: also verifying historical-tx with SPV. Needed?
104
+ this.startTime(`submit_${txid}`);
105
+ this.startTime(`chainTracker_${txid.substring(0, 10)}`);
106
+ const txValid = await tx.verify(this.chainTracker);
88
107
  if (!txValid)
89
108
  throw new Error('Unable to verify SPV information.');
90
- // Broadcast the transaction if not historical and broadcaster is configured
91
- if (mode !== 'historical-tx' && this.broadcaster !== undefined) {
92
- const response = await this.broadcaster.broadcast(tx);
93
- if ((0, sdk_1.isBroadcastFailure)(response)) {
94
- throw new Error(`Failed to broadcast transaction! Error: ${response.description}`);
95
- }
96
- }
97
- // Find UTXOs belonging to a particular topic
109
+ this.endTime(`chainTracker_${txid.substring(0, 10)}`);
98
110
  const steak = {};
99
- for (const topic of taggedBEEF.topics) {
100
- // Ensure transaction is not already applied to the topic
101
- const dupeCheck = await this.storage.doesAppliedTransactionExist({
102
- txid,
103
- topic
104
- });
111
+ let admissableOutputs = { outputsToAdmit: [], coinsToRetain: [] };
112
+ const previousCoins = [];
113
+ // Parallelize the topic processing
114
+ const topicPromises = taggedBEEF.topics.map(async (topic) => {
115
+ if (this.managers[topic] === undefined || this.managers[topic] === null) {
116
+ throw new Error(`This server does not support this topic: ${topic}`);
117
+ }
118
+ // Check for duplicate transactions
119
+ this.startTime(`dupCheck_${txid.substring(0, 10)}`);
120
+ const dupeCheck = await this.storage.doesAppliedTransactionExist({ txid, topic });
121
+ this.endTime(`dupCheck_${txid.substring(0, 10)}`);
105
122
  if (dupeCheck) {
106
- // The transaction was already processed.
107
- // Currently, NO OUTPUTS ARE ADMITTED FOR DUPLICATE TRANSACTIONS.
108
- // An alternative decision, one that was decided against, would be to act as if the operation was successful: looking up and returning the list of admitted outputs from when the transaction was originally processed.
109
- // This was decided against, because we don't want to encourage unnecessary flooding of duplicative transactions to overlay services.
110
- steak[topic] = {
111
- outputsToAdmit: [],
112
- coinsToRetain: []
113
- };
114
- continue;
123
+ steak[topic] = { outputsToAdmit: [], coinsToRetain: [] };
124
+ return;
115
125
  }
116
- // Check if any input of this transaction is a previous UTXO, adding previous UTXOs to the list
117
- const previousCoins = [];
118
- for (const [i, input] of tx.inputs.entries()) {
119
- const previousTXID = input.sourceTXID || ((_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex'));
120
- // Check if a previous UTXO exists in the storage medium
121
- const output = await this.storage.findOutput(previousTXID, input.sourceOutputIndex, topic);
126
+ // Check if any input of this transaction is a previous UTXO
127
+ const outputPromises = tx.inputs.map(async (input, i) => {
128
+ var _a;
129
+ const previousTXID = input.sourceTXID !== undefined ? input.sourceTXID : (_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
130
+ if (previousTXID !== undefined) {
131
+ const output = this.storage.findOutput(previousTXID, input.sourceOutputIndex, topic);
132
+ if (output !== undefined && output !== null) {
133
+ previousCoins.push(i);
134
+ return await Promise.resolve(output);
135
+ }
136
+ }
137
+ return await Promise.resolve(null);
138
+ });
139
+ this.startTime(`previousOutputQuery_${txid.substring(0, 10)}`);
140
+ const outputs = await Promise.all(outputPromises);
141
+ this.endTime(`previousOutputQuery_${txid.substring(0, 10)}`);
142
+ const markSpentPromises = outputs.map(async (output) => {
122
143
  if (output !== undefined && output !== null) {
123
- previousCoins.push(i);
124
- // This output is now spent.
125
- await this.storage.markUTXOAsSpent(output.txid, output.outputIndex, topic);
126
- // Notify the lookup services about the spending of this output
127
- for (const l of Object.values(this.lookupServices)) {
128
- try {
129
- if (l.outputSpent !== undefined && l.outputSpent !== null) {
130
- await l.outputSpent(output.txid, output.outputIndex, topic);
144
+ try {
145
+ await this.storage.markUTXOAsSpent(output.txid, output.outputIndex, topic);
146
+ await Promise.all(Object.values(this.lookupServices).map(async (l) => {
147
+ var _a;
148
+ try {
149
+ await ((_a = l.outputSpent) === null || _a === void 0 ? void 0 : _a.call(l, output.txid, output.outputIndex, topic));
131
150
  }
132
- }
133
- catch (_) { }
151
+ catch (error) {
152
+ console.error('Error in lookup service for outputSpent:', error);
153
+ }
154
+ }));
155
+ }
156
+ catch (error) {
157
+ console.error('Error marking UTXO as spent:', error);
134
158
  }
135
159
  }
160
+ });
161
+ // Determine which outputs are admissible
162
+ const admissibleOutputsPromise = (async () => {
163
+ try {
164
+ this.startTime(`identifyAdmissibleOutputs_${txid.substring(0, 10)}`);
165
+ admissableOutputs = await this.managers[topic].identifyAdmissibleOutputs(taggedBEEF.beef, previousCoins);
166
+ this.endTime(`identifyAdmissibleOutputs_${txid.substring(0, 10)}`);
167
+ }
168
+ catch (_) {
169
+ steak[topic] = { outputsToAdmit: [], coinsToRetain: [] };
170
+ }
171
+ })();
172
+ // Wait for both tasks to complete
173
+ await Promise.all([markSpentPromises, admissibleOutputsPromise]);
174
+ // Keep track of what outputs were admitted for what topic
175
+ steak[topic] = admissableOutputs;
176
+ });
177
+ await Promise.all(topicPromises);
178
+ // Broadcast the transaction if not historical and broadcaster is configured
179
+ this.startTime(`broadcast_${txid.substring(0, 10)}`);
180
+ if (mode !== 'historical-tx' && this.broadcaster !== undefined) {
181
+ const response = await this.broadcaster.broadcast(tx);
182
+ if ((0, sdk_1.isBroadcastFailure)(response)) {
183
+ throw new Error(`Failed to broadcast transaction! Error: ${response.description}`);
136
184
  }
137
- // Use the manager to determine which outputs are admissable
138
- let admissableOutputs;
139
- try {
140
- admissableOutputs = await this.managers[topic].identifyAdmissibleOutputs(taggedBEEF.beef, previousCoins);
141
- }
142
- catch (_) {
143
- // If the topic manager throws an error, other topics may still succeed, so we continue to the next one.
144
- // No outputs were admitted to this topic in this case. Note, however, that the transaction is still valid according to Bitcoin, so it may have spent some previous overlay members. This is unavoidable and good.
145
- steak[topic] = {
146
- outputsToAdmit: [],
147
- coinsToRetain: []
148
- };
149
- continue;
150
- }
185
+ }
186
+ this.endTime(`broadcast_${txid.substring(0, 10)}`);
187
+ // Call the callback function if it is provided
188
+ if (onSteakReady !== undefined) {
189
+ this.endTime(`submit_${txid}`);
190
+ onSteakReady(steak);
191
+ }
192
+ for (const topic of taggedBEEF.topics) {
151
193
  // Keep track of which outputs to admit, mark as stale, or retain
152
194
  const outputsToAdmit = admissableOutputs.outputsToAdmit;
153
195
  const staleCoins = [];
@@ -155,7 +197,7 @@ class Engine {
155
197
  // Find which outputs should not be retained and mark them as stale
156
198
  // For each of the previous UTXOs, if the the UTXO was not included in the list of UTXOs identified for retention, then it will be marked as stale.
157
199
  for (const inputIndex of previousCoins) {
158
- const previousTXID = tx.inputs[inputIndex].sourceTXID || ((_b = tx.inputs[inputIndex].sourceTransaction) === null || _b === void 0 ? void 0 : _b.id('hex'));
200
+ const previousTXID = tx.inputs[inputIndex].sourceTXID || ((_a = tx.inputs[inputIndex].sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex'));
159
201
  const previousOutputIndex = tx.inputs[inputIndex].sourceOutputIndex;
160
202
  if (!admissableOutputs.coinsToRetain.includes(inputIndex)) {
161
203
  staleCoins.push({
@@ -171,16 +213,18 @@ class Engine {
171
213
  }
172
214
  }
173
215
  // Remove stale outputs recursively
174
- for (const coin of staleCoins) {
216
+ this.startTime(`lookForStaleOutputs_${txid.substring(0, 10)}`);
217
+ await Promise.all(staleCoins.map(async (coin) => {
175
218
  const output = await this.storage.findOutput(coin.txid, coin.outputIndex, topic);
176
219
  if (output !== undefined && output !== null) {
177
220
  await this.deleteUTXODeep(output);
178
221
  }
179
- }
222
+ }));
223
+ this.endTime(`lookForStaleOutputs_${txid.substring(0, 10)}`);
180
224
  // Handle admittance and notification of incoming UTXOs
181
225
  const newUTXOs = [];
182
- for (const outputIndex of outputsToAdmit) {
183
- // Store the output
226
+ await Promise.all(outputsToAdmit.map(async (outputIndex) => {
227
+ this.startTime(`insertNewOutput_${txid.substring(0, 10)}`);
184
228
  await this.storage.insertOutput({
185
229
  txid,
186
230
  outputIndex,
@@ -192,51 +236,40 @@ class Engine {
192
236
  consumedBy: [],
193
237
  outputsConsumed
194
238
  });
195
- newUTXOs.push({
196
- txid,
197
- outputIndex
198
- });
199
- // Notify all the lookup services about the new UTXO
200
- for (const l of Object.values(this.lookupServices)) {
201
- try {
202
- if (l.outputAdded !== undefined && l.outputAdded !== null) {
203
- await l.outputAdded(txid, outputIndex, tx.outputs[outputIndex].lockingScript, topic);
204
- }
239
+ this.endTime(`insertNewOutput_${txid.substring(0, 10)}`);
240
+ newUTXOs.push({ txid, outputIndex });
241
+ this.startTime(`notifyLookupService${txid.substring(0, 10)}`);
242
+ await Promise.all(Object.values(this.lookupServices).map(async (l) => { var _a; return await ((_a = l.outputAdded) === null || _a === void 0 ? void 0 : _a.call(l, txid, outputIndex, tx.outputs[outputIndex].lockingScript, topic)); }));
243
+ this.endTime(`notifyLookupService${txid.substring(0, 10)}`);
244
+ }));
245
+ this.startTime(`outputConsumed_${txid.substring(0, 10)}`);
246
+ // Update each output consumed to know who consumed it and insert applied transaction in parallel
247
+ await Promise.all([
248
+ ...outputsConsumed.map(async (output) => {
249
+ const outputToUpdate = await this.storage.findOutput(output.txid, output.outputIndex, topic);
250
+ if (outputToUpdate !== undefined && outputToUpdate !== null) {
251
+ const newConsumedBy = [...new Set([...newUTXOs, ...outputToUpdate.consumedBy])];
252
+ await this.storage.updateConsumedBy(output.txid, output.outputIndex, topic, newConsumedBy);
205
253
  }
206
- catch (_) { }
207
- }
208
- }
209
- // Update each output consumed to know who consumed it
210
- for (const output of outputsConsumed) {
211
- const outputToUpdate = await this.storage.findOutput(output.txid, output.outputIndex, topic);
212
- if (outputToUpdate !== undefined && outputToUpdate !== null) {
213
- const newConsumedBy = [...new Set([...newUTXOs, ...outputToUpdate.consumedBy])];
214
- // Note: only update if newConsumedBy !== new Set(JSON.parse(outputToUpdate.consumedBy)) ?
215
- await this.storage.updateConsumedBy(output.txid, output.outputIndex, topic, newConsumedBy);
216
- }
217
- }
218
- // Insert the applied transaction to prevent duplicate processing
219
- await this.storage.insertAppliedTransaction({
220
- txid,
221
- topic
222
- });
223
- // Keep track of what outputs were admitted for what topic
224
- steak[topic] = admissableOutputs;
225
- }
226
- // Call the callback function if it is provided
227
- // TODO: To call `onSteakReady` sooner, we could have two loops. First we figure out topical admittance only, then we call `onSteakReeady` and do everything else after the first loop.
228
- if (onSteakReady !== undefined) {
229
- onSteakReady(steak);
254
+ }),
255
+ this.storage.insertAppliedTransaction({
256
+ txid,
257
+ topic
258
+ })
259
+ ]);
260
+ this.endTime(`outputConsumed_${txid.substring(0, 10)}`);
230
261
  }
231
262
  // If we don't have an advertiser or we are dealing with historical transactions, just return the steak
232
263
  if (this.advertiser === undefined || mode === 'historical-tx') {
233
264
  return steak;
234
265
  }
266
+ this.startTime(`transactionPropagation_${txid.substring(0, 10)}`);
235
267
  // Propagate transaction to other nodes according to synchronization agreements
236
268
  // 1. Find nodes that host the topics associated with admissable outputs
237
269
  // We want to figure out which topics we actually care about (because their associated outputs were admitted)
238
270
  // AND if the topic was not admitted we want to remove it from the list of topics we care about.
239
271
  const relevantTopics = taggedBEEF.topics.filter(topic => steak[topic] !== undefined && steak[topic].outputsToAdmit.length !== 0);
272
+ // TODO: Cache ship/slap lookup with expiry (every 5min)
240
273
  if (relevantTopics.length > 0) {
241
274
  // Find all SHIP advertisements for the topics we care about
242
275
  const domainToTopicsMap = new Map();
@@ -327,6 +360,7 @@ class Engine {
327
360
  console.error('Error during broadcasting:', error);
328
361
  }
329
362
  }
363
+ this.endTime(`transactionPropgation_${txid.substring(0, 10)}`);
330
364
  // Immediately return from the function without waiting for the promises to resolve.
331
365
  return steak;
332
366
  }
@@ -339,7 +373,7 @@ class Engine {
339
373
  // Validate a lookup service for the provider is found
340
374
  const lookupService = this.lookupServices[lookupQuestion.service];
341
375
  if (lookupService === undefined || lookupService === null)
342
- throw new Error(`Lookup service not found for provider: ${lookupQuestion.service}`);
376
+ throw new Error(`Lookup service not found for provider: ${lookupQuestion.service} `);
343
377
  let lookupResult = await lookupService.lookup(lookupQuestion);
344
378
  // Handle custom lookup service answers
345
379
  if (lookupResult.type === 'freeform' || lookupResult.type === 'output-list') {
@@ -349,12 +383,12 @@ class Engine {
349
383
  const hydratedOutputs = [];
350
384
  for (const { txid, outputIndex, history } of lookupResult) {
351
385
  // Make sure this is an unspent output (UTXO)
352
- const UTXO = await this.storage.findOutput(txid, outputIndex, undefined, false);
386
+ const UTXO = await this.storage.findOutput(txid, outputIndex, undefined, undefined, true);
353
387
  if (UTXO === undefined || UTXO === null)
354
388
  continue;
355
389
  // Get the history for this utxo and construct a BRC-8 Envelope
356
390
  const output = await this.getUTXOHistory(UTXO, history, 0);
357
- if (output !== undefined && output !== null) {
391
+ if ((output === null || output === void 0 ? void 0 : output.beef) !== undefined) {
358
392
  hydratedOutputs.push({
359
393
  beef: output.beef,
360
394
  outputIndex: output.outputIndex
@@ -487,7 +521,7 @@ class Engine {
487
521
  if (Array.isArray(syncEndpoints)) {
488
522
  await Promise.all(syncEndpoints.map(async (endpoint) => {
489
523
  // Sync to each host that is associated with this topic
490
- const gasp = new gasp_1.GASP(new OverlayGASPStorage(topic, this), new OverlayGASPRemote(endpoint, topic), 0, `[GASP Sync of ${topic} with ${endpoint}] `, true);
524
+ const gasp = new gasp_1.GASP(new OverlayGASPStorage_js_1.OverlayGASPStorage(topic, this), new OverlayGASPRemote_js_1.OverlayGASPRemote(endpoint, topic), 0, `[GASP Sync of ${topic} with ${endpoint}]`, true);
491
525
  await gasp.sync();
492
526
  }));
493
527
  }
@@ -527,7 +561,7 @@ class Engine {
527
561
  */
528
562
  async provideForeignGASPNode(graphID, txid, outputIndex) {
529
563
  const hydrator = async (output) => {
530
- if (output === undefined || output === null) {
564
+ if ((output === null || output === void 0 ? void 0 : output.beef) === undefined) {
531
565
  throw new Error('No matching output found!');
532
566
  }
533
567
  const rootTx = sdk_1.Transaction.fromBEEF(output.beef);
@@ -605,6 +639,9 @@ class Engine {
605
639
  if (typeof historySelector === 'undefined') {
606
640
  return output;
607
641
  }
642
+ if (output.beef === undefined) {
643
+ throw new Error('Output must have associated transaction BEEF!');
644
+ }
608
645
  // Determine if history traversal should continue for the current node
609
646
  let shouldTraverseHistory;
610
647
  if (typeof historySelector !== 'number') {
@@ -625,7 +662,7 @@ class Engine {
625
662
  const outputsConsumed = output.outputsConsumed;
626
663
  // Find the child outputs for each utxo consumed by the current output
627
664
  const childHistories = (await Promise.all(outputsConsumed.map(async (outputIdentifier) => {
628
- const output = await this.storage.findOutput(outputIdentifier.txid, outputIdentifier.outputIndex);
665
+ const output = await this.storage.findOutput(outputIdentifier.txid, outputIdentifier.outputIndex, undefined, undefined, true);
629
666
  // Make sure an output was found
630
667
  if (output === undefined || output === null) {
631
668
  return undefined;
@@ -644,6 +681,9 @@ class Engine {
644
681
  : (_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
645
682
  return sourceTXID === output.txid && input.sourceOutputIndex === output.outputIndex;
646
683
  });
684
+ if (input.beef === undefined) {
685
+ throw new Error('Input must have associated transaction BEEF!');
686
+ }
647
687
  tx.inputs[inputIndex].sourceTransaction = sdk_1.Transaction.fromBEEF(input.beef);
648
688
  }
649
689
  const beef = tx.toBEEF();
@@ -655,9 +695,9 @@ class Engine {
655
695
  catch (e) {
656
696
  // Handle any errors that occurred
657
697
  // Note: Test this!
658
- console.error(`Error retrieving UTXO history: ${e}`);
698
+ console.error(`Error retrieving UTXO history: ${e} `);
659
699
  // return []
660
- throw new Error(`Error retrieving UTXO history: ${e}`);
700
+ throw new Error(`Error retrieving UTXO history: ${e} `);
661
701
  }
662
702
  }
663
703
  /**
@@ -701,7 +741,7 @@ class Engine {
701
741
  });
702
742
  }
703
743
  catch (error) {
704
- throw new Error(`Failed to delete all stale outputs: ${error}`);
744
+ throw new Error(`Failed to delete all stale outputs: ${error} `);
705
745
  }
706
746
  }
707
747
  /**
@@ -738,6 +778,9 @@ class Engine {
738
778
  * @param proof - The merklePath proving txid is a mined transaction hash
739
779
  */
740
780
  async updateMerkleProof(output, txid, proof) {
781
+ if (output.beef === undefined) {
782
+ throw new Error('Output must have associated transaction BEEF!');
783
+ }
741
784
  const tx = sdk_1.Transaction.fromBEEF(output.beef);
742
785
  if (tx.merklePath !== undefined) {
743
786
  // Update the merkle path to handle potential reorgs
@@ -747,10 +790,10 @@ class Engine {
747
790
  // recursively update all sourceTransactions proven by (txid,proof)
748
791
  this.updateInputProofs(tx, txid, proof);
749
792
  // Update the output's BEEF in the storage DB
750
- await this.storage.updateOutputBeef(output.txid, output.outputIndex, output.topic, tx.toBEEF());
793
+ await this.storage.updateTransactionBEEF(output.txid, tx.toBEEF());
751
794
  // Recursively update the consumedBy outputs
752
795
  for (const consumingOutput of output.consumedBy) {
753
- const consumedOutputs = await this.storage.findOutputsForTransaction(consumingOutput.txid);
796
+ const consumedOutputs = await this.storage.findOutputsForTransaction(consumingOutput.txid, true);
754
797
  for (const consumedOutput of consumedOutputs) {
755
798
  await this.updateMerkleProof(consumedOutput, txid, proof);
756
799
  }
@@ -765,7 +808,7 @@ class Engine {
765
808
  */
766
809
  async handleNewMerkleProof(txid, proof, blockHeight) {
767
810
  var _a, _b;
768
- const outputs = await this.storage.findOutputsForTransaction(txid);
811
+ const outputs = await this.storage.findOutputsForTransaction(txid, true);
769
812
  if (outputs === undefined || outputs.length === 0) {
770
813
  throw new Error('Could not find matching transaction outputs for proof ingest!');
771
814
  }
@@ -824,388 +867,5 @@ There is currently a bug with the test runner that prevents importing and using
824
867
  Thus, all non-type exports have been moved to Engine.
825
868
  */
826
869
  // TODO: fix bug with imports that break tests. -----[GASP/OverlayGASPRemote.ts]-----
827
- class OverlayGASPRemote {
828
- constructor(endpointURL, topic) {
829
- this.endpointURL = endpointURL;
830
- this.topic = topic;
831
- }
832
- /**
833
- * Given an outgoing initial request, sends the request to the foreign instance and obtains their initial response.
834
- * @param request
835
- * @returns
836
- */
837
- async getInitialResponse(request) {
838
- // Send out an HTTP request to the URL (current host for topic)
839
- // Include the topic in the request
840
- // Parse out response and return correct format
841
- const url = `${this.endpointURL}/requestSyncResponse`;
842
- const response = await fetch(url, {
843
- method: 'POST',
844
- headers: {
845
- 'Content-Type': 'application/json',
846
- 'X-BSV-Topic': this.topic
847
- },
848
- body: JSON.stringify(request)
849
- });
850
- if (!response.ok) {
851
- throw new Error(`HTTP error! Status: ${response.status}`);
852
- }
853
- const result = await response.json();
854
- // Validate and return the response in the correct format
855
- if (!Array.isArray(result.UTXOList) || typeof result.since !== 'number') {
856
- throw new Error('Invalid response format');
857
- }
858
- return {
859
- UTXOList: result.UTXOList.map((utxo) => ({
860
- txid: utxo.txid,
861
- outputIndex: utxo.outputIndex
862
- })),
863
- since: result.since
864
- };
865
- }
866
- /**
867
- * Given an outgoing txid, outputIndex and optional metadata, request the associated GASP node from the foreign instance.
868
- * @param graphID
869
- * @param txid
870
- * @param outputIndex
871
- * @param metadata
872
- * @returns
873
- */
874
- async requestNode(graphID, txid, outputIndex, metadata) {
875
- // Send an HTTP request with the provided info and get back a gaspNode
876
- const url = `${this.endpointURL}/requestForeignGASPNode`;
877
- const body = {
878
- graphID,
879
- txid,
880
- outputIndex,
881
- metadata
882
- };
883
- const response = await fetch(url, {
884
- method: 'POST',
885
- headers: {
886
- 'Content-Type': 'application/json'
887
- },
888
- body: JSON.stringify(body)
889
- });
890
- if (!response.ok) {
891
- throw new Error(`HTTP error! Status: ${response.status}`);
892
- }
893
- const result = await response.json();
894
- // Validate and return the response in the correct format
895
- if (typeof result.graphID !== 'string' || typeof result.rawTx !== 'string' || typeof result.outputIndex !== 'number') {
896
- throw new Error('Invalid response format');
897
- }
898
- const gaspNode = {
899
- graphID: result.graphID,
900
- rawTx: result.rawTx,
901
- outputIndex: result.outputIndex,
902
- proof: result.proof,
903
- txMetadata: result.txMetadata,
904
- outputMetadata: result.outputMetadata,
905
- inputs: result.inputs
906
- };
907
- return gaspNode;
908
- }
909
- // ---- Now optional methods ----
910
- // When are only syncing to them
911
- async getInitialReply(response) {
912
- throw new Error('Function not supported!');
913
- }
914
- // Only used when supporting bidirectional sync.
915
- // Overlay services does not support this.
916
- async submitNode(node) {
917
- throw new Error('Node submission not supported!');
918
- }
919
- }
920
- exports.OverlayGASPRemote = OverlayGASPRemote;
921
- class OverlayGASPStorage {
922
- constructor(topic, engine, maxNodesInGraph) {
923
- this.topic = topic;
924
- this.engine = engine;
925
- this.maxNodesInGraph = maxNodesInGraph;
926
- this.temporaryGraphNodeRefs = {};
927
- }
928
- /**
929
- *
930
- * @param since
931
- * @returns
932
- */
933
- async findKnownUTXOs(since) {
934
- const UTXOs = await this.engine.storage.findUTXOsForTopic(this.topic, since);
935
- return UTXOs.map(output => ({
936
- txid: output.txid,
937
- outputIndex: output.outputIndex
938
- }));
939
- }
940
- /**
941
- * 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.
942
- * @param graphID
943
- * @param txid
944
- * @param outputIndex
945
- * @param metadata
946
- * @returns
947
- */
948
- async hydrateGASPNode(graphID, txid, outputIndex, metadata) {
949
- const output = await this.engine.storage.findOutput(txid, outputIndex);
950
- if (output === undefined || output === null) {
951
- throw new Error('No matching output found!');
952
- }
953
- const tx = sdk_1.Transaction.fromBEEF(output.beef);
954
- const rawTx = tx.toHex();
955
- const node = {
956
- rawTx,
957
- graphID,
958
- outputIndex
959
- };
960
- if (tx.merklePath !== undefined) {
961
- node.proof = tx.merklePath.toHex();
962
- }
963
- return node;
964
- }
965
- /**
966
- * For a given node, returns the inputs needed to complete the graph, including whether updated metadata is requested for those inputs.
967
- * @param tx The node for which needed inputs should be found.
968
- * @returns A promise for a mapping of requested input transactions and whether metadata should be provided for each.
969
- */
970
- async findNeededInputs(tx) {
971
- var _a, _b, _c;
972
- // If there is no Merkle proof, we always need the inputs
973
- const response = {
974
- requestedInputs: {}
975
- };
976
- const parsedTx = sdk_1.Transaction.fromHex(tx.rawTx);
977
- if (tx.proof === undefined) {
978
- for (const input of parsedTx.inputs) {
979
- response.requestedInputs[`${input.sourceTXID}.${input.sourceOutputIndex}`] = {
980
- metadata: false
981
- };
982
- }
983
- return await this.stripAlreadyKnownInputs(response);
984
- }
985
- // Attempt to check if the current transaction is admissible
986
- parsedTx.merklePath = sdk_1.MerklePath.fromHex(tx.proof);
987
- const admittanceResult = await this.engine.managers[this.topic].identifyAdmissibleOutputs(parsedTx.toBEEF(), []);
988
- if (admittanceResult.outputsToAdmit.includes(tx.outputIndex)) {
989
- // The transaction is admissible, no further inputs are needed
990
- }
991
- else {
992
- // The transaction is not admissible, get inputs needed for further verification
993
- // TopicManagers should implement a function to identify which inputs are needed.
994
- if (this.engine.managers[this.topic] !== undefined && typeof this.engine.managers[this.topic].identifyNeededInputs === 'function') {
995
- try {
996
- const neededInputs = (_c = await ((_b = (_a = this.engine.managers[this.topic]).identifyNeededInputs) === null || _b === void 0 ? void 0 : _b.call(_a, parsedTx.toBEEF()))) !== null && _c !== void 0 ? _c : [];
997
- for (const input of neededInputs) {
998
- response.requestedInputs[`${input.txid}.${input.outputIndex}`] = {
999
- metadata: false
1000
- };
1001
- }
1002
- return await this.stripAlreadyKnownInputs(response);
1003
- }
1004
- catch (e) {
1005
- console.error(`An error occurred when identifying needed inputs for transaction: ${parsedTx.id('hex')}.${tx.outputIndex}!`);
1006
- // Cut off the graph in case of an error here.
1007
- }
1008
- }
1009
- // By default, if the topic manager isn't able to stipulate needed inputs, only the inputs necessary for SPV are requested.
1010
- }
1011
- // Everything else falls through to returning undefined/void, which will terminate the synchronization at this point.
1012
- }
1013
- /**
1014
- * Ensures that no inputs are requested from foreign nodes before sending any GASP response
1015
- * Also terminates graphs if the response would be empty.
1016
- */
1017
- async stripAlreadyKnownInputs(response) {
1018
- if (typeof response === 'undefined') {
1019
- return response;
1020
- }
1021
- for (const inputNodeId of Object.keys(response.requestedInputs)) {
1022
- const [txid, outputIndex] = inputNodeId.split('.');
1023
- const found = await this.engine.storage.findOutput(txid, Number(outputIndex), this.topic);
1024
- if (found) {
1025
- delete response.requestedInputs[inputNodeId];
1026
- }
1027
- }
1028
- if (Object.keys(response.requestedInputs).length === 0) {
1029
- return undefined;
1030
- }
1031
- return response;
1032
- }
1033
- /**
1034
- * Appends a new node to a temporary graph.
1035
- * @param tx The node to append to this graph.
1036
- * @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.
1037
- * @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.
1038
- */
1039
- async appendToGraph(tx, spentBy) {
1040
- if (this.maxNodesInGraph !== undefined && Object.keys(this.temporaryGraphNodeRefs).length >= this.maxNodesInGraph) {
1041
- throw new Error('The max number of nodes in transaction graph has been reached!');
1042
- }
1043
- const parsedTx = sdk_1.Transaction.fromHex(tx.rawTx);
1044
- const txid = parsedTx.id('hex');
1045
- if (tx.proof !== undefined) {
1046
- parsedTx.merklePath = sdk_1.MerklePath.fromHex(tx.proof);
1047
- }
1048
- // Given the passed in node, append to the temp graph
1049
- // Use the spentBy param which should be a txid.inputIndex for the node which spent this one in 36-byte format
1050
- const newGraphNode = {
1051
- txid,
1052
- graphID: tx.graphID,
1053
- rawTx: tx.rawTx,
1054
- outputIndex: tx.outputIndex,
1055
- proof: tx.proof,
1056
- txMetadata: tx.txMetadata,
1057
- outputMetadata: tx.outputMetadata,
1058
- inputs: tx.inputs,
1059
- children: []
1060
- };
1061
- // If spentBy is undefined, then we know it's the root node.
1062
- if (spentBy === undefined) {
1063
- this.temporaryGraphNodeRefs[tx.graphID] = newGraphNode;
1064
- }
1065
- else {
1066
- // Find the parent node based on spentBy
1067
- const parentNode = this.temporaryGraphNodeRefs[spentBy];
1068
- if (parentNode !== undefined) {
1069
- // Set parent-child relationship
1070
- parentNode.children.push(newGraphNode);
1071
- newGraphNode.parent = parentNode;
1072
- this.temporaryGraphNodeRefs[`${newGraphNode.txid}.${newGraphNode.outputIndex}`] = newGraphNode;
1073
- }
1074
- else {
1075
- throw new Error(`Parent node with GraphID ${spentBy} not found`);
1076
- }
1077
- }
1078
- }
1079
- /**
1080
- * Checks whether the given graph, in its current state, makes reference only to transactions that are proven in the blockchain, or already known by the recipient to be valid.
1081
- * Additionally, in a breadth-first manner (ensuring that all inputs for any given node are processed before nodes that spend them), it ensures that the root node remains valid according to the rules of the overlay's topic manager,
1082
- * while considering any coins which the Manager had previously indicated were either valid or invalid.
1083
- * @param graphID The TXID and output index (in 36-byte format) for the UTXO at the tip of this graph.
1084
- * @throws If the graph is not well-anchored, according to the rules of Bitcoin or the rules of the Overlay Topic Manager.
1085
- */
1086
- async validateGraphAnchor(graphID) {
1087
- var _a;
1088
- const rootNode = this.temporaryGraphNodeRefs[graphID];
1089
- if (rootNode === undefined) {
1090
- throw new Error(`Graph node with ID ${graphID} not found`);
1091
- }
1092
- // Check that the root node is Bitcoin-valid.
1093
- const beef = this.getBEEFForNode(rootNode);
1094
- const spvTx = sdk_1.Transaction.fromBEEF(beef);
1095
- const isBitcoinValid = await spvTx.verify(this.engine.chainTracker);
1096
- if (!isBitcoinValid) {
1097
- throw new Error('The graph is not well-anchored according to the rules of Bitcoin.');
1098
- }
1099
- // Then, ensure the node is Overlay-valid.
1100
- const beefs = this.computeOrderedBEEFsForGraph(graphID);
1101
- // coins: a Set of all historical coins to retain (no need to remove them), used to emulate topical admittance of previous inputs over time.
1102
- const coins = new Set();
1103
- // Submit all historical BEEFs in order through the topic manager, tracking what would be retained until we submit the root node last.
1104
- // If, at the end, the root node is admitted, we have a valid overlay-specific graph.
1105
- for (const beef of beefs) {
1106
- // For any input to this transaction, see if it's a valid coin that's admitted. If so, it's a previous coin.
1107
- const previousCoins = [];
1108
- const tx = sdk_1.Transaction.fromBEEF(beef);
1109
- for (const inputIndex in tx.inputs) {
1110
- const input = tx.inputs[inputIndex];
1111
- const sourceTXID = input.sourceTXID || ((_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex'));
1112
- const coin = `${sourceTXID}.${input.sourceOutputIndex}`;
1113
- if (coins.has(coin)) {
1114
- previousCoins.push(Number(inputIndex));
1115
- }
1116
- }
1117
- const admittanceInstructions = await this.engine.managers[this.topic].identifyAdmissibleOutputs(beef, previousCoins);
1118
- // Every admitted output is now a coin.
1119
- for (const outputIndex of admittanceInstructions.outputsToAdmit) {
1120
- coins.add(`${tx.id('hex')}.${outputIndex}`);
1121
- }
1122
- }
1123
- // After sending through all the graph's BEEFs...
1124
- // If the root node is now a coin, we have acceptance by the overlay.
1125
- // Otherwise, throw.
1126
- if (!coins.has(graphID)) {
1127
- throw new Error('This graph did not result in topical admittance of the root node. Rejecting.');
1128
- }
1129
- }
1130
- /**
1131
- * Deletes all data associated with a temporary graph that has failed to sync, if the graph exists.
1132
- * @param graphID The TXID and output index (in 36-byte format) for the UTXO at the tip of this graph.
1133
- */
1134
- async discardGraph(graphID) {
1135
- for (const [nodeId, graphRef] of Object.entries(this.temporaryGraphNodeRefs)) {
1136
- if (graphRef.graphID === graphID) {
1137
- // Delete child node
1138
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1139
- delete this.temporaryGraphNodeRefs[nodeId];
1140
- }
1141
- }
1142
- }
1143
- /**
1144
- * Finalizes a graph, solidifying the new UTXO and its ancestors so that it will appear in the list of known UTXOs.
1145
- * @param graphID The TXID and output index (in 36-byte format) for the UTXO at the root of this graph.
1146
- */
1147
- async finalizeGraph(graphID) {
1148
- const beefs = this.computeOrderedBEEFsForGraph(graphID);
1149
- // Submit all historical BEEFs in order, finalizing the graph for the current UTXO
1150
- for (const beef of beefs) {
1151
- await this.engine.submit({
1152
- beef,
1153
- topics: [this.topic]
1154
- }, () => { }, 'historical-tx');
1155
- }
1156
- }
1157
- /**
1158
- * Computes an ordered set of BEEFs for the graph with the given graph IDs
1159
- * @param {string} graphID — The ID of the graph for which BEEFs are required
1160
- * @returns Ordered BEEFs for the graph
1161
- */
1162
- computeOrderedBEEFsForGraph(graphID) {
1163
- const beefs = [];
1164
- const hydrator = (node) => {
1165
- const currentBEEF = this.getBEEFForNode(node);
1166
- if (beefs.indexOf(currentBEEF) === -1) {
1167
- beefs.unshift(currentBEEF);
1168
- }
1169
- for (const child of node.children) {
1170
- // Continue backwards to the earliest nodes, adding them onto the beginning
1171
- hydrator(child);
1172
- }
1173
- };
1174
- // Start the hydrator with the root node
1175
- const foundRoot = this.temporaryGraphNodeRefs[graphID];
1176
- if (!foundRoot) {
1177
- throw new Error('Unable to find root node in graph for finalization!');
1178
- }
1179
- hydrator(foundRoot);
1180
- return beefs;
1181
- }
1182
- /**
1183
- * Computes a full BEEF for a given graph node, based on the temporary graph store.
1184
- * @param node Graph node for which BEEF is needed.
1185
- * @returns BEEF array, including all proofs on inputs.
1186
- */
1187
- getBEEFForNode(node) {
1188
- // Given a node, hydrate its merkle proof or all inputs, returning a reference to the hydrated node's Transaction object
1189
- const hydrator = (node) => {
1190
- const tx = sdk_1.Transaction.fromHex(node.rawTx);
1191
- if (node.proof) {
1192
- tx.merklePath = sdk_1.MerklePath.fromHex(node.proof);
1193
- return tx; // Transaction with proof, end of the line.
1194
- }
1195
- // For each input, look it up and recurse.
1196
- for (const inputIndex in tx.inputs) {
1197
- const input = tx.inputs[inputIndex];
1198
- const foundNode = this.temporaryGraphNodeRefs[`${input.sourceTXID}.${input.sourceOutputIndex}`];
1199
- if (!foundNode) {
1200
- throw new Error('Required input node for unproven parent not found in temporary graph store. Ensure, for every parent of any given already-proven node (kept for Overlay-specific historical reasons), that a proof is also provided on those inputs. While implicitly they are valid by virtue of their descendents being proven in the blockchain, BEEF serialization will still fail when winding forward the topical UTXO set histories during sync.');
1201
- }
1202
- tx.inputs[inputIndex].sourceTransaction = hydrator(foundNode);
1203
- }
1204
- return tx;
1205
- };
1206
- const finalTX = hydrator(node);
1207
- return finalTX.toBEEF();
1208
- }
1209
- }
1210
- exports.OverlayGASPStorage = OverlayGASPStorage;
870
+ // TODO: fix bug with imports that break tests. -----[GASP/OverlayGASPStorage.ts]-----
1211
871
  //# sourceMappingURL=Engine.js.map