@bsv/sdk 2.1.1 → 2.1.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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/identity/ContactsManager.js +44 -6
- package/dist/cjs/src/identity/ContactsManager.js.map +1 -1
- package/dist/cjs/src/identity/IdentityClient.js +106 -37
- package/dist/cjs/src/identity/IdentityClient.js.map +1 -1
- package/dist/cjs/src/overlay-tools/LookupResolver.js +85 -58
- package/dist/cjs/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/cjs/src/primitives/Hash.js +173 -50
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/SymmetricKey.js +123 -1
- package/dist/cjs/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/identity/ContactsManager.js +44 -6
- package/dist/esm/src/identity/ContactsManager.js.map +1 -1
- package/dist/esm/src/identity/IdentityClient.js +106 -37
- package/dist/esm/src/identity/IdentityClient.js.map +1 -1
- package/dist/esm/src/overlay-tools/LookupResolver.js +85 -58
- package/dist/esm/src/overlay-tools/LookupResolver.js.map +1 -1
- package/dist/esm/src/primitives/Hash.js +177 -50
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/SymmetricKey.js +123 -1
- package/dist/esm/src/primitives/SymmetricKey.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/identity/ContactsManager.d.ts +13 -2
- package/dist/types/src/identity/ContactsManager.d.ts.map +1 -1
- package/dist/types/src/identity/IdentityClient.d.ts +50 -24
- package/dist/types/src/identity/IdentityClient.d.ts.map +1 -1
- package/dist/types/src/overlay-tools/LookupResolver.d.ts +14 -1
- package/dist/types/src/overlay-tools/LookupResolver.d.ts.map +1 -1
- package/dist/types/src/primitives/Hash.d.ts +21 -16
- package/dist/types/src/primitives/Hash.d.ts.map +1 -1
- package/dist/types/src/primitives/SymmetricKey.d.ts.map +1 -1
- package/dist/types/src/wallet/Wallet.interfaces.d.ts +16 -1
- package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
- package/dist/types/src/wallet/WalletClient.d.ts +1 -1
- package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
- package/dist/types/src/wallet/substrates/window.CWI.d.ts +1 -1
- package/dist/types/src/wallet/substrates/window.CWI.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/package.json +1 -1
- package/src/identity/ContactsManager.ts +47 -6
- package/src/identity/IdentityClient.ts +137 -53
- package/src/identity/__tests/IdentityClient.additional.test.ts +150 -1
- package/src/identity/__tests/IdentityClient.test.ts +4 -4
- package/src/overlay-tools/LookupResolver.ts +94 -57
- package/src/primitives/Hash.ts +232 -96
- package/src/primitives/SymmetricKey.ts +145 -1
- package/src/primitives/__tests/Hash.additional.test.ts +65 -0
- package/src/primitives/__tests/Hash.test.ts +6 -1
- package/src/wallet/Wallet.interfaces.ts +16 -1
- package/src/wallet/WalletClient.ts +1 -1
- package/src/wallet/substrates/window.CWI.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Transaction } from '../transaction/index.js'
|
|
2
|
+
import { Beef } from '../transaction/Beef.js'
|
|
2
3
|
import OverlayAdminTokenTemplate from './OverlayAdminTokenTemplate.js'
|
|
3
4
|
import * as Utils from '../primitives/utils.js'
|
|
4
5
|
import { getOverlayHostReputationTracker, HostReputationTracker } from './HostReputationTracker.js'
|
|
@@ -35,6 +36,8 @@ export type LookupAnswer =
|
|
|
35
36
|
beef: number[]
|
|
36
37
|
outputIndex: number
|
|
37
38
|
context?: number[]
|
|
39
|
+
/** Optional txid hint. When present, consumers can skip re-parsing beef to derive the txid. */
|
|
40
|
+
txid?: string
|
|
38
41
|
}>
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -65,7 +68,7 @@ export interface LookupQueryOptions {
|
|
|
65
68
|
*/
|
|
66
69
|
export interface LookupAnswerProgress {
|
|
67
70
|
type: 'output-list'
|
|
68
|
-
outputs: Array<{ beef: number[], outputIndex: number, context?: number[] }>
|
|
71
|
+
outputs: Array<{ beef: number[], outputIndex: number, context?: number[], txid?: string }>
|
|
69
72
|
/** Parallel array of resolved tx ids for each output (same index as `outputs`). */
|
|
70
73
|
txIds: string[]
|
|
71
74
|
/** True only for the final emission, after every in-flight host has settled. */
|
|
@@ -196,44 +199,61 @@ export class HTTPSOverlayLookupFacilitator implements OverlayLookupFacilitator {
|
|
|
196
199
|
|
|
197
200
|
if (!response.ok) throw new Error(`Failed to facilitate lookup (HTTP ${response.status})`)
|
|
198
201
|
if (response.headers.get('content-type') === 'application/octet-stream') {
|
|
199
|
-
|
|
200
|
-
const r = new Utils.Reader([...new Uint8Array(payload)])
|
|
201
|
-
const nOutpoints = r.readVarIntNum()
|
|
202
|
-
const outpoints: Array<{ txid: string, outputIndex: number, context?: number[] }> = []
|
|
203
|
-
for (let i = 0; i < nOutpoints; i++) {
|
|
204
|
-
const txid = Utils.toHex(r.read(32))
|
|
205
|
-
const outputIndex = r.readVarIntNum()
|
|
206
|
-
const contextLength = r.readVarIntNum()
|
|
207
|
-
let context
|
|
208
|
-
if (contextLength > 0) {
|
|
209
|
-
context = r.read(contextLength)
|
|
210
|
-
}
|
|
211
|
-
outpoints.push({
|
|
212
|
-
txid,
|
|
213
|
-
outputIndex,
|
|
214
|
-
context
|
|
215
|
-
})
|
|
216
|
-
}
|
|
217
|
-
const beef = r.read()
|
|
218
|
-
return {
|
|
219
|
-
type: 'output-list',
|
|
220
|
-
outputs: outpoints.map(x => ({
|
|
221
|
-
outputIndex: x.outputIndex,
|
|
222
|
-
context: x.context,
|
|
223
|
-
beef: Transaction.fromBEEF(beef, x.txid).toBEEF()
|
|
224
|
-
}))
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
return await response.json()
|
|
202
|
+
return await this.parseOctetStreamLookup(response)
|
|
228
203
|
}
|
|
204
|
+
return await response.json()
|
|
229
205
|
} catch (e) {
|
|
230
206
|
// Normalize timeouts to a consistent error message
|
|
231
|
-
if ((e as { name?: string })?.name === 'AbortError')
|
|
207
|
+
if ((e as { name?: string })?.name === 'AbortError') {
|
|
208
|
+
throw new Error('Request timed out')
|
|
209
|
+
}
|
|
232
210
|
throw e
|
|
233
211
|
} finally {
|
|
234
212
|
clearTimeout(timer)
|
|
235
213
|
}
|
|
236
214
|
}
|
|
215
|
+
|
|
216
|
+
/** Parse the aggregated octet-stream lookup response into an output-list LookupAnswer. */
|
|
217
|
+
private async parseOctetStreamLookup (response: Response): Promise<LookupAnswer> {
|
|
218
|
+
const payload = await response.arrayBuffer()
|
|
219
|
+
const r = new Utils.Reader([...new Uint8Array(payload)])
|
|
220
|
+
const nOutpoints = r.readVarIntNum()
|
|
221
|
+
const outpoints: Array<{ txid: string, outputIndex: number, context?: number[] }> = []
|
|
222
|
+
for (let i = 0; i < nOutpoints; i++) {
|
|
223
|
+
const txid = Utils.toHex(r.read(32))
|
|
224
|
+
const outputIndex = r.readVarIntNum()
|
|
225
|
+
const contextLength = r.readVarIntNum()
|
|
226
|
+
const context = contextLength > 0 ? r.read(contextLength) : undefined
|
|
227
|
+
outpoints.push({ txid, outputIndex, context })
|
|
228
|
+
}
|
|
229
|
+
const beef = r.read()
|
|
230
|
+
const beefObj = Beef.fromBinary(beef)
|
|
231
|
+
const outputs = await this.extractAtomicOutputs(outpoints, beefObj)
|
|
232
|
+
return { type: 'output-list', outputs }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Memoize per-txid atomic BEEF extraction, yielding to the event loop between outputs. */
|
|
236
|
+
private async extractAtomicOutputs (
|
|
237
|
+
outpoints: Array<{ txid: string, outputIndex: number, context?: number[] }>,
|
|
238
|
+
beefObj: Beef
|
|
239
|
+
): Promise<Array<{ outputIndex: number, context?: number[], beef: number[], txid: string }>> {
|
|
240
|
+
const beefByTxid = new Map<string, number[]>()
|
|
241
|
+
const outputs: Array<{ outputIndex: number, context?: number[], beef: number[], txid: string }> = new Array(outpoints.length)
|
|
242
|
+
for (let idx = 0; idx < outpoints.length; idx++) {
|
|
243
|
+
const x = outpoints[idx]
|
|
244
|
+
let beefBytes = beefByTxid.get(x.txid)
|
|
245
|
+
if (beefBytes === undefined) {
|
|
246
|
+
beefBytes = beefObj.toBinaryAtomic(x.txid)
|
|
247
|
+
beefByTxid.set(x.txid, beefBytes)
|
|
248
|
+
}
|
|
249
|
+
outputs[idx] = { outputIndex: x.outputIndex, context: x.context, beef: beefBytes, txid: x.txid }
|
|
250
|
+
// Yield to event loop so UI animations and other JS don't starve.
|
|
251
|
+
if (idx > 0 && idx < outpoints.length - 1) {
|
|
252
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return outputs
|
|
256
|
+
}
|
|
237
257
|
}
|
|
238
258
|
|
|
239
259
|
/**
|
|
@@ -296,13 +316,18 @@ export default class LookupResolver {
|
|
|
296
316
|
timeout?: number,
|
|
297
317
|
options?: LookupQueryOptions
|
|
298
318
|
): Promise<LookupAnswer> {
|
|
299
|
-
let last: LookupAnswerProgress | null = null
|
|
300
319
|
// Existing fast-but-narrow contract: return at the first cumulative emission
|
|
301
320
|
// (the post-grace aggregate, or the final emission when every host settles
|
|
302
321
|
// before the grace window). Callers wanting progressive enrichment use query$().
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
322
|
+
// Take only the first emission, then explicitly close the iterator so the
|
|
323
|
+
// generator's `finally` block runs and clears any outstanding timers.
|
|
324
|
+
const iter = this.query$(question, timeout, options)[Symbol.asyncIterator]()
|
|
325
|
+
let last: LookupAnswerProgress | null = null
|
|
326
|
+
try {
|
|
327
|
+
const { value, done } = await iter.next()
|
|
328
|
+
if (done !== true && value != null) last = value
|
|
329
|
+
} finally {
|
|
330
|
+
await iter.return?.(undefined)
|
|
306
331
|
}
|
|
307
332
|
return {
|
|
308
333
|
type: 'output-list',
|
|
@@ -401,31 +426,16 @@ export default class LookupResolver {
|
|
|
401
426
|
let graceFired = false
|
|
402
427
|
let emittedOnce = false
|
|
403
428
|
|
|
404
|
-
const beefKey = (beef: number[] | undefined): string => {
|
|
405
|
-
if (typeof beef !== 'object' || beef == null) return ''
|
|
406
|
-
return beef.join(',')
|
|
407
|
-
}
|
|
408
|
-
|
|
409
429
|
const mergeAnswer = (answer: LookupAnswer): boolean => {
|
|
410
430
|
let added = false
|
|
431
|
+
const now = Date.now()
|
|
411
432
|
for (const output of answer.outputs) {
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
if (typeof memo !== 'object' || memo === null || memo.expiresAt <= now) {
|
|
416
|
-
try {
|
|
417
|
-
const txId = Transaction.fromBEEF(output.beef).id('hex')
|
|
418
|
-
memo = { txId, expiresAt: now + this.txMemoTtlMs }
|
|
419
|
-
if (this.txMemo.size > 4096) this.evictOldest(this.txMemo)
|
|
420
|
-
this.txMemo.set(keyForBeef, memo)
|
|
421
|
-
} catch {
|
|
422
|
-
continue
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
const uniqKey = `${memo.txId}.${output.outputIndex}`
|
|
433
|
+
const txId = this.resolveTxIdForOutput(output, now)
|
|
434
|
+
if (txId === null) continue
|
|
435
|
+
const uniqKey = `${txId}.${output.outputIndex}`
|
|
426
436
|
if (!outputsMap.has(uniqKey)) {
|
|
427
437
|
outputsMap.set(uniqKey, output)
|
|
428
|
-
txIds.push(
|
|
438
|
+
txIds.push(txId)
|
|
429
439
|
added = true
|
|
430
440
|
}
|
|
431
441
|
}
|
|
@@ -603,7 +613,7 @@ export default class LookupResolver {
|
|
|
603
613
|
resolve([...allHosts])
|
|
604
614
|
}
|
|
605
615
|
})
|
|
606
|
-
.catch(() => { /* tracker
|
|
616
|
+
.catch(() => { /* tracker failure tracked in reputation */ })
|
|
607
617
|
.finally(() => {
|
|
608
618
|
pending--
|
|
609
619
|
if (pending === 0 && !resolved) {
|
|
@@ -615,7 +625,34 @@ export default class LookupResolver {
|
|
|
615
625
|
})
|
|
616
626
|
}
|
|
617
627
|
|
|
618
|
-
/**
|
|
628
|
+
/**
|
|
629
|
+
* Resolve a txid for an aggregated lookup output. Uses the threaded-through `output.txid`
|
|
630
|
+
* fast path when present; otherwise memoizes Transaction.fromBEEF(beef).id('hex') keyed by
|
|
631
|
+
* the BEEF byte sequence. Returns null when the BEEF is unparseable.
|
|
632
|
+
*/
|
|
633
|
+
private resolveTxIdForOutput (
|
|
634
|
+
output: { txid?: string, beef: number[], outputIndex: number, context?: number[] },
|
|
635
|
+
now: number
|
|
636
|
+
): string | null {
|
|
637
|
+
if (typeof output.txid === 'string' && output.txid.length > 0) {
|
|
638
|
+
return output.txid
|
|
639
|
+
}
|
|
640
|
+
const keyForBeef = Array.isArray(output.beef) ? output.beef.join(',') : ''
|
|
641
|
+
const memo = this.txMemo.get(keyForBeef)
|
|
642
|
+
if (typeof memo === 'object' && memo !== null && memo.expiresAt > now) {
|
|
643
|
+
return memo.txId
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
const txId = Transaction.fromBEEF(output.beef).id('hex')
|
|
647
|
+
if (this.txMemo.size > 4096) this.evictOldest(this.txMemo)
|
|
648
|
+
this.txMemo.set(keyForBeef, { txId, expiresAt: now + this.txMemoTtlMs })
|
|
649
|
+
return txId
|
|
650
|
+
} catch {
|
|
651
|
+
return null
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/** Evict an arbitrary "oldest" entry from a Map (iteration order). */
|
|
619
656
|
private evictOldest<T>(m: Map<string, T>): void {
|
|
620
657
|
const firstKey = m.keys().next().value
|
|
621
658
|
if (firstKey !== undefined) m.delete(firstKey)
|