@bsv/wallet-toolbox 1.7.18 → 1.7.20
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 +132 -57
- package/docs/services.md +13 -1
- package/docs/storage.md +13 -1
- package/docs/wallet.md +132 -57
- package/out/src/WalletPermissionsManager.d.ts +48 -1
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +775 -124
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.js +9 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.flows.test.js +121 -0
- package/out/src/__tests/WalletPermissionsManager.flows.test.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.d.ts +1 -1
- package/out/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.js +3 -1
- package/out/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.js.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.d.ts.map +1 -1
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js +12 -0
- package/out/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.js.map +1 -1
- package/out/src/storage/StorageProvider.d.ts +16 -2
- package/out/src/storage/StorageProvider.d.ts.map +1 -1
- package/out/src/storage/StorageProvider.js +33 -4
- package/out/src/storage/StorageProvider.js.map +1 -1
- package/out/src/storage/methods/__test/offsetKey.test.js +266 -100
- package/out/src/storage/methods/__test/offsetKey.test.js.map +1 -1
- package/out/src/storage/methods/getBeefForTransaction.js +1 -1
- package/out/src/storage/methods/getBeefForTransaction.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/WalletPermissionsManager.ts +973 -129
- package/src/__tests/WalletPermissionsManager.fixtures.ts +9 -0
- package/src/__tests/WalletPermissionsManager.flows.test.ts +157 -0
- package/src/services/chaintracker/chaintracks/Ingest/BulkIngestorWhatsOnChainCdn.ts +1 -1
- package/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.ts +2 -1
- package/src/services/chaintracker/chaintracks/util/validBulkHeaderFilesByFileHash.ts +12 -0
- package/src/storage/StorageProvider.ts +38 -5
- package/src/storage/methods/__test/offsetKey.test.ts +299 -103
- package/src/storage/methods/getBeefForTransaction.ts +3 -1
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
const { Validation } = jest.requireActual('@bsv/sdk')
|
|
2
2
|
|
|
3
|
+
const existingFetch = (globalThis as any).fetch
|
|
4
|
+
if (!existingFetch || !(existingFetch as any)._isMockFunction) {
|
|
5
|
+
;(globalThis as any).fetch = jest.fn(async () => ({
|
|
6
|
+
ok: false,
|
|
7
|
+
status: 404,
|
|
8
|
+
json: async () => ({})
|
|
9
|
+
}))
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
13
|
* A permissions manager testing mock/stub file for:
|
|
5
14
|
* 1) The `@bsv/sdk` library: Transaction, LockingScript, PushDrop, Utils, Random, etc.
|
|
@@ -35,6 +35,163 @@ describe('WalletPermissionsManager - Permission Request Flow & Active Requests',
|
|
|
35
35
|
* UNIT TESTS
|
|
36
36
|
*/
|
|
37
37
|
describe('Unit Tests: requestPermissionFlow & activeRequests map', () => {
|
|
38
|
+
it('should coalesce level-2 protocol requests for the same counterparty into a single grouped permission prompt based on manifest.json', async () => {
|
|
39
|
+
mockNoTokensFound(manager)
|
|
40
|
+
|
|
41
|
+
jest.spyOn(manager as any, 'fetchManifestGroupPermissions').mockResolvedValue({
|
|
42
|
+
protocolPermissions: [
|
|
43
|
+
{
|
|
44
|
+
protocolID: [2, 'l2-proto-A'],
|
|
45
|
+
counterparty: '',
|
|
46
|
+
description: 'A'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
protocolID: [2, 'l2-proto-B'],
|
|
50
|
+
counterparty: 'peer-123',
|
|
51
|
+
description: 'B'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
protocolID: [2, 'l2-proto-C'],
|
|
55
|
+
counterparty: 'peer-999',
|
|
56
|
+
description: 'C'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
protocolID: [1, 'l1-proto-D'],
|
|
60
|
+
counterparty: 'peer-123',
|
|
61
|
+
description: 'D'
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const groupRequestCallback = jest.fn(() => {})
|
|
67
|
+
manager.bindCallback('onGroupedPermissionRequested', groupRequestCallback)
|
|
68
|
+
|
|
69
|
+
const callA = manager.ensureProtocolPermission({
|
|
70
|
+
originator: 'example.com',
|
|
71
|
+
privileged: false,
|
|
72
|
+
protocolID: [2, 'l2-proto-A'],
|
|
73
|
+
counterparty: 'peer-123',
|
|
74
|
+
reason: 'UnitTest - L2 A',
|
|
75
|
+
seekPermission: true,
|
|
76
|
+
usageType: 'signing'
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const callB = manager.ensureProtocolPermission({
|
|
80
|
+
originator: 'example.com',
|
|
81
|
+
privileged: false,
|
|
82
|
+
protocolID: [2, 'l2-proto-B'],
|
|
83
|
+
counterparty: 'peer-123',
|
|
84
|
+
reason: 'UnitTest - L2 B',
|
|
85
|
+
seekPermission: true,
|
|
86
|
+
usageType: 'signing'
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await new Promise(res => setTimeout(res, 5))
|
|
90
|
+
|
|
91
|
+
expect(groupRequestCallback).toHaveBeenCalledTimes(1)
|
|
92
|
+
const callbackArg = (groupRequestCallback.mock as any).calls[0][0]
|
|
93
|
+
const requestID = callbackArg.requestID
|
|
94
|
+
expect(typeof requestID).toBe('string')
|
|
95
|
+
expect(requestID).toMatch(/^group-peer:/)
|
|
96
|
+
|
|
97
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
98
|
+
const queued = activeRequests.get(requestID)
|
|
99
|
+
expect(queued.request.permissions.protocolPermissions.length).toBe(2)
|
|
100
|
+
expect(queued.request.permissions.protocolPermissions).toEqual(
|
|
101
|
+
expect.arrayContaining([
|
|
102
|
+
expect.objectContaining({ protocolID: [2, 'l2-proto-A'], counterparty: 'peer-123' }),
|
|
103
|
+
expect.objectContaining({ protocolID: [2, 'l2-proto-B'], counterparty: 'peer-123' })
|
|
104
|
+
])
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
await manager.denyGroupedPermission(requestID)
|
|
108
|
+
|
|
109
|
+
await expect(callA).rejects.toThrow(/denied/i)
|
|
110
|
+
await expect(callB).rejects.toThrow(/denied/i)
|
|
111
|
+
|
|
112
|
+
expect(activeRequests.size).toBe(0)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should create separate grouped permission requests for different peers (no cross-peer grouping)', async () => {
|
|
116
|
+
mockNoTokensFound(manager)
|
|
117
|
+
|
|
118
|
+
jest.spyOn(manager as any, 'fetchManifestGroupPermissions').mockResolvedValue({
|
|
119
|
+
protocolPermissions: [
|
|
120
|
+
{
|
|
121
|
+
protocolID: [2, 'l2-proto-B'],
|
|
122
|
+
counterparty: 'peer-123',
|
|
123
|
+
description: 'B'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
protocolID: [2, 'l2-proto-C'],
|
|
127
|
+
counterparty: 'peer-999',
|
|
128
|
+
description: 'C'
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const groupRequestCallback = jest.fn(() => {})
|
|
134
|
+
manager.bindCallback('onGroupedPermissionRequested', groupRequestCallback)
|
|
135
|
+
|
|
136
|
+
const callB = manager.ensureProtocolPermission({
|
|
137
|
+
originator: 'example.com',
|
|
138
|
+
privileged: false,
|
|
139
|
+
protocolID: [2, 'l2-proto-B'],
|
|
140
|
+
counterparty: 'peer-123',
|
|
141
|
+
reason: 'UnitTest - L2 B peer-123',
|
|
142
|
+
seekPermission: true,
|
|
143
|
+
usageType: 'signing'
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const callC = manager.ensureProtocolPermission({
|
|
147
|
+
originator: 'example.com',
|
|
148
|
+
privileged: false,
|
|
149
|
+
protocolID: [2, 'l2-proto-C'],
|
|
150
|
+
counterparty: 'peer-999',
|
|
151
|
+
reason: 'UnitTest - L2 C peer-999',
|
|
152
|
+
seekPermission: true,
|
|
153
|
+
usageType: 'signing'
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
await new Promise(res => setTimeout(res, 5))
|
|
157
|
+
|
|
158
|
+
expect(groupRequestCallback).toHaveBeenCalledTimes(2)
|
|
159
|
+
const requestID1 = (groupRequestCallback.mock as any).calls[0][0].requestID
|
|
160
|
+
const requestID2 = (groupRequestCallback.mock as any).calls[1][0].requestID
|
|
161
|
+
|
|
162
|
+
expect(requestID1).not.toBe(requestID2)
|
|
163
|
+
expect(requestID1).toMatch(/^group-peer:/)
|
|
164
|
+
expect(requestID2).toMatch(/^group-peer:/)
|
|
165
|
+
|
|
166
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
167
|
+
expect(activeRequests.size).toBe(2)
|
|
168
|
+
|
|
169
|
+
const queued1 = activeRequests.get(requestID1)
|
|
170
|
+
const queued2 = activeRequests.get(requestID2)
|
|
171
|
+
|
|
172
|
+
expect(queued1.request.permissions.protocolPermissions).toEqual(
|
|
173
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-B'], counterparty: 'peer-123' })])
|
|
174
|
+
)
|
|
175
|
+
expect(queued1.request.permissions.protocolPermissions).not.toEqual(
|
|
176
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-C'], counterparty: 'peer-999' })])
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
expect(queued2.request.permissions.protocolPermissions).toEqual(
|
|
180
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-C'], counterparty: 'peer-999' })])
|
|
181
|
+
)
|
|
182
|
+
expect(queued2.request.permissions.protocolPermissions).not.toEqual(
|
|
183
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-B'], counterparty: 'peer-123' })])
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
await manager.denyGroupedPermission(requestID1)
|
|
187
|
+
await manager.denyGroupedPermission(requestID2)
|
|
188
|
+
|
|
189
|
+
await expect(callB).rejects.toThrow(/denied/i)
|
|
190
|
+
await expect(callC).rejects.toThrow(/denied/i)
|
|
191
|
+
|
|
192
|
+
expect(activeRequests.size).toBe(0)
|
|
193
|
+
})
|
|
194
|
+
|
|
38
195
|
it('should coalesce parallel requests for the same resource into a single user prompt', async () => {
|
|
39
196
|
// We want to test the underlying private method "requestPermissionFlow" indirectly
|
|
40
197
|
// or we can test it via a public method that calls it. We'll do so via ensureProtocolPermission.
|
|
@@ -11,7 +11,7 @@ import { WhatsOnChainServices, WhatsOnChainServicesOptions } from './WhatsOnChai
|
|
|
11
11
|
|
|
12
12
|
export interface BulkIngestorWhatsOnChainOptions extends BulkIngestorBaseOptions, WhatsOnChainServicesOptions {
|
|
13
13
|
/**
|
|
14
|
-
* Maximum
|
|
14
|
+
* Maximum msecs of "normal" pause with no new data arriving.
|
|
15
15
|
*/
|
|
16
16
|
idleWait: number | undefined
|
|
17
17
|
/**
|
|
@@ -428,5 +428,17 @@ export const validBulkHeaderFiles: BulkHeaderFileInfo[] = [
|
|
|
428
428
|
prevHash: '00000000000000000e7dcc27c06ee353bd37260b2e7e664314c204f0324a5087',
|
|
429
429
|
sourceUrl: 'https://cdn.projectbabbage.com/blockheaders',
|
|
430
430
|
validated: true
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
chain: 'main',
|
|
434
|
+
count: 31772,
|
|
435
|
+
fileHash: 'NuVsRUrI5QnjILbYy4LS3A/Udl6PH/m8Y9uVguEsekM=',
|
|
436
|
+
fileName: 'mainNet_9.headers',
|
|
437
|
+
firstHeight: 900000,
|
|
438
|
+
lastChainWork: '0000000000000000000000000000000000000000016ab16bb9b31430588788d3',
|
|
439
|
+
lastHash: '0000000000000000024a2f1caef4b0ffdc1a036b09f9ed7f48b538f619f32ef2',
|
|
440
|
+
prevChainWork: '000000000000000000000000000000000000000001664db1f2d50327928007e0',
|
|
441
|
+
prevHash: '00000000000000000e7dcc27c06ee353bd37260b2e7e664314c204f0324a5087',
|
|
442
|
+
sourceUrl: 'https://cdn.projectbabbage.com/blockheaders'
|
|
431
443
|
}
|
|
432
444
|
]
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
RelinquishOutputArgs,
|
|
13
13
|
AbortActionArgs,
|
|
14
14
|
Validation,
|
|
15
|
-
WalletLoggerInterface
|
|
15
|
+
WalletLoggerInterface,
|
|
16
|
+
ChainTracker
|
|
16
17
|
} from '@bsv/sdk'
|
|
17
18
|
import { getBeefForTransaction } from './methods/getBeefForTransaction'
|
|
18
19
|
import { GetReqsAndBeefDetail, GetReqsAndBeefResult, processAction } from './methods/processAction'
|
|
@@ -57,6 +58,7 @@ import { TableMonitorEvent } from '../../src/storage/schema/tables/TableMonitorE
|
|
|
57
58
|
import { TableCertificateX } from './schema/tables/TableCertificate'
|
|
58
59
|
import {
|
|
59
60
|
WERR_INTERNAL,
|
|
61
|
+
WERR_INVALID_MERKLE_ROOT,
|
|
60
62
|
WERR_INVALID_OPERATION,
|
|
61
63
|
WERR_INVALID_PARAMETER,
|
|
62
64
|
WERR_MISSING_PARAMETER,
|
|
@@ -438,6 +440,20 @@ export abstract class StorageProvider extends StorageReaderWriter implements Wal
|
|
|
438
440
|
return proven != undefined || rawTx != undefined
|
|
439
441
|
}
|
|
440
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Pulls data from storage to build a valid beef for a txid.
|
|
445
|
+
*
|
|
446
|
+
* Optionally merges the data into an existing beef.
|
|
447
|
+
* Optionally requires a minimum number of proof levels.
|
|
448
|
+
*
|
|
449
|
+
* @param txid
|
|
450
|
+
* @param mergeToBeef
|
|
451
|
+
* @param trustSelf
|
|
452
|
+
* @param knownTxids
|
|
453
|
+
* @param trx
|
|
454
|
+
* @param requiredLevels
|
|
455
|
+
* @returns
|
|
456
|
+
*/
|
|
441
457
|
async getValidBeefForKnownTxid(
|
|
442
458
|
txid: string,
|
|
443
459
|
mergeToBeef?: Beef,
|
|
@@ -457,7 +473,9 @@ export abstract class StorageProvider extends StorageReaderWriter implements Wal
|
|
|
457
473
|
trustSelf?: TrustSelf,
|
|
458
474
|
knownTxids?: string[],
|
|
459
475
|
trx?: TrxToken,
|
|
460
|
-
requiredLevels?: number
|
|
476
|
+
requiredLevels?: number,
|
|
477
|
+
chainTracker?: ChainTracker,
|
|
478
|
+
skipInvalidProofs?: boolean
|
|
461
479
|
): Promise<Beef | undefined> {
|
|
462
480
|
const beef = mergeToBeef || new Beef()
|
|
463
481
|
|
|
@@ -468,10 +486,25 @@ export abstract class StorageProvider extends StorageReaderWriter implements Wal
|
|
|
468
486
|
} else {
|
|
469
487
|
if (trustSelf === 'known') beef.mergeTxidOnly(txid)
|
|
470
488
|
else {
|
|
471
|
-
beef.mergeRawTx(r.proven.rawTx)
|
|
472
489
|
const mp = new EntityProvenTx(r.proven).getMerklePath()
|
|
473
|
-
|
|
474
|
-
|
|
490
|
+
if (chainTracker) {
|
|
491
|
+
const root = mp.computeRoot()
|
|
492
|
+
const isValid = await chainTracker.isValidRootForHeight(root, r.proven.height)
|
|
493
|
+
if (!isValid) {
|
|
494
|
+
if (!skipInvalidProofs) {
|
|
495
|
+
throw new WERR_INVALID_MERKLE_ROOT(r.proven.blockHash, r.proven.height, root, txid)
|
|
496
|
+
}
|
|
497
|
+
// ignore this currently invalid proof and try to recurse deeper
|
|
498
|
+
r.rawTx = r.proven.rawTx
|
|
499
|
+
r.proven = undefined
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (r.proven) {
|
|
503
|
+
// If we still like this proof, merge it and return
|
|
504
|
+
beef.mergeRawTx(r.proven.rawTx)
|
|
505
|
+
beef.mergeBump(mp)
|
|
506
|
+
return beef
|
|
507
|
+
}
|
|
475
508
|
}
|
|
476
509
|
}
|
|
477
510
|
}
|