@bsv/overlay 0.5.1 → 0.5.2

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 (54) hide show
  1. package/dist/cjs/package.json +2 -2
  2. package/dist/cjs/src/Engine.js +249 -220
  3. package/dist/cjs/src/Engine.js.map +1 -1
  4. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  5. package/dist/esm/src/Engine.js +245 -216
  6. package/dist/esm/src/Engine.js.map +1 -1
  7. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  8. package/dist/types/src/Engine.d.ts.map +1 -1
  9. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  10. package/docs/API.md +2 -2
  11. package/package.json +2 -2
  12. package/src/Engine.ts +243 -218
  13. package/dist/cjs/src/AdmittanceInstructions.js +0 -3
  14. package/dist/cjs/src/AdmittanceInstructions.js.map +0 -1
  15. package/dist/cjs/src/LookupAnswer.js +0 -3
  16. package/dist/cjs/src/LookupAnswer.js.map +0 -1
  17. package/dist/cjs/src/LookupQuestion.js +0 -3
  18. package/dist/cjs/src/LookupQuestion.js.map +0 -1
  19. package/dist/cjs/src/SHIPAdvertisement.js +0 -3
  20. package/dist/cjs/src/SHIPAdvertisement.js.map +0 -1
  21. package/dist/cjs/src/SLAPAdvertisement.js +0 -3
  22. package/dist/cjs/src/SLAPAdvertisement.js.map +0 -1
  23. package/dist/cjs/src/STEAK.js +0 -3
  24. package/dist/cjs/src/STEAK.js.map +0 -1
  25. package/dist/cjs/src/TaggedBEEF.js +0 -3
  26. package/dist/cjs/src/TaggedBEEF.js.map +0 -1
  27. package/dist/esm/src/AdmittanceInstructions.js +0 -2
  28. package/dist/esm/src/AdmittanceInstructions.js.map +0 -1
  29. package/dist/esm/src/LookupAnswer.js +0 -2
  30. package/dist/esm/src/LookupAnswer.js.map +0 -1
  31. package/dist/esm/src/LookupQuestion.js +0 -2
  32. package/dist/esm/src/LookupQuestion.js.map +0 -1
  33. package/dist/esm/src/SHIPAdvertisement.js +0 -2
  34. package/dist/esm/src/SHIPAdvertisement.js.map +0 -1
  35. package/dist/esm/src/SLAPAdvertisement.js +0 -2
  36. package/dist/esm/src/SLAPAdvertisement.js.map +0 -1
  37. package/dist/esm/src/STEAK.js +0 -2
  38. package/dist/esm/src/STEAK.js.map +0 -1
  39. package/dist/esm/src/TaggedBEEF.js +0 -2
  40. package/dist/esm/src/TaggedBEEF.js.map +0 -1
  41. package/dist/types/src/AdmittanceInstructions.d.ts +0 -14
  42. package/dist/types/src/AdmittanceInstructions.d.ts.map +0 -1
  43. package/dist/types/src/LookupAnswer.d.ts +0 -15
  44. package/dist/types/src/LookupAnswer.d.ts.map +0 -1
  45. package/dist/types/src/LookupQuestion.d.ts +0 -15
  46. package/dist/types/src/LookupQuestion.d.ts.map +0 -1
  47. package/dist/types/src/SHIPAdvertisement.d.ts +0 -9
  48. package/dist/types/src/SHIPAdvertisement.d.ts.map +0 -1
  49. package/dist/types/src/SLAPAdvertisement.d.ts +0 -9
  50. package/dist/types/src/SLAPAdvertisement.d.ts.map +0 -1
  51. package/dist/types/src/STEAK.d.ts +0 -10
  52. package/dist/types/src/STEAK.d.ts.map +0 -1
  53. package/dist/types/src/TaggedBEEF.d.ts +0 -11
  54. package/dist/types/src/TaggedBEEF.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/overlay",
3
- "version": "0.5.0rc1",
3
+ "version": "0.5.2",
4
4
  "type": "commonjs",
5
5
  "description": "BSV Blockchain Overlay Services Engine",
6
6
  "files": [
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@bsv/gasp": "^1.2.0",
52
- "@bsv/sdk": "^1.8.4",
52
+ "@bsv/sdk": "^1.8.11",
53
53
  "knex": "^3.1.0"
54
54
  }
55
55
  }
@@ -119,135 +119,152 @@ class Engine {
119
119
  this.endTime(`chainTracker_${txid.substring(0, 10)}`);
120
120
  const steak = {};
121
121
  const dupeTopics = new Set();
122
+ const failedTopics = new Set();
122
123
  // Parallelize the topic processing
123
124
  const topicPromises = taggedBEEF.topics.map(async (topic) => {
124
- if (this.managers[topic] === undefined || this.managers[topic] === null) {
125
- throw new Error(`This server does not support this topic: ${topic}`);
126
- }
127
- // Check for duplicate transactions
128
- this.startTime(`dupCheck_${txid.substring(0, 10)}`);
129
- const dupeCheck = await this.storage.doesAppliedTransactionExist({ txid, topic });
130
- this.endTime(`dupCheck_${txid.substring(0, 10)}`);
131
- if (dupeCheck) {
132
- dupeTopics.add(topic);
133
- steak[topic] = { outputsToAdmit: [], coinsToRetain: [] };
134
- return;
135
- }
136
- // Identify previous coins admitted to this specific topic
137
- const previousCoins = [];
138
- const outputPromises = tx.inputs.map(async (input, i) => {
139
- var _a;
140
- const previousTXID = input.sourceTXID !== undefined ? input.sourceTXID : (_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
141
- if (previousTXID !== undefined) {
142
- // Check if the previous output was admitted to this specific topic
143
- const output = await this.storage.findOutput(previousTXID, input.sourceOutputIndex, topic);
144
- if (output !== undefined && output !== null) {
145
- previousCoins.push(i);
146
- return output;
147
- }
125
+ try {
126
+ if (this.managers[topic] === undefined || this.managers[topic] === null) {
127
+ throw new Error(`This server does not support this topic: ${topic}`);
148
128
  }
149
- return null;
150
- });
151
- this.startTime(`previousOutputQuery_${txid.substring(0, 10)}`);
152
- const outputs = await Promise.all(outputPromises);
153
- this.endTime(`previousOutputQuery_${txid.substring(0, 10)}`);
154
- const markSpentPromises = outputs.map(async (output) => {
155
- if (output !== undefined && output !== null) {
156
- try {
157
- await this.storage.markUTXOAsSpent(output.txid, output.outputIndex, topic);
158
- await Promise.all(Object.values(this.lookupServices).map(async (l) => {
159
- var _a;
160
- try {
161
- if (typeof l.outputSpent === 'function') {
162
- if (l.spendNotificationMode === 'txid') {
163
- await l.outputSpent({
164
- mode: 'txid',
165
- spendingTxid: txid,
166
- txid: output.txid,
167
- outputIndex: output.outputIndex,
168
- topic
169
- });
170
- }
171
- else if (l.spendNotificationMode === 'script') {
172
- const inputIndex = tx.inputs.findIndex(i => {
173
- var _a;
174
- let realSource = i.sourceTXID;
175
- if (!realSource) {
176
- realSource = (_a = i.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
129
+ // Check for duplicate transactions
130
+ this.startTime(`dupCheck_${txid.substring(0, 10)}`);
131
+ const dupeCheck = await this.storage.doesAppliedTransactionExist({ txid, topic });
132
+ this.endTime(`dupCheck_${txid.substring(0, 10)}`);
133
+ if (dupeCheck) {
134
+ dupeTopics.add(topic);
135
+ steak[topic] = { outputsToAdmit: [], coinsToRetain: [] };
136
+ return;
137
+ }
138
+ // Identify previous coins admitted to this specific topic
139
+ const previousCoins = [];
140
+ const outputPromises = tx.inputs.map(async (input, i) => {
141
+ var _a;
142
+ const previousTXID = input.sourceTXID !== undefined ? input.sourceTXID : (_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
143
+ if (previousTXID !== undefined) {
144
+ // Check if the previous output was admitted to this specific topic
145
+ const output = await this.storage.findOutput(previousTXID, input.sourceOutputIndex, topic);
146
+ if (output !== undefined && output !== null) {
147
+ previousCoins.push(i);
148
+ return output;
149
+ }
150
+ }
151
+ return null;
152
+ });
153
+ this.startTime(`previousOutputQuery_${txid.substring(0, 10)}`);
154
+ const outputs = await Promise.all(outputPromises);
155
+ this.endTime(`previousOutputQuery_${txid.substring(0, 10)}`);
156
+ const markSpentPromises = outputs.map(async (output) => {
157
+ if (output !== undefined && output !== null) {
158
+ try {
159
+ await this.storage.markUTXOAsSpent(output.txid, output.outputIndex, topic);
160
+ await Promise.all(Object.values(this.lookupServices).map(async (l) => {
161
+ var _a;
162
+ try {
163
+ if (typeof l.outputSpent === 'function') {
164
+ if (l.spendNotificationMode === 'txid') {
165
+ await l.outputSpent({
166
+ mode: 'txid',
167
+ spendingTxid: txid,
168
+ txid: output.txid,
169
+ outputIndex: output.outputIndex,
170
+ topic
171
+ });
172
+ }
173
+ else if (l.spendNotificationMode === 'script') {
174
+ const inputIndex = tx.inputs.findIndex(i => {
175
+ var _a;
176
+ let realSource = i.sourceTXID;
177
+ if (!realSource) {
178
+ realSource = (_a = i.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
179
+ }
180
+ return realSource === output.txid && i.sourceOutputIndex === output.outputIndex;
181
+ });
182
+ if (inputIndex === -1) {
183
+ throw new Error('Could not find input index');
177
184
  }
178
- return realSource === output.txid && i.sourceOutputIndex === output.outputIndex;
179
- });
180
- if (!inputIndex) {
181
- throw new Error('Could not find input index');
185
+ await l.outputSpent({
186
+ mode: 'script',
187
+ spendingTxid: txid,
188
+ inputIndex,
189
+ sequenceNumber: (_a = tx.inputs[inputIndex].sequence) !== null && _a !== void 0 ? _a : 0xffffffff,
190
+ unlockingScript: tx.inputs[inputIndex].unlockingScript,
191
+ txid: output.txid,
192
+ outputIndex: output.outputIndex,
193
+ topic,
194
+ offChainValues
195
+ });
196
+ }
197
+ else if (l.spendNotificationMode === 'whole-tx') {
198
+ await l.outputSpent({
199
+ mode: 'whole-tx',
200
+ spendingAtomicBEEF: tx.toAtomicBEEF(),
201
+ txid: output.txid,
202
+ outputIndex: output.outputIndex,
203
+ topic,
204
+ offChainValues
205
+ });
206
+ }
207
+ else { // none
208
+ await l.outputSpent({
209
+ mode: 'none',
210
+ txid: output.txid,
211
+ outputIndex: output.outputIndex,
212
+ topic
213
+ });
182
214
  }
183
- await l.outputSpent({
184
- mode: 'script',
185
- spendingTxid: txid,
186
- inputIndex,
187
- sequenceNumber: (_a = tx.inputs[inputIndex].sequence) !== null && _a !== void 0 ? _a : 0xffffffff,
188
- unlockingScript: tx.inputs[inputIndex].unlockingScript,
189
- txid: output.txid,
190
- outputIndex: output.outputIndex,
191
- topic,
192
- offChainValues
193
- });
194
- }
195
- else if (l.spendNotificationMode === 'whole-tx') {
196
- await l.outputSpent({
197
- mode: 'whole-tx',
198
- spendingAtomicBEEF: tx.toAtomicBEEF(),
199
- txid: output.txid,
200
- outputIndex: output.outputIndex,
201
- topic,
202
- offChainValues
203
- });
204
- }
205
- else { // none
206
- await l.outputSpent({
207
- mode: 'none',
208
- txid: output.txid,
209
- outputIndex: output.outputIndex,
210
- topic
211
- });
212
215
  }
213
216
  }
214
- }
215
- catch (error) {
216
- this.logger.error('Error in lookup service for outputSpent:', error);
217
- }
218
- }));
217
+ catch (error) {
218
+ this.logger.error('Error in lookup service for outputSpent:', error);
219
+ }
220
+ }));
221
+ }
222
+ catch (error) {
223
+ this.logger.error('Error marking UTXO as spent:', error);
224
+ }
219
225
  }
220
- catch (error) {
221
- this.logger.error('Error marking UTXO as spent:', error);
226
+ });
227
+ let admissibleOutputs = { outputsToAdmit: [], coinsToRetain: [] };
228
+ // Determine which outputs are admissible for this topic
229
+ const admissibleOutputsPromise = (async () => {
230
+ try {
231
+ this.startTime(`identifyAdmissibleOutputs_${txid.substring(0, 10)}`);
232
+ admissibleOutputs = await this.managers[topic].identifyAdmissibleOutputs(taggedBEEF.beef, previousCoins, offChainValues);
233
+ this.endTime(`identifyAdmissibleOutputs_${txid.substring(0, 10)}`);
222
234
  }
223
- }
224
- });
225
- let admissibleOutputs = { outputsToAdmit: [], coinsToRetain: [] };
226
- // Determine which outputs are admissible for this topic
227
- const admissibleOutputsPromise = (async () => {
228
- try {
229
- this.startTime(`identifyAdmissibleOutputs_${txid.substring(0, 10)}`);
230
- admissibleOutputs = await this.managers[topic].identifyAdmissibleOutputs(taggedBEEF.beef, previousCoins, offChainValues);
231
- this.endTime(`identifyAdmissibleOutputs_${txid.substring(0, 10)}`);
232
- }
233
- catch (_) {
234
- steak[topic] = { outputsToAdmit: [], coinsToRetain: [] };
235
- }
236
- })();
237
- // Wait for both tasks to complete
238
- await Promise.all([markSpentPromises, admissibleOutputsPromise]);
239
- // Keep track of what outputs were admitted for what topic
240
- steak[topic] = admissibleOutputs;
235
+ catch (_) {
236
+ failedTopics.add(topic);
237
+ steak[topic] = { outputsToAdmit: [], coinsToRetain: [] };
238
+ }
239
+ })();
240
+ // Wait for both tasks to complete
241
+ await Promise.all([...markSpentPromises, admissibleOutputsPromise]);
242
+ // Keep track of what outputs were admitted for what topic
243
+ steak[topic] = admissibleOutputs;
244
+ }
245
+ catch (error) {
246
+ this.logger.error('Error processing topic during submit:', error);
247
+ failedTopics.add(topic);
248
+ steak[topic] = { outputsToAdmit: [], coinsToRetain: [] };
249
+ }
241
250
  });
242
251
  await Promise.all(topicPromises);
243
252
  // Broadcast the transaction if not historical and broadcaster is configured
244
253
  this.startTime(`broadcast_${txid.substring(0, 10)}`);
245
254
  if (mode !== 'historical-tx' && this.broadcaster !== undefined) {
246
- const response = await this.broadcaster.broadcast(tx);
247
- if ((0, sdk_1.isBroadcastFailure)(response) && this.throwOnBroadcastFailure) {
248
- const e = new Error(`Failed to broadcast transaction! Error: ${response.description}`);
249
- e.more = response.more;
250
- throw e;
255
+ try {
256
+ const response = await this.broadcaster.broadcast(tx);
257
+ if ((0, sdk_1.isBroadcastFailure)(response) && this.throwOnBroadcastFailure) {
258
+ const e = new Error(`Failed to broadcast transaction! Error: ${response.description}`);
259
+ e.more = response.more;
260
+ throw e;
261
+ }
262
+ }
263
+ catch (error) {
264
+ if (this.throwOnBroadcastFailure) {
265
+ throw error;
266
+ }
267
+ this.logger.error('Error broadcasting transaction:', error);
251
268
  }
252
269
  }
253
270
  this.endTime(`broadcast_${txid.substring(0, 10)}`);
@@ -260,119 +277,131 @@ class Engine {
260
277
  if (dupeTopics.has(topic)) {
261
278
  continue;
262
279
  }
263
- // Keep track of which outputs to admit, mark as stale, or retain
264
- const admissibleOutputs = steak[topic];
265
- const outputsToAdmit = admissibleOutputs.outputsToAdmit;
266
- const outputsConsumed = [];
267
- const outputsToMarkStale = [];
268
- // Recompute previousCoins for this topic to use in the update logic
269
- const previousCoins = [];
270
- await Promise.all(tx.inputs.map(async (input, i) => {
271
- var _a;
272
- const previousTXID = input.sourceTXID !== undefined ? input.sourceTXID : (_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
273
- if (previousTXID !== undefined) {
274
- const output = await this.storage.findOutput(previousTXID, input.sourceOutputIndex, topic);
275
- if (output !== undefined && output !== null) {
276
- previousCoins.push(i);
277
- }
278
- }
279
- }));
280
- // 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.
281
- for (const inputIndex of previousCoins) {
282
- const previousTXID = (_a = tx.inputs[inputIndex].sourceTXID) !== null && _a !== void 0 ? _a : (_b = tx.inputs[inputIndex].sourceTransaction) === null || _b === void 0 ? void 0 : _b.id('hex');
283
- if (typeof previousTXID !== 'string')
284
- continue;
285
- const previousOutputIndex = tx.inputs[inputIndex].sourceOutputIndex;
286
- if (!admissibleOutputs.coinsToRetain.includes(inputIndex)) {
287
- outputsToMarkStale.push({
288
- txid: previousTXID,
289
- previousOutputIndex,
290
- inputIndex
291
- });
292
- }
293
- else {
294
- outputsConsumed.push({
295
- txid: previousTXID,
296
- outputIndex: previousOutputIndex
297
- });
298
- }
280
+ if (failedTopics.has(topic)) {
281
+ continue;
299
282
  }
300
- // Remove stale outputs recursively
301
- this.startTime(`lookForStaleOutputs_${txid.substring(0, 10)}`);
302
- await Promise.all(outputsToMarkStale.map(async (coin) => {
303
- const output = await this.storage.findOutput(coin.txid, coin.previousOutputIndex, topic);
304
- if (output !== undefined && output !== null) {
305
- await this.deleteUTXODeep(output);
306
- }
307
- }));
308
- this.endTime(`lookForStaleOutputs_${txid.substring(0, 10)}`);
309
- // Update the STEAK to indicate which coins were removed
310
- steak[topic].coinsRemoved = outputsToMarkStale.map(x => x.inputIndex);
311
- // Handle admittance and notification of incoming UTXOs
312
- const newUTXOs = [];
313
- await Promise.all(outputsToAdmit.map(async (outputIndex) => {
314
- if (typeof tx.outputs[outputIndex].satoshis !== 'number')
315
- return;
316
- this.startTime(`insertNewOutput_${txid.substring(0, 10)}`);
317
- await this.storage.insertOutput({
318
- txid,
319
- outputIndex,
320
- outputScript: tx.outputs[outputIndex].lockingScript.toBinary(),
321
- satoshis: tx.outputs[outputIndex].satoshis,
322
- topic,
323
- spent: false,
324
- beef: taggedBEEF.beef,
325
- consumedBy: [],
326
- outputsConsumed,
327
- score: Date.now()
328
- });
329
- this.endTime(`insertNewOutput_${txid.substring(0, 10)}`);
330
- newUTXOs.push({ txid, outputIndex });
331
- this.startTime(`notifyLookupService${txid.substring(0, 10)}`);
332
- await Promise.all(Object.values(this.lookupServices).map(async (l) => {
333
- if (l.admissionMode === 'locking-script') {
334
- if (typeof tx.outputs[outputIndex].lockingScript !== 'object' ||
335
- typeof tx.outputs[outputIndex].satoshis !== 'number') {
336
- return;
283
+ try {
284
+ const admissibleOutputs = steak[topic];
285
+ const outputsToAdmit = admissibleOutputs.outputsToAdmit;
286
+ const outputsConsumed = [];
287
+ const outputsToMarkStale = [];
288
+ // Recompute previousCoins for this topic to use in the update logic
289
+ const previousCoins = [];
290
+ await Promise.all(tx.inputs.map(async (input, i) => {
291
+ var _a;
292
+ const previousTXID = input.sourceTXID !== undefined ? input.sourceTXID : (_a = input.sourceTransaction) === null || _a === void 0 ? void 0 : _a.id('hex');
293
+ if (previousTXID !== undefined) {
294
+ const output = await this.storage.findOutput(previousTXID, input.sourceOutputIndex, topic);
295
+ if (output !== undefined && output !== null) {
296
+ previousCoins.push(i);
337
297
  }
338
- await l.outputAdmittedByTopic({
339
- mode: 'locking-script',
340
- txid,
341
- outputIndex,
342
- lockingScript: tx.outputs[outputIndex].lockingScript,
343
- satoshis: tx.outputs[outputIndex].satoshis,
344
- topic,
345
- offChainValues
298
+ }
299
+ }));
300
+ // 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.
301
+ for (const inputIndex of previousCoins) {
302
+ const previousTXID = (_a = tx.inputs[inputIndex].sourceTXID) !== null && _a !== void 0 ? _a : (_b = tx.inputs[inputIndex].sourceTransaction) === null || _b === void 0 ? void 0 : _b.id('hex');
303
+ if (typeof previousTXID !== 'string')
304
+ continue;
305
+ const previousOutputIndex = tx.inputs[inputIndex].sourceOutputIndex;
306
+ if (!admissibleOutputs.coinsToRetain.includes(inputIndex)) {
307
+ outputsToMarkStale.push({
308
+ txid: previousTXID,
309
+ previousOutputIndex,
310
+ inputIndex
346
311
  });
347
312
  }
348
313
  else {
349
- await l.outputAdmittedByTopic({
350
- mode: 'whole-tx',
351
- atomicBEEF: tx.toAtomicBEEF(),
352
- outputIndex,
353
- topic,
354
- offChainValues
314
+ outputsConsumed.push({
315
+ txid: previousTXID,
316
+ outputIndex: previousOutputIndex
355
317
  });
356
318
  }
357
- }));
358
- this.endTime(`notifyLookupService${txid.substring(0, 10)}`);
359
- }));
360
- this.startTime(`outputConsumed_${txid.substring(0, 10)}`);
361
- // Update each output consumed to know who consumed it and insert applied transaction in parallel
362
- await Promise.all([
363
- ...outputsConsumed.map(async (output) => {
364
- const outputToUpdate = await this.storage.findOutput(output.txid, output.outputIndex, topic);
365
- if (outputToUpdate !== undefined && outputToUpdate !== null) {
366
- const newConsumedBy = [...new Set([...newUTXOs, ...outputToUpdate.consumedBy])];
367
- await this.storage.updateConsumedBy(output.txid, output.outputIndex, topic, newConsumedBy);
319
+ }
320
+ // Remove stale outputs recursively
321
+ this.startTime(`lookForStaleOutputs_${txid.substring(0, 10)}`);
322
+ await Promise.all(outputsToMarkStale.map(async (coin) => {
323
+ const output = await this.storage.findOutput(coin.txid, coin.previousOutputIndex, topic);
324
+ if (output !== undefined && output !== null) {
325
+ await this.deleteUTXODeep(output);
368
326
  }
369
- }),
370
- this.storage.insertAppliedTransaction({
371
- txid,
372
- topic
373
- })
374
- ]);
375
- this.endTime(`outputConsumed_${txid.substring(0, 10)}`);
327
+ }));
328
+ this.endTime(`lookForStaleOutputs_${txid.substring(0, 10)}`);
329
+ // Update the STEAK to indicate which coins were removed
330
+ steak[topic].coinsRemoved = outputsToMarkStale.map(x => x.inputIndex);
331
+ // Handle admittance and notification of incoming UTXOs
332
+ const newUTXOs = [];
333
+ await Promise.all(outputsToAdmit.map(async (outputIndex) => {
334
+ if (typeof tx.outputs[outputIndex].satoshis !== 'number')
335
+ return;
336
+ this.startTime(`insertNewOutput_${txid.substring(0, 10)}`);
337
+ await this.storage.insertOutput({
338
+ txid,
339
+ outputIndex,
340
+ outputScript: tx.outputs[outputIndex].lockingScript.toBinary(),
341
+ satoshis: tx.outputs[outputIndex].satoshis,
342
+ topic,
343
+ spent: false,
344
+ beef: taggedBEEF.beef,
345
+ consumedBy: [],
346
+ outputsConsumed,
347
+ score: Date.now()
348
+ });
349
+ this.endTime(`insertNewOutput_${txid.substring(0, 10)}`);
350
+ newUTXOs.push({ txid, outputIndex });
351
+ this.startTime(`notifyLookupService${txid.substring(0, 10)}`);
352
+ await Promise.all(Object.values(this.lookupServices).map(async (l) => {
353
+ try {
354
+ if (l.admissionMode === 'locking-script') {
355
+ if (typeof tx.outputs[outputIndex].lockingScript !== 'object' ||
356
+ typeof tx.outputs[outputIndex].satoshis !== 'number') {
357
+ return;
358
+ }
359
+ await l.outputAdmittedByTopic({
360
+ mode: 'locking-script',
361
+ txid,
362
+ outputIndex,
363
+ lockingScript: tx.outputs[outputIndex].lockingScript,
364
+ satoshis: tx.outputs[outputIndex].satoshis,
365
+ topic,
366
+ offChainValues
367
+ });
368
+ }
369
+ else {
370
+ await l.outputAdmittedByTopic({
371
+ mode: 'whole-tx',
372
+ atomicBEEF: tx.toAtomicBEEF(),
373
+ outputIndex,
374
+ topic,
375
+ offChainValues
376
+ });
377
+ }
378
+ }
379
+ catch (error) {
380
+ this.logger.error('Error in lookup service for outputAdmittedByTopic:', error);
381
+ }
382
+ }));
383
+ this.endTime(`notifyLookupService${txid.substring(0, 10)}`);
384
+ }));
385
+ this.startTime(`outputConsumed_${txid.substring(0, 10)}`);
386
+ // Update each output consumed to know who consumed it and insert applied transaction in parallel
387
+ await Promise.all([
388
+ ...outputsConsumed.map(async (output) => {
389
+ const outputToUpdate = await this.storage.findOutput(output.txid, output.outputIndex, topic);
390
+ if (outputToUpdate !== undefined && outputToUpdate !== null) {
391
+ const newConsumedBy = [...new Set([...newUTXOs, ...outputToUpdate.consumedBy])];
392
+ await this.storage.updateConsumedBy(output.txid, output.outputIndex, topic, newConsumedBy);
393
+ }
394
+ }),
395
+ this.storage.insertAppliedTransaction({
396
+ txid,
397
+ topic
398
+ })
399
+ ]);
400
+ this.endTime(`outputConsumed_${txid.substring(0, 10)}`);
401
+ }
402
+ catch (error) {
403
+ this.logger.error('Error updating storage and notifying lookup services for topic', topic, error);
404
+ }
376
405
  }
377
406
  // If we don't have an advertiser or we are dealing with historical transactions, just return the steak
378
407
  if (this.advertiser === undefined || mode === 'historical-tx') {