@bsv/sdk 1.2.1 → 1.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -214,7 +214,7 @@
214
214
  "ts-jest": "^29.1.1",
215
215
  "ts-loader": "^9.5.1",
216
216
  "ts-standard": "^12.0.2",
217
- "ts2md": "^0.2.0",
217
+ "ts2md": "^0.2.4",
218
218
  "tsconfig-to-dual-package": "^1.2.0",
219
219
  "typescript": "^5.2.2",
220
220
  "webpack": "^5.95.0",
@@ -49,6 +49,8 @@ export const DEFAULT_SLAP_TRACKERS: string[] = [
49
49
  // Trackers known to host invalid or illegal records will be removed at the discretion of the BSV Association.
50
50
  ]
51
51
 
52
+ const MAX_TRACKER_WAIT_TIME = 1000
53
+
52
54
  /** Configuration options for the Lookup resolver. */
53
55
  export interface LookupResolverConfig {
54
56
  /** The facilitator used to make requests to Overlay Services hosts. */
@@ -63,7 +65,14 @@ export interface LookupResolverConfig {
63
65
 
64
66
  /** Facilitates lookups to URLs that return answers. */
65
67
  export interface OverlayLookupFacilitator {
66
- lookup: (url: string, question: LookupQuestion) => Promise<LookupAnswer>
68
+ /**
69
+ * Returns a lookup answer for a lookup question
70
+ * @param url - Overlay Service URL to send the lookup question to.
71
+ * @param question - Lookup question to find an answer to.
72
+ * @param timeout - Specifics how long to wait for a lookup answer in milliseconds.
73
+ * @returns
74
+ */
75
+ lookup: (url: string, question: LookupQuestion, timeout?: number) => Promise<LookupAnswer>
67
76
  }
68
77
 
69
78
  export class HTTPSOverlayLookupFacilitator implements OverlayLookupFacilitator {
@@ -73,17 +82,24 @@ export class HTTPSOverlayLookupFacilitator implements OverlayLookupFacilitator {
73
82
  this.fetchClient = httpClient
74
83
  }
75
84
 
76
- async lookup(url: string, question: LookupQuestion): Promise<LookupAnswer> {
85
+ async lookup(url: string, question: LookupQuestion, timeout: number = 5000): Promise<LookupAnswer> {
77
86
  if (!url.startsWith('https:')) {
78
87
  throw new Error('HTTPS facilitator can only use URLs that start with "https:"')
79
88
  }
80
- const response = await fetch(`${url}/lookup`, {
89
+ const timeoutPromise = new Promise((_, reject) =>
90
+ setTimeout(() => reject(new Error('Request timed out')), timeout)
91
+ )
92
+
93
+ const fetchPromise = fetch(`${url}/lookup`, {
81
94
  method: 'POST',
82
95
  headers: {
83
96
  'Content-Type': 'application/json'
84
97
  },
85
98
  body: JSON.stringify({ service: question.service, query: question.query })
86
99
  })
100
+
101
+ const response: Response = await Promise.race([fetchPromise, timeoutPromise]) as unknown as Response
102
+
87
103
  if (response.ok) {
88
104
  return await response.json()
89
105
  } else {
@@ -112,7 +128,7 @@ export default class LookupResolver {
112
128
  /**
113
129
  * Given a LookupQuestion, returns a LookupAnswer. Aggregates across multiple services and supports resiliency.
114
130
  */
115
- async query(question: LookupQuestion): Promise<LookupAnswer> {
131
+ async query(question: LookupQuestion, timeout?: number): Promise<LookupAnswer> {
116
132
  let competentHosts: string[] = []
117
133
  if (question.service === 'ls_slap') {
118
134
  competentHosts = this.slapTrackers
@@ -133,7 +149,7 @@ export default class LookupResolver {
133
149
 
134
150
  // Use Promise.allSettled to handle individual host failures
135
151
  const hostResponses = await Promise.allSettled(
136
- competentHosts.map(async host => await this.facilitator.lookup(host, question))
152
+ competentHosts.map(async host => await this.facilitator.lookup(host, question, timeout))
137
153
  )
138
154
 
139
155
  const successfulResponses = hostResponses
@@ -190,7 +206,7 @@ export default class LookupResolver {
190
206
 
191
207
  // Use Promise.allSettled to handle individual SLAP tracker failures
192
208
  const trackerResponses = await Promise.allSettled(
193
- this.slapTrackers.map(async tracker => await this.facilitator.lookup(tracker, query))
209
+ this.slapTrackers.map(async tracker => await this.facilitator.lookup(tracker, query, MAX_TRACKER_WAIT_TIME))
194
210
  )
195
211
 
196
212
  const hosts = new Set<string>()
@@ -56,6 +56,8 @@ export interface OverlayBroadcastFacilitator {
56
56
  send: (url: string, taggedBEEF: TaggedBEEF) => Promise<STEAK>
57
57
  }
58
58
 
59
+ const MAX_SHIP_QUERY_TIMEOUT = 1000
60
+
59
61
  export class HTTPSOverlayBroadcastFacilitator implements OverlayBroadcastFacilitator {
60
62
  httpClient: typeof fetch
61
63
 
@@ -116,8 +118,8 @@ export default class SHIPCast implements Broadcaster {
116
118
  } = config ?? {} as SHIPBroadcasterConfig
117
119
  this.facilitator = facilitator ?? new HTTPSOverlayBroadcastFacilitator()
118
120
  this.resolver = resolver ?? new LookupResolver()
119
- this.requireAcknowledgmentFromAllHostsForTopics = requireAcknowledgmentFromAllHostsForTopics ?? 'all'
120
- this.requireAcknowledgmentFromAnyHostForTopics = requireAcknowledgmentFromAnyHostForTopics ?? []
121
+ this.requireAcknowledgmentFromAllHostsForTopics = requireAcknowledgmentFromAllHostsForTopics ?? []
122
+ this.requireAcknowledgmentFromAnyHostForTopics = requireAcknowledgmentFromAnyHostForTopics ?? 'all'
121
123
  this.requireAcknowledgmentFromSpecificHostsForTopics = requireAcknowledgmentFromSpecificHostsForTopics ?? {}
122
124
  }
123
125
 
@@ -178,6 +180,7 @@ export default class SHIPCast implements Broadcaster {
178
180
  for (const [topic, instructions] of Object.entries(steak)) {
179
181
  const outputsToAdmit = instructions.outputsToAdmit
180
182
  const coinsToRetain = instructions.coinsToRetain
183
+ // TODO: Account for coins removed property on STEAK
181
184
  if ((outputsToAdmit && outputsToAdmit.length > 0) || (coinsToRetain && coinsToRetain.length > 0)) {
182
185
  acknowledgedTopics.add(topic)
183
186
  }
@@ -378,8 +381,8 @@ export default class SHIPCast implements Broadcaster {
378
381
  service: 'ls_ship',
379
382
  query: {
380
383
  topics: this.topics
381
- }
382
- })
384
+ },
385
+ }, MAX_SHIP_QUERY_TIMEOUT)
383
386
  if (answer.type !== 'output-list') {
384
387
  throw new Error('SHIP answer is not an output list.')
385
388
  }
@@ -63,7 +63,8 @@ describe('LookupResolver', () => {
63
63
  query: {
64
64
  service: 'ls_foo'
65
65
  }
66
- }
66
+ },
67
+ 1000
67
68
  ],
68
69
  [
69
70
  'https://slaphost.com',
@@ -71,8 +72,9 @@ describe('LookupResolver', () => {
71
72
  service: 'ls_foo',
72
73
  query: {
73
74
  test: 1
74
- }
75
- }
75
+ },
76
+ },
77
+ undefined
76
78
  ]
77
79
  ])
78
80
  })
@@ -139,7 +141,8 @@ describe('LookupResolver', () => {
139
141
  query: {
140
142
  service: 'ls_foo'
141
143
  }
142
- }
144
+ },
145
+ 1000
143
146
  ],
144
147
  [
145
148
  'https://slaphost.com',
@@ -148,7 +151,8 @@ describe('LookupResolver', () => {
148
151
  query: {
149
152
  test: 1
150
153
  }
151
- }
154
+ },
155
+ undefined
152
156
  ],
153
157
  [ // additional host should also have been queried
154
158
  'https://additional.host',
@@ -157,7 +161,8 @@ describe('LookupResolver', () => {
157
161
  query: {
158
162
  test: 1
159
163
  }
160
- }
164
+ },
165
+ undefined
161
166
  ]
162
167
  ])
163
168
  })
@@ -197,7 +202,8 @@ describe('LookupResolver', () => {
197
202
  query: {
198
203
  test: 1
199
204
  }
200
- }
205
+ },
206
+ undefined
201
207
  ]
202
208
  ])
203
209
  })
@@ -252,7 +258,8 @@ describe('LookupResolver', () => {
252
258
  query: {
253
259
  test: 1
254
260
  }
255
- }
261
+ },
262
+ undefined
256
263
  ],
257
264
  [ // additional host should also have been queried
258
265
  'https://additional.host',
@@ -261,7 +268,8 @@ describe('LookupResolver', () => {
261
268
  query: {
262
269
  test: 1
263
270
  }
264
- }
271
+ },
272
+ undefined
265
273
  ]
266
274
  ])
267
275
  })
@@ -341,7 +349,8 @@ describe('LookupResolver', () => {
341
349
  query: {
342
350
  service: 'ls_foo'
343
351
  }
344
- }
352
+ },
353
+ 1000
345
354
  ],
346
355
  [
347
356
  'https://mock.slap2',
@@ -350,7 +359,8 @@ describe('LookupResolver', () => {
350
359
  query: {
351
360
  service: 'ls_foo'
352
361
  }
353
- }
362
+ },
363
+ 1000
354
364
  ],
355
365
  [
356
366
  'https://slaphost1.com',
@@ -359,7 +369,8 @@ describe('LookupResolver', () => {
359
369
  query: {
360
370
  test: 1
361
371
  }
362
- }
372
+ },
373
+ undefined
363
374
  ],
364
375
  [
365
376
  'https://slaphost2.com',
@@ -368,7 +379,8 @@ describe('LookupResolver', () => {
368
379
  query: {
369
380
  test: 1
370
381
  }
371
- }
382
+ },
383
+ undefined
372
384
  ]
373
385
  ])
374
386
  })
@@ -434,7 +446,8 @@ describe('LookupResolver', () => {
434
446
  query: {
435
447
  service: 'ls_foo'
436
448
  }
437
- }
449
+ },
450
+ 1000
438
451
  ],
439
452
  [
440
453
  'https://slaphost1.com',
@@ -443,7 +456,8 @@ describe('LookupResolver', () => {
443
456
  query: {
444
457
  test: 1
445
458
  }
446
- }
459
+ },
460
+ undefined
447
461
  ],
448
462
  [
449
463
  'https://slaphost2.com',
@@ -452,7 +466,8 @@ describe('LookupResolver', () => {
452
466
  query: {
453
467
  test: 1
454
468
  }
455
- }
469
+ },
470
+ undefined
456
471
  ]
457
472
  ])
458
473
  })
@@ -521,7 +536,8 @@ describe('LookupResolver', () => {
521
536
  query: {
522
537
  service: 'ls_foo'
523
538
  }
524
- }
539
+ },
540
+ 1000
525
541
  ],
526
542
  [
527
543
  'https://slaphost1.com',
@@ -530,7 +546,8 @@ describe('LookupResolver', () => {
530
546
  query: {
531
547
  test: 1
532
548
  }
533
- }
549
+ },
550
+ undefined
534
551
  ],
535
552
  [
536
553
  'https://slaphost2.com',
@@ -539,7 +556,8 @@ describe('LookupResolver', () => {
539
556
  query: {
540
557
  test: 1
541
558
  }
542
- }
559
+ },
560
+ undefined
543
561
  ]
544
562
  ])
545
563
  })
@@ -607,7 +625,8 @@ describe('LookupResolver', () => {
607
625
  query: {
608
626
  service: 'ls_foo'
609
627
  }
610
- }
628
+ },
629
+ 1000
611
630
  ],
612
631
  [
613
632
  'https://slaphost1.com',
@@ -616,7 +635,8 @@ describe('LookupResolver', () => {
616
635
  query: {
617
636
  test: 1
618
637
  }
619
- }
638
+ },
639
+ undefined
620
640
  ],
621
641
  [
622
642
  'https://slaphost2.com',
@@ -625,7 +645,8 @@ describe('LookupResolver', () => {
625
645
  query: {
626
646
  test: 1
627
647
  }
628
- }
648
+ },
649
+ undefined
629
650
  ]
630
651
  ])
631
652
  })
@@ -655,7 +676,8 @@ describe('LookupResolver', () => {
655
676
  query: {
656
677
  service: 'ls_foo'
657
678
  }
658
- }
679
+ },
680
+ 1000
659
681
  ]
660
682
  ])
661
683
  })
@@ -725,7 +747,8 @@ describe('LookupResolver', () => {
725
747
  query: {
726
748
  service: 'ls_foo'
727
749
  }
728
- }
750
+ },
751
+ 1000
729
752
  ],
730
753
  [
731
754
  'https://slaphost1.com',
@@ -734,7 +757,8 @@ describe('LookupResolver', () => {
734
757
  query: {
735
758
  test: 1
736
759
  }
737
- }
760
+ },
761
+ undefined
738
762
  ],
739
763
  [
740
764
  'https://slaphost2.com',
@@ -743,7 +767,8 @@ describe('LookupResolver', () => {
743
767
  query: {
744
768
  test: 1
745
769
  }
746
- }
770
+ },
771
+ undefined
747
772
  ]
748
773
  ])
749
774
  })
@@ -777,7 +802,8 @@ describe('LookupResolver', () => {
777
802
  query: {
778
803
  test: 1
779
804
  }
780
- }
805
+ },
806
+ undefined
781
807
  ]
782
808
  ])
783
809
  })
@@ -808,7 +834,8 @@ describe('LookupResolver', () => {
808
834
  query: {
809
835
  service: 'ls_foo'
810
836
  }
811
- }
837
+ },
838
+ 1000
812
839
  ]
813
840
  ])
814
841
  })
@@ -882,7 +909,8 @@ describe('LookupResolver', () => {
882
909
  query: {
883
910
  service: 'ls_foo'
884
911
  }
885
- }
912
+ },
913
+ 1000
886
914
  ],
887
915
  [
888
916
  'https://mock.slap2',
@@ -891,7 +919,8 @@ describe('LookupResolver', () => {
891
919
  query: {
892
920
  service: 'ls_foo'
893
921
  }
894
- }
922
+ },
923
+ 1000
895
924
  ],
896
925
  [
897
926
  'https://slaphost.com',
@@ -900,7 +929,8 @@ describe('LookupResolver', () => {
900
929
  query: {
901
930
  test: 1
902
931
  }
903
- }
932
+ },
933
+ undefined
904
934
  ]
905
935
  ])
906
936
  })
@@ -977,7 +1007,8 @@ describe('LookupResolver', () => {
977
1007
  query: {
978
1008
  service: 'ls_foo'
979
1009
  }
980
- }
1010
+ },
1011
+ 1000
981
1012
  ],
982
1013
  [
983
1014
  'https://slaphost1.com',
@@ -986,7 +1017,8 @@ describe('LookupResolver', () => {
986
1017
  query: {
987
1018
  test: 1
988
1019
  }
989
- }
1020
+ },
1021
+ undefined
990
1022
  ],
991
1023
  [
992
1024
  'https://slaphost2.com',
@@ -995,7 +1027,8 @@ describe('LookupResolver', () => {
995
1027
  query: {
996
1028
  test: 1
997
1029
  }
998
- }
1030
+ },
1031
+ undefined
999
1032
  ]
1000
1033
  ])
1001
1034
  })
@@ -1063,7 +1096,8 @@ describe('LookupResolver', () => {
1063
1096
  query: {
1064
1097
  service: 'ls_foo'
1065
1098
  }
1066
- }
1099
+ },
1100
+ 1000
1067
1101
  ],
1068
1102
  [
1069
1103
  'https://slaphost1.com',
@@ -1072,7 +1106,8 @@ describe('LookupResolver', () => {
1072
1106
  query: {
1073
1107
  test: 1
1074
1108
  }
1075
- }
1109
+ },
1110
+ undefined
1076
1111
  ],
1077
1112
  [
1078
1113
  'https://slaphost2.com',
@@ -1081,7 +1116,8 @@ describe('LookupResolver', () => {
1081
1116
  query: {
1082
1117
  test: 1
1083
1118
  }
1084
- }
1119
+ },
1120
+ undefined
1085
1121
  ]
1086
1122
  ])
1087
1123
  })
@@ -1148,7 +1184,8 @@ describe('LookupResolver', () => {
1148
1184
  query: {
1149
1185
  service: 'ls_foo'
1150
1186
  }
1151
- }
1187
+ },
1188
+ 1000
1152
1189
  ],
1153
1190
  [
1154
1191
  'https://slaphost1.com',
@@ -1157,7 +1194,8 @@ describe('LookupResolver', () => {
1157
1194
  query: {
1158
1195
  test: 1
1159
1196
  }
1160
- }
1197
+ },
1198
+ undefined
1161
1199
  ],
1162
1200
  [
1163
1201
  'https://slaphost2.com',
@@ -1166,7 +1204,8 @@ describe('LookupResolver', () => {
1166
1204
  query: {
1167
1205
  test: 1
1168
1206
  }
1169
- }
1207
+ },
1208
+ undefined
1170
1209
  ]
1171
1210
  ])
1172
1211
  })
@@ -1217,7 +1256,8 @@ describe('LookupResolver', () => {
1217
1256
  query: {
1218
1257
  service: 'ls_foo'
1219
1258
  }
1220
- }
1259
+ },
1260
+ 1000
1221
1261
  ],
1222
1262
  [
1223
1263
  'https://slaphost.com',
@@ -1226,7 +1266,8 @@ describe('LookupResolver', () => {
1226
1266
  query: {
1227
1267
  test: 1
1228
1268
  }
1229
- }
1269
+ },
1270
+ undefined
1230
1271
  ]
1231
1272
  ])
1232
1273
  })
@@ -69,8 +69,8 @@ describe('SHIPCast', () => {
69
69
  service: 'ls_ship',
70
70
  query: {
71
71
  topics: ['tm_foo']
72
- }
73
- })
72
+ },
73
+ }, 1000)
74
74
 
75
75
  expect(mockFacilitator.send).toHaveBeenCalledWith('https://shiphost.com', {
76
76
  beef: testTx.toBEEF(),
@@ -231,8 +231,8 @@ describe('SHIPCast', () => {
231
231
  service: 'ls_ship',
232
232
  query: {
233
233
  topics: ['tm_foo']
234
- }
235
- })
234
+ },
235
+ }, 1000)
236
236
 
237
237
  expect(mockFacilitator.send).not.toHaveBeenCalled()
238
238
  })
@@ -396,12 +396,12 @@ describe('SHIPCast', () => {
396
396
  query: {
397
397
  topics: ['tm_foo', 'tm_bar']
398
398
  }
399
- })
399
+ }, 1000)
400
400
 
401
401
  expect(mockFacilitator.send).toHaveBeenCalledTimes(2)
402
402
  })
403
403
 
404
- it('should fail when a host does not acknowledge all topics (default behavior)', async () => {
404
+ it('should fail if at least one host does not acknowledge every topic (default behavior)', async () => {
405
405
  const shipHostKey1 = new PrivateKey(42)
406
406
  const shipWallet1 = new ProtoWallet(shipHostKey1)
407
407
  const shipLib1 = new OverlayAdminTokenTemplate(shipWallet1)
@@ -434,7 +434,7 @@ describe('SHIPCast', () => {
434
434
  const steak = {}
435
435
  for (const topic of topics) {
436
436
  steak[topic] = {
437
- outputsToAdmit: [0],
437
+ outputsToAdmit: [],
438
438
  coinsToRetain: []
439
439
  }
440
440
  }
@@ -462,8 +462,8 @@ describe('SHIPCast', () => {
462
462
 
463
463
  expect(response).toEqual({
464
464
  status: 'error',
465
- code: 'ERR_REQUIRE_ACK_FROM_ALL_HOSTS_FAILED',
466
- description: 'Not all hosts acknowledged the required topics.'
465
+ code: 'ERR_REQUIRE_ACK_FROM_ANY_HOST_FAILED',
466
+ description: 'No host acknowledged the required topics.'
467
467
  })
468
468
  })
469
469