@bsv/overlay 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cjs/mod.js.map +1 -1
  2. package/dist/cjs/package.json +2 -2
  3. package/dist/cjs/src/Advertisement.js +3 -0
  4. package/dist/cjs/src/Advertisement.js.map +1 -0
  5. package/dist/cjs/src/Engine.js +53 -52
  6. package/dist/cjs/src/Engine.js.map +1 -1
  7. package/dist/cjs/src/storage/knex/KnexStorage.js +11 -5
  8. package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
  9. package/dist/cjs/src/storage/knex/all-migrations.js +3 -4
  10. package/dist/cjs/src/storage/knex/all-migrations.js.map +1 -1
  11. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.js +18 -0
  12. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -0
  13. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  14. package/dist/esm/mod.js.map +1 -1
  15. package/dist/esm/src/Advertisement.js +2 -0
  16. package/dist/esm/src/Advertisement.js.map +1 -0
  17. package/dist/esm/src/Engine.js +51 -51
  18. package/dist/esm/src/Engine.js.map +1 -1
  19. package/dist/esm/src/storage/knex/KnexStorage.js +11 -5
  20. package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
  21. package/dist/esm/src/storage/knex/all-migrations.js +4 -5
  22. package/dist/esm/src/storage/knex/all-migrations.js.map +1 -1
  23. package/dist/esm/src/storage/knex/migrations/2024-07-10-001-block-height.js +13 -0
  24. package/dist/esm/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -0
  25. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  26. package/dist/types/mod.d.ts +2 -0
  27. package/dist/types/mod.d.ts.map +1 -1
  28. package/dist/types/src/Advertisement.d.ts +9 -0
  29. package/dist/types/src/Advertisement.d.ts.map +1 -0
  30. package/dist/types/src/Advertiser.d.ts +16 -24
  31. package/dist/types/src/Advertiser.d.ts.map +1 -1
  32. package/dist/types/src/Engine.d.ts +7 -9
  33. package/dist/types/src/Engine.d.ts.map +1 -1
  34. package/dist/types/src/Output.d.ts +1 -0
  35. package/dist/types/src/Output.d.ts.map +1 -1
  36. package/dist/types/src/storage/Storage.d.ts +8 -0
  37. package/dist/types/src/storage/Storage.d.ts.map +1 -1
  38. package/dist/types/src/storage/knex/KnexStorage.d.ts +1 -0
  39. package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
  40. package/dist/types/src/storage/knex/all-migrations.d.ts +3 -2
  41. package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
  42. package/dist/types/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts +4 -0
  43. package/dist/types/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts.map +1 -0
  44. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  45. package/mod.ts +2 -0
  46. package/package.json +2 -2
  47. package/src/Advertisement.ts +8 -0
  48. package/src/Advertiser.ts +17 -25
  49. package/src/Engine.ts +58 -62
  50. package/src/Output.ts +1 -0
  51. package/src/storage/Storage.ts +10 -0
  52. package/src/storage/knex/KnexStorage.ts +12 -5
  53. package/src/storage/knex/all-migrations.ts +10 -7
  54. package/src/storage/knex/migrations/2024-07-10-001-block-height.ts +15 -0
  55. package/src/GASP.ts +0 -421
  56. package/src/SHIPAdvertisement.ts +0 -8
  57. package/src/SLAPAdvertisement.ts +0 -8
package/src/Engine.ts CHANGED
@@ -9,10 +9,10 @@ import { LookupQuestion } from './LookupQuestion.js'
9
9
  import { LookupAnswer } from './LookupAnswer.js'
10
10
  import { LookupFormula } from './LookupFormula.js'
11
11
  import { Transaction, ChainTracker, MerklePath, Broadcaster, isBroadcastFailure } from '@bsv/sdk'
12
- import { Advertiser } from './Advertiser.js'
13
- import { SHIPAdvertisement } from './SHIPAdvertisement.js'
14
- import { GASP, GASPInitialReply, GASPInitialRequest, GASPInitialResponse, GASPNode, GASPNodeResponse, GASPRemote, GASPStorage } from './GASP.js'
12
+ import { AdvertisementData, Advertiser } from './Advertiser.js'
13
+ import { GASP, GASPInitialReply, GASPInitialRequest, GASPInitialResponse, GASPNode, GASPNodeResponse, GASPRemote, GASPStorage } from '@bsv/gasp'
15
14
  import { SyncConfiguration } from './SyncConfiguration.js'
15
+ import { Advertisement } from './Advertisement.js'
16
16
 
17
17
  /**
18
18
  * Am engine for running BSV Overlay Services (topic managers and lookup services).
@@ -272,7 +272,7 @@ export class Engine {
272
272
 
273
273
  // Lookup will currently always return type output-list
274
274
  if (lookupAnswer.type === 'output-list') {
275
- const shipAdvertisements: SHIPAdvertisement[] = []
275
+ const shipAdvertisements: Advertisement[] = []
276
276
  lookupAnswer.outputs.forEach(output => {
277
277
  try {
278
278
  // Parse out the advertisements using the provided parser
@@ -286,7 +286,7 @@ export class Engine {
286
286
  }
287
287
  })
288
288
  if (shipAdvertisements.length > 0) {
289
- shipAdvertisements.forEach((advertisement: SHIPAdvertisement) => {
289
+ shipAdvertisements.forEach((advertisement: Advertisement) => {
290
290
  if (!domainToTopicsMap.has(advertisement.domain)) {
291
291
  domainToTopicsMap.set(advertisement.domain, new Set<string>())
292
292
  }
@@ -423,57 +423,49 @@ export class Engine {
423
423
  const configuredServices = Object.keys(this.lookupServices)
424
424
 
425
425
  // Step 2: Fetch Existing Advertisements
426
- const currentSHIPAdvertisements = await advertiser.findAllSHIPAdvertisements()
427
- const currentSLAPAdvertisements = await advertiser.findAllSLAPAdvertisements()
426
+ const currentSHIPAdvertisements = await advertiser.findAllAdvertisements('SHIP')
427
+ const currentSLAPAdvertisements = await advertiser.findAllAdvertisements('SLAP')
428
428
 
429
429
  // Step 3: Compare and Determine Actions
430
430
  const requiredSHIPAdvertisements = new Set(configuredTopics)
431
431
  const requiredSLAPAdvertisements = new Set(configuredServices)
432
432
 
433
- const existingSHIPTopics = new Set(currentSHIPAdvertisements.map(ad => ad.topic))
434
- const existingSLAPServices = new Set(currentSLAPAdvertisements.map(ad => ad.service))
433
+ const existingTopics = new Set(currentSHIPAdvertisements.map(ad => ad.topicOrService))
434
+ const existingServices = new Set(currentSLAPAdvertisements.map(ad => ad.topicOrService))
435
435
 
436
- const shipToCreate = Array.from(requiredSHIPAdvertisements).filter(topic => !existingSHIPTopics.has(topic))
437
- const shipToRevoke = currentSHIPAdvertisements.filter(ad => !requiredSHIPAdvertisements.has(ad.topic))
436
+ const shipsToCreate = Array.from(requiredSHIPAdvertisements).filter(topicOrService => !existingTopics.has(topicOrService))
437
+ const slapsToCreate = Array.from(requiredSLAPAdvertisements).filter(topicOrService => !existingServices.has(topicOrService))
438
+ const shipsToRevoke = currentSHIPAdvertisements.filter(ad => !requiredSHIPAdvertisements.has(ad.topicOrService))
439
+ const slapsToRevoke = currentSLAPAdvertisements.filter(ad => !requiredSLAPAdvertisements.has(ad.topicOrService))
438
440
 
439
- const slapToCreate = Array.from(requiredSLAPAdvertisements).filter(service => !existingSLAPServices.has(service))
440
- const slapToRevoke = currentSLAPAdvertisements.filter(ad => !requiredSLAPAdvertisements.has(ad.service))
441
-
442
- // Step 4: Update Advertisements
443
- for (const topic of shipToCreate) {
444
- try {
445
- const taggedBEEF = await advertiser.createSHIPAdvertisement(topic)
446
- await this.submit(taggedBEEF)
447
- } catch (error) {
448
- console.error('Failed to create SHIP advertisement:', error)
449
- }
450
- }
451
-
452
- for (const service of slapToCreate) {
453
- try {
454
- const taggedBEEF = await advertiser.createSLAPAdvertisement(service)
455
- await this.submit(taggedBEEF)
456
- } catch (error) {
457
- console.error('Failed to create SLAP advertisement:', error)
458
- }
459
- }
460
-
461
- for (const ad of shipToRevoke) {
462
- try {
463
- const taggedBEEF = await advertiser.revokeAdvertisement(ad)
441
+ // Create needed SHIP/SLAP advertisements
442
+ try {
443
+ if (shipsToCreate.length > 0 || slapsToCreate.length > 0) {
444
+ const advertisementData: AdvertisementData[] = [
445
+ ...shipsToCreate.map(topic => ({
446
+ protocol: 'SHIP' as 'SHIP',
447
+ topicOrServiceName: topic
448
+ })),
449
+ ...slapsToCreate.map(service => ({
450
+ protocol: 'SLAP' as 'SLAP',
451
+ topicOrServiceName: service
452
+ }))
453
+ ]
454
+ const taggedBEEF = await advertiser.createAdvertisements(advertisementData)
464
455
  await this.submit(taggedBEEF)
465
- } catch (error) {
466
- console.error('Failed to revoke SHIP advertisement:', error)
467
456
  }
457
+ } catch (error) {
458
+ console.error('Failed to create SHIP advertisement:', error)
468
459
  }
469
460
 
470
- for (const ad of slapToRevoke) {
471
- try {
472
- const taggedBEEF = await advertiser.revokeAdvertisement(ad)
461
+ // Revoke all advertisements to revoke
462
+ try {
463
+ if (shipsToRevoke.length > 0 || slapsToRevoke.length > 0) {
464
+ const taggedBEEF = await advertiser.revokeAdvertisements([...shipsToRevoke, ...slapsToRevoke])
473
465
  await this.submit(taggedBEEF)
474
- } catch (error) {
475
- console.error('Failed to revoke SLAP advertisement:', error)
476
466
  }
467
+ } catch (error) {
468
+ console.error('Failed to revoke SHIP/SLAP advertisements:', error)
477
469
  }
478
470
  }
479
471
 
@@ -539,12 +531,12 @@ export class Engine {
539
531
  * Given a GASP request, create an initial response.
540
532
  *
541
533
  * This method processes an initial synchronization request by finding the relevant UTXOs for the given topic
542
- * since the provided (TODO: timestamp or block height, we need to decide on sync timing semantics) in the request. It constructs a response that includes a list of these UTXOs
543
- * and the (timestamp or block height, TODO...) from the initial request.
534
+ * since the provided block height in the request. It constructs a response that includes a list of these UTXOs
535
+ * and the min block height from the initial request.
544
536
  *
545
- * @param initialRequest - The GASP initial request containing the version and the (timestamp or block height, TODO...) since the last sync.
537
+ * @param initialRequest - The GASP initial request containing the version and the block height since the last sync.
546
538
  * @param topic - The topic for which UTXOs are being requested.
547
- * @returns A promise that resolves to a GASPInitialResponse containing the list of UTXOs and the provided timestamp.
539
+ * @returns A promise that resolves to a GASPInitialResponse containing the list of UTXOs and the provided min block height.
548
540
  */
549
541
  async provideForeignSyncResponse(initialRequest: GASPInitialRequest, topic: string): Promise<GASPInitialResponse> {
550
542
  const UTXOs = await this.storage.findUTXOsForTopic(topic, initialRequest.since)
@@ -770,20 +762,19 @@ export class Engine {
770
762
 
771
763
  /**
772
764
  * Given a new transaction proof (txid, proof),
773
- *
774
765
  * update tx.merklePath if appropriate,
775
- *
776
766
  * and if not, recurse through all input sourceTransactions.
777
- *
767
+ *
778
768
  * @param tx transaction which may benefit from new proof.
779
769
  * @param txid BE hex string double hash of transaction proven by proof.
780
770
  * @param proof for txid
781
771
  */
782
- private updateInputProofs(tx: Transaction, txid: string, proof: MerklePath) {
783
- if (tx.merklePath)
784
- // transaction already has a proof
772
+ private updateInputProofs(tx: Transaction, txid: string, proof: MerklePath): void {
773
+ if (tx.merklePath !== undefined) {
774
+ // Update the merkle path to handle potential reorgs
775
+ tx.merklePath = proof
785
776
  return
786
-
777
+ }
787
778
  if (tx.id('hex') === txid) {
788
779
  tx.merklePath = proof
789
780
  } else {
@@ -803,12 +794,12 @@ export class Engine {
803
794
  * @param proof - The merklePath proving txid is a mined transaction hash
804
795
  */
805
796
  private async updateMerkleProof(output: Output, txid: string, proof: MerklePath): Promise<void> {
806
-
807
797
  const tx = Transaction.fromBEEF(output.beef)
808
-
809
- if (tx.merklePath)
810
- // Already have a proof for this output's transaction.
798
+ if (tx.merklePath !== undefined) {
799
+ // Update the merkle path to handle potential reorgs
800
+ tx.merklePath = proof
811
801
  return
802
+ }
812
803
 
813
804
  // recursively update all sourceTransactions proven by (txid,proof)
814
805
  this.updateInputProofs(tx, txid, proof)
@@ -830,16 +821,23 @@ export class Engine {
830
821
  *
831
822
  * @param txid - Transaction ID of the associated outputs to prune.
832
823
  * @param proof - Merkle proof containing the Merkle path and other relevant data to verify the transaction.
824
+ * @param blockHeight - The block height associated with the incoming merkle proof.
833
825
  */
834
- async handleNewMerkleProof(txid: string, proof: MerklePath): Promise<void> {
826
+ async handleNewMerkleProof(txid: string, proof: MerklePath, blockHeight?: number): Promise<void> {
835
827
  const outputs = await this.storage.findOutputsForTransaction(txid)
836
828
 
837
- if (outputs == undefined || outputs.length === 0) {
829
+ if (outputs === undefined || outputs.length === 0) {
838
830
  throw new Error('Could not find matching transaction outputs for proof ingest!')
839
831
  }
840
832
 
841
833
  for (const output of outputs) {
842
834
  await this.updateMerkleProof(output, txid, proof)
835
+
836
+ // Add the associated blockHeight
837
+ if (blockHeight !== undefined) {
838
+ output.blockHeight = blockHeight
839
+ await this.storage.updateOutputBlockHeight?.(output.txid, output.outputIndex, output.topic, blockHeight)
840
+ }
843
841
  }
844
842
  }
845
843
 
@@ -1006,7 +1004,6 @@ export class OverlayGASPRemote implements GASPRemote {
1006
1004
  */
1007
1005
  export interface GraphNode {
1008
1006
  txid: string
1009
- time: number
1010
1007
  graphID: string
1011
1008
  rawTx: string
1012
1009
  outputIndex: number
@@ -1158,7 +1155,6 @@ export class OverlayGASPStorage implements GASPStorage {
1158
1155
  // Use the spentBy param which should be a txid.inputIndex for the node which spent this one in 36-byte format
1159
1156
  const newGraphNode: GraphNode = {
1160
1157
  txid,
1161
- time: 0, // TODO: Determine required format for Time (either block height or timestamp, undefined / Infinity for unconfirmed transactions
1162
1158
  graphID: tx.graphID,
1163
1159
  rawTx: tx.rawTx,
1164
1160
  outputIndex: tx.outputIndex,
package/src/Output.ts CHANGED
@@ -26,4 +26,5 @@ export type Output = {
26
26
  txid: string
27
27
  outputIndex: number
28
28
  }>
29
+ blockHeight?: number
29
30
  }
@@ -80,6 +80,16 @@ export interface Storage {
80
80
  */
81
81
  updateOutputBeef: (txid: string, outputIndex: number, topic: string, beef: number[]) => Promise<void>
82
82
 
83
+
84
+ /**
85
+ * Updates the block height on an output
86
+ * @param txid — TXID of the output to update
87
+ * @param outputIndex — Index of the output to update
88
+ * @param topic— Topic in which the output should be updated
89
+ * @param blockHeight - height of the block the transaction associated with this output was included in
90
+ */
91
+ updateOutputBlockHeight?: (txid: string, outputIndex: number, topic: string, blockHeight: number) => Promise<void>
92
+
83
93
  /**
84
94
  * Inserts record of the applied transaction
85
95
  * @param tx — The transaction to insert
@@ -59,11 +59,10 @@ export class KnexStorage implements Storage {
59
59
  async findUTXOsForTopic(topic: string, since?: number): Promise<Output[]> {
60
60
  const query = this.knex('outputs').where({ topic, spent: false })
61
61
 
62
- // TODO: Create new migration for outputs that adds either created_at or block height tagging for proven outpoints if necessary.
63
- // if (since !== undefined) {
64
- // const sinceDate = new Date(since).toISOString()
65
- // await query.andWhere('created_at', '>=', sinceDate)
66
- // }
62
+ // If provided, additionally filters UTXOs by block height
63
+ if (since !== undefined && since > 0) {
64
+ await query.andWhere('blockHeight', '>=', since)
65
+ }
67
66
 
68
67
  const outputs = await query.select(
69
68
  'txid', 'outputIndex', 'outputScript', 'topic', 'satoshis', 'beef', 'outputsConsumed', 'spent', 'consumedBy'
@@ -127,6 +126,14 @@ export class KnexStorage implements Storage {
127
126
  }).update('beef', Buffer.from(beef))
128
127
  }
129
128
 
129
+ async updateOutputBlockHeight(txid: string, outputIndex: number, topic: string, blockHeight: number): Promise<void> {
130
+ await this.knex('outputs').where({
131
+ txid,
132
+ outputIndex,
133
+ topic
134
+ }).update('blockHeight', blockHeight)
135
+ }
136
+
130
137
  async insertAppliedTransaction(tx: { txid: string, topic: string }): Promise<void> {
131
138
  await this.knex('applied_transactions').insert({
132
139
  txid: tx.txid,
@@ -1,14 +1,17 @@
1
1
  import { Knex } from 'knex'
2
- import { up as oneUp, down as oneDown } from './migrations/2024-05-18-001-initial.js'
2
+ import { up as initialSchemaUp, down as initialSchemaDown } from './migrations/2024-05-18-001-initial.js'
3
+ import { up as addBlockHeightColumnUp, down as addBlockHeightColumnDown } from './migrations/2024-07-10-001-block-height.js'
3
4
 
4
5
  /**
5
6
  * An array of all migrations, in order.
6
7
  */
7
- const allMigrations: {
8
- up: (knex: Knex) => Promise<void>
9
- down: (knex: Knex) => Promise<void>
10
- }[] = [
11
- { up: oneUp, down: oneDown }
12
- ]
8
+ type Migration = {
9
+ up: (knex: Knex) => Promise<void>;
10
+ down: (knex: Knex) => Promise<void>;
11
+ };
13
12
 
13
+ const allMigrations: Migration[] = [
14
+ { up: initialSchemaUp, down: initialSchemaDown },
15
+ { up: addBlockHeightColumnUp, down: addBlockHeightColumnDown }
16
+ ];
14
17
  export default allMigrations
@@ -0,0 +1,15 @@
1
+ import type { Knex } from 'knex'
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ // Add new column for blockHeight associated with outputs
5
+ await knex.schema.table('outputs', table => {
6
+ table.integer('blockHeight').unsigned().nullable()
7
+ })
8
+ }
9
+
10
+ export async function down(knex: Knex): Promise<void> {
11
+ // Remove the blockHeight column (rollback operation)
12
+ await knex.schema.table('outputs', table => {
13
+ table.dropColumn('blockHeight')
14
+ })
15
+ }