@bsv/wallet-toolbox 1.6.15 → 1.6.17
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/docs/client.md +118 -96
- package/docs/services.md +118 -96
- package/docs/wallet.md +118 -96
- package/mobile/out/src/sdk/WalletError.js +2 -1
- package/mobile/out/src/sdk/WalletError.js.map +1 -1
- package/mobile/out/src/services/chaintracker/chaintracks/Chaintracks.d.ts +1 -0
- package/mobile/out/src/services/chaintracker/chaintracks/Chaintracks.d.ts.map +1 -1
- package/mobile/out/src/services/chaintracker/chaintracks/Chaintracks.js +140 -114
- package/mobile/out/src/services/chaintracker/chaintracks/Chaintracks.js.map +1 -1
- package/mobile/package-lock.json +2 -2
- package/mobile/package.json +1 -1
- package/out/src/sdk/WalletError.js +2 -1
- package/out/src/sdk/WalletError.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Chaintracks.d.ts +1 -0
- package/out/src/services/chaintracker/chaintracks/Chaintracks.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Chaintracks.js +140 -114
- package/out/src/services/chaintracker/chaintracks/Chaintracks.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/sdk/WalletError.ts +1 -1
- package/src/services/chaintracker/chaintracks/Chaintracks.ts +139 -114
package/package.json
CHANGED
package/src/sdk/WalletError.ts
CHANGED
|
@@ -54,7 +54,7 @@ export class WalletError extends Error implements WalletErrorObject {
|
|
|
54
54
|
*
|
|
55
55
|
*/
|
|
56
56
|
static fromUnknown(err: unknown): WalletError {
|
|
57
|
-
|
|
57
|
+
if (err instanceof WalletError) return err
|
|
58
58
|
let name = 'WERR_UNKNOWN'
|
|
59
59
|
let message = ''
|
|
60
60
|
let stack: string | undefined
|
|
@@ -14,6 +14,8 @@ import { HeightRange, HeightRanges } from './util/HeightRange'
|
|
|
14
14
|
import { SingleWriterMultiReaderLock } from './util/SingleWriterMultiReaderLock'
|
|
15
15
|
import { ChaintracksFsApi } from './Api/ChaintracksFsApi'
|
|
16
16
|
import { randomBytesBase64, wait } from '../../../utility/utilityHelpers'
|
|
17
|
+
import { WalletError } from '../../../sdk/WalletError'
|
|
18
|
+
import { CWIStyleWalletManager } from '../../../CWIStyleWalletManager'
|
|
17
19
|
|
|
18
20
|
export class Chaintracks implements ChaintracksManagementApi {
|
|
19
21
|
static createOptions(chain: Chain): ChaintracksOptions {
|
|
@@ -47,6 +49,7 @@ export class Chaintracks implements ChaintracksManagementApi {
|
|
|
47
49
|
private addLiveRecursionLimit = 11
|
|
48
50
|
|
|
49
51
|
private available = false
|
|
52
|
+
private startupError: WalletError | null = null
|
|
50
53
|
|
|
51
54
|
private subscriberCallbacksEnabled = false
|
|
52
55
|
private stopMainThread = true
|
|
@@ -169,9 +172,11 @@ export class Chaintracks implements ChaintracksManagementApi {
|
|
|
169
172
|
this.promises.push(this.mainThreadShiftLiveHeaders())
|
|
170
173
|
|
|
171
174
|
// Wait for the main thread to finish initial sync.
|
|
172
|
-
while (!this.available) {
|
|
175
|
+
while (!this.available && !this.startupError) {
|
|
173
176
|
await wait(100)
|
|
174
177
|
}
|
|
178
|
+
|
|
179
|
+
if (this.startupError) throw this.startupError
|
|
175
180
|
})
|
|
176
181
|
}
|
|
177
182
|
|
|
@@ -324,6 +329,7 @@ export class Chaintracks implements ChaintracksManagementApi {
|
|
|
324
329
|
|
|
325
330
|
let done = false
|
|
326
331
|
for (; !done; ) {
|
|
332
|
+
let bulkSyncError: WalletError | undefined
|
|
327
333
|
for (const bulk of this.bulkIngestors) {
|
|
328
334
|
try {
|
|
329
335
|
const r = await bulk.synchronize(presentHeight, before, newLiveHeaders)
|
|
@@ -340,23 +346,30 @@ export class Chaintracks implements ChaintracksManagementApi {
|
|
|
340
346
|
done = true
|
|
341
347
|
break
|
|
342
348
|
}
|
|
343
|
-
} catch (
|
|
344
|
-
|
|
349
|
+
} catch (eu: unknown) {
|
|
350
|
+
const e = (bulkSyncError = WalletError.fromUnknown(eu))
|
|
351
|
+
this.log(`bulk sync error: ${e.message}`)
|
|
345
352
|
}
|
|
346
353
|
}
|
|
354
|
+
if (!bulkDone && !this.available && bulkSyncError) {
|
|
355
|
+
this.startupError = bulkSyncError
|
|
356
|
+
break
|
|
357
|
+
}
|
|
347
358
|
if (bulkDone) break
|
|
348
359
|
}
|
|
349
360
|
|
|
350
|
-
this.
|
|
361
|
+
if (!this.startupError) {
|
|
362
|
+
this.liveHeaders.unshift(...newLiveHeaders)
|
|
351
363
|
|
|
352
|
-
|
|
364
|
+
added = after.bulk.above(initialRanges.bulk)
|
|
353
365
|
|
|
354
|
-
|
|
366
|
+
this.log(`syncBulkStorage done
|
|
355
367
|
Before sync: bulk ${initialRanges.bulk}, live ${initialRanges.live}
|
|
356
368
|
After sync: bulk ${after.bulk}, live ${after.live}
|
|
357
369
|
${added.length} headers added to bulk storage
|
|
358
370
|
${this.liveHeaders.length} headers forwarded to live header storage
|
|
359
371
|
`)
|
|
372
|
+
}
|
|
360
373
|
}
|
|
361
374
|
|
|
362
375
|
private async getMissingBlockHeader(hash: string): Promise<BlockHeader | undefined> {
|
|
@@ -432,147 +445,159 @@ export class Chaintracks implements ChaintracksManagementApi {
|
|
|
432
445
|
const syncCheckRepeatMsecs = 30 * 60 * 1000 // 30 minutes
|
|
433
446
|
|
|
434
447
|
while (!this.stopMainThread) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
448
|
+
try {
|
|
449
|
+
// Review the need for bulk sync...
|
|
450
|
+
const now = Date.now()
|
|
451
|
+
lastSyncCheck = now
|
|
438
452
|
|
|
439
|
-
|
|
440
|
-
|
|
453
|
+
const presentHeight = await this.getPresentHeight()
|
|
454
|
+
const before = await this.storage.getAvailableHeightRanges()
|
|
441
455
|
|
|
442
|
-
|
|
443
|
-
|
|
456
|
+
// Skip bulk sync if within less than half the recursion limit of present height
|
|
457
|
+
let skipBulkSync =
|
|
458
|
+
!before.live.isEmpty && before.live.maxHeight >= presentHeight - this.addLiveRecursionLimit / 2
|
|
444
459
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
460
|
+
if (skipBulkSync && now - lastSyncCheck > cdnSyncRepeatMsecs) {
|
|
461
|
+
// If we haven't re-synced in a long time, do it just to check for a CDN update.
|
|
462
|
+
skipBulkSync = false
|
|
463
|
+
}
|
|
449
464
|
|
|
450
|
-
|
|
465
|
+
this.log(`Chaintracks Update Services: Bulk Header Sync Review
|
|
451
466
|
presentHeight=${presentHeight} addLiveRecursionLimit=${this.addLiveRecursionLimit}
|
|
452
467
|
Before synchronize: bulk ${before.bulk}, live ${before.live}
|
|
453
468
|
${skipBulkSync ? 'Skipping' : 'Starting'} syncBulkStorage.
|
|
454
469
|
`)
|
|
455
470
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
471
|
+
if (!skipBulkSync) {
|
|
472
|
+
// Bring bulk storage up-to-date and (re-)initialize liveHeaders
|
|
473
|
+
lastBulkSync = now
|
|
474
|
+
if (this.available)
|
|
475
|
+
// Once available, initial write lock is released, take a new one to update bulk storage.
|
|
476
|
+
await this.syncBulkStorage(presentHeight, before)
|
|
477
|
+
else
|
|
478
|
+
// While still not available, the makeAvailable write lock is held.
|
|
479
|
+
await this.syncBulkStorageNoLock(presentHeight, before)
|
|
480
|
+
if (this.startupError) throw this.startupError
|
|
481
|
+
}
|
|
466
482
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
needSyncCheck = true
|
|
482
|
-
} else if (ihr.noPrev) {
|
|
483
|
-
// Previous header is unknown, request it by hash from the network and try adding it first...
|
|
484
|
-
if (recursions-- <= 0) {
|
|
485
|
-
// Ignore this header...
|
|
486
|
-
this.log(
|
|
487
|
-
`Ignoring liveHeader ${header.height} ${header.hash} addLiveRecursionLimit=${this.addLiveRecursionLimit} exceeded.`
|
|
488
|
-
)
|
|
483
|
+
let count = 0
|
|
484
|
+
let liveHeaderDupes = 0
|
|
485
|
+
let needSyncCheck = false
|
|
486
|
+
|
|
487
|
+
for (; !needSyncCheck && !this.stopMainThread; ) {
|
|
488
|
+
let header = this.liveHeaders.shift()
|
|
489
|
+
if (header) {
|
|
490
|
+
// Process a "live" block header...
|
|
491
|
+
let recursions = this.addLiveRecursionLimit
|
|
492
|
+
for (; !needSyncCheck && !this.stopMainThread; ) {
|
|
493
|
+
//console.log(`Processing liveHeader: height: ${header.height} hash: ${header.hash} ${new Date().toISOString()}`)
|
|
494
|
+
const ihr = await this.addLiveHeader(header)
|
|
495
|
+
if (this.invalidInsertHeaderResult(ihr)) {
|
|
496
|
+
this.log(`Ignoring liveHeader ${header.height} ${header.hash} due to invalid insert result.`)
|
|
489
497
|
needSyncCheck = true
|
|
490
|
-
} else {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
498
|
+
} else if (ihr.noPrev) {
|
|
499
|
+
// Previous header is unknown, request it by hash from the network and try adding it first...
|
|
500
|
+
if (recursions-- <= 0) {
|
|
501
|
+
// Ignore this header...
|
|
494
502
|
this.log(
|
|
495
|
-
`Ignoring liveHeader ${header.height} ${header.hash}
|
|
503
|
+
`Ignoring liveHeader ${header.height} ${header.hash} addLiveRecursionLimit=${this.addLiveRecursionLimit} exceeded.`
|
|
496
504
|
)
|
|
497
505
|
needSyncCheck = true
|
|
498
506
|
} else {
|
|
499
|
-
|
|
500
|
-
this.
|
|
501
|
-
|
|
507
|
+
const hash = header.previousHash
|
|
508
|
+
const prevHeader = await this.getMissingBlockHeader(hash)
|
|
509
|
+
if (!prevHeader) {
|
|
510
|
+
this.log(
|
|
511
|
+
`Ignoring liveHeader ${header.height} ${header.hash} failed to find previous header by hash ${asString(hash)}`
|
|
512
|
+
)
|
|
513
|
+
needSyncCheck = true
|
|
514
|
+
} else {
|
|
515
|
+
// Switch to trying to add prevHeader, unshifting current header to try it again after prevHeader exists.
|
|
516
|
+
this.liveHeaders.unshift(header)
|
|
517
|
+
header = prevHeader
|
|
518
|
+
}
|
|
502
519
|
}
|
|
503
|
-
}
|
|
504
|
-
} else {
|
|
505
|
-
if (this.subscriberCallbacksEnabled)
|
|
506
|
-
this.log(
|
|
507
|
-
`addLiveHeader ${header.height}${ihr.added ? ' added' : ''}${ihr.dupe ? ' dupe' : ''}${ihr.isActiveTip ? ' isActiveTip' : ''}${ihr.reorgDepth ? ' reorg depth ' + ihr.reorgDepth : ''}${ihr.noPrev ? ' noPrev' : ''}${ihr.noActiveAncestor || ihr.noTip || ihr.badPrev ? ' error' : ''}`
|
|
508
|
-
)
|
|
509
|
-
if (ihr.dupe) {
|
|
510
|
-
liveHeaderDupes++
|
|
511
|
-
}
|
|
512
|
-
// Header wasn't invalid and previous header is known. If it was successfully added, count it as a win.
|
|
513
|
-
if (ihr.added) {
|
|
514
|
-
count++
|
|
515
|
-
}
|
|
516
|
-
break
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
} else {
|
|
520
|
-
// There are no liveHeaders currently to process, check the out-of-band baseHeaders channel (`addHeader` method called by a client).
|
|
521
|
-
const bheader = this.baseHeaders.shift()
|
|
522
|
-
if (bheader) {
|
|
523
|
-
const prev = await this.storage.findLiveHeaderForBlockHash(bheader.previousHash)
|
|
524
|
-
if (!prev) {
|
|
525
|
-
// Ignoring attempt to add a baseHeader with unknown previous hash, no attempt made to find previous header(s).
|
|
526
|
-
this.log(`Ignoring header with unknown previousHash ${bheader.previousHash} in live storage.`)
|
|
527
|
-
// Does not trigger a re-sync.
|
|
528
|
-
} else {
|
|
529
|
-
const header: BlockHeader = {
|
|
530
|
-
...bheader,
|
|
531
|
-
height: prev.height + 1,
|
|
532
|
-
hash: blockHash(bheader)
|
|
533
|
-
}
|
|
534
|
-
const ihr = await this.addLiveHeader(header)
|
|
535
|
-
if (this.invalidInsertHeaderResult(ihr)) {
|
|
536
|
-
this.log(`Ignoring invalid baseHeader ${header.height} ${header.hash}.`)
|
|
537
520
|
} else {
|
|
538
521
|
if (this.subscriberCallbacksEnabled)
|
|
539
522
|
this.log(
|
|
540
|
-
`
|
|
523
|
+
`addLiveHeader ${header.height}${ihr.added ? ' added' : ''}${ihr.dupe ? ' dupe' : ''}${ihr.isActiveTip ? ' isActiveTip' : ''}${ihr.reorgDepth ? ' reorg depth ' + ihr.reorgDepth : ''}${ihr.noPrev ? ' noPrev' : ''}${ihr.noActiveAncestor || ihr.noTip || ihr.badPrev ? ' error' : ''}`
|
|
541
524
|
)
|
|
542
|
-
|
|
525
|
+
if (ihr.dupe) {
|
|
526
|
+
liveHeaderDupes++
|
|
527
|
+
}
|
|
528
|
+
// Header wasn't invalid and previous header is known. If it was successfully added, count it as a win.
|
|
543
529
|
if (ihr.added) {
|
|
544
530
|
count++
|
|
545
531
|
}
|
|
532
|
+
break
|
|
546
533
|
}
|
|
547
534
|
}
|
|
548
535
|
} else {
|
|
549
|
-
// There are no liveHeaders
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
536
|
+
// There are no liveHeaders currently to process, check the out-of-band baseHeaders channel (`addHeader` method called by a client).
|
|
537
|
+
const bheader = this.baseHeaders.shift()
|
|
538
|
+
if (bheader) {
|
|
539
|
+
const prev = await this.storage.findLiveHeaderForBlockHash(bheader.previousHash)
|
|
540
|
+
if (!prev) {
|
|
541
|
+
// Ignoring attempt to add a baseHeader with unknown previous hash, no attempt made to find previous header(s).
|
|
542
|
+
this.log(`Ignoring header with unknown previousHash ${bheader.previousHash} in live storage.`)
|
|
543
|
+
// Does not trigger a re-sync.
|
|
544
|
+
} else {
|
|
545
|
+
const header: BlockHeader = {
|
|
546
|
+
...bheader,
|
|
547
|
+
height: prev.height + 1,
|
|
548
|
+
hash: blockHash(bheader)
|
|
549
|
+
}
|
|
550
|
+
const ihr = await this.addLiveHeader(header)
|
|
551
|
+
if (this.invalidInsertHeaderResult(ihr)) {
|
|
552
|
+
this.log(`Ignoring invalid baseHeader ${header.height} ${header.hash}.`)
|
|
553
|
+
} else {
|
|
554
|
+
if (this.subscriberCallbacksEnabled)
|
|
555
|
+
this.log(
|
|
556
|
+
`addBaseHeader ${header.height}${ihr.added ? ' added' : ''}${ihr.dupe ? ' dupe' : ''}${ihr.isActiveTip ? ' isActiveTip' : ''}${ihr.reorgDepth ? ' reorg depth ' + ihr.reorgDepth : ''}${ihr.noPrev ? ' noPrev' : ''}${ihr.noActiveAncestor || ihr.noTip || ihr.badPrev ? ' error' : ''}`
|
|
557
|
+
)
|
|
558
|
+
// baseHeader was successfully added.
|
|
559
|
+
if (ihr.added) {
|
|
560
|
+
count++
|
|
561
|
+
}
|
|
562
|
+
}
|
|
554
563
|
}
|
|
555
|
-
|
|
556
|
-
|
|
564
|
+
} else {
|
|
565
|
+
// There are no liveHeaders and no baseHeaders to add,
|
|
566
|
+
if (count > 0) {
|
|
567
|
+
if (liveHeaderDupes > 0) {
|
|
568
|
+
this.log(`${liveHeaderDupes} duplicate headers ignored.`)
|
|
569
|
+
liveHeaderDupes = 0
|
|
570
|
+
}
|
|
571
|
+
const updated = await this.storage.getAvailableHeightRanges()
|
|
572
|
+
this.log(`After adding ${count} live headers
|
|
557
573
|
After live: bulk ${updated.bulk}, live ${updated.live}
|
|
558
574
|
`)
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
if (!this.subscriberCallbacksEnabled) {
|
|
562
|
-
const live = await this.storage.findLiveHeightRange()
|
|
563
|
-
if (!live.isEmpty) {
|
|
564
|
-
this.subscriberCallbacksEnabled = true
|
|
565
|
-
this.log(`listening at height of ${live.maxHeight}`)
|
|
575
|
+
count = 0
|
|
566
576
|
}
|
|
577
|
+
if (!this.subscriberCallbacksEnabled) {
|
|
578
|
+
const live = await this.storage.findLiveHeightRange()
|
|
579
|
+
if (!live.isEmpty) {
|
|
580
|
+
this.subscriberCallbacksEnabled = true
|
|
581
|
+
this.log(`listening at height of ${live.maxHeight}`)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (!this.available) {
|
|
585
|
+
this.available = true
|
|
586
|
+
}
|
|
587
|
+
needSyncCheck = Date.now() - lastSyncCheck > syncCheckRepeatMsecs
|
|
588
|
+
// If we aren't going to review sync, wait before checking input queues again
|
|
589
|
+
if (!needSyncCheck) await wait(1000)
|
|
567
590
|
}
|
|
568
|
-
if (!this.available) {
|
|
569
|
-
this.available = true
|
|
570
|
-
}
|
|
571
|
-
needSyncCheck = Date.now() - lastSyncCheck > syncCheckRepeatMsecs
|
|
572
|
-
// If we aren't going to review sync, wait before checking input queues again
|
|
573
|
-
if (!needSyncCheck) await wait(1000)
|
|
574
591
|
}
|
|
575
592
|
}
|
|
593
|
+
} catch (eu: unknown) {
|
|
594
|
+
const e = WalletError.fromUnknown(eu)
|
|
595
|
+
if (!this.available) {
|
|
596
|
+
this.startupError = e
|
|
597
|
+
this.stopMainThread = true
|
|
598
|
+
} else {
|
|
599
|
+
this.log(`Error occurred during chaintracks main thread processing: ${e.stack || e.message}`)
|
|
600
|
+
}
|
|
576
601
|
}
|
|
577
602
|
}
|
|
578
603
|
}
|