@bsv/wallet-toolbox 1.4.9 → 1.4.11

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 (51) hide show
  1. package/CHANGELOG.md +13 -7
  2. package/docs/client.md +19 -53
  3. package/docs/monitor.md +1 -1
  4. package/docs/storage.md +18 -52
  5. package/docs/wallet.md +19 -53
  6. package/mobile/out/src/monitor/tasks/TaskReviewStatus.d.ts +1 -1
  7. package/mobile/out/src/monitor/tasks/TaskReviewStatus.js +1 -1
  8. package/mobile/out/src/storage/WalletStorageManager.d.ts +19 -25
  9. package/mobile/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  10. package/mobile/out/src/storage/WalletStorageManager.js +64 -69
  11. package/mobile/out/src/storage/WalletStorageManager.js.map +1 -1
  12. package/mobile/out/src/storage/methods/createAction.d.ts.map +1 -1
  13. package/mobile/out/src/storage/methods/createAction.js +9 -4
  14. package/mobile/out/src/storage/methods/createAction.js.map +1 -1
  15. package/mobile/out/src/storage/methods/processAction.js +1 -1
  16. package/mobile/out/src/storage/methods/processAction.js.map +1 -1
  17. package/mobile/package-lock.json +6 -6
  18. package/mobile/package.json +2 -2
  19. package/out/src/monitor/tasks/TaskReviewStatus.d.ts +1 -1
  20. package/out/src/monitor/tasks/TaskReviewStatus.js +1 -1
  21. package/out/src/storage/WalletStorageManager.d.ts +19 -25
  22. package/out/src/storage/WalletStorageManager.d.ts.map +1 -1
  23. package/out/src/storage/WalletStorageManager.js +64 -69
  24. package/out/src/storage/WalletStorageManager.js.map +1 -1
  25. package/out/src/storage/methods/createAction.d.ts.map +1 -1
  26. package/out/src/storage/methods/createAction.js +9 -4
  27. package/out/src/storage/methods/createAction.js.map +1 -1
  28. package/out/src/storage/methods/listOutputsKnex.js +2 -2
  29. package/out/src/storage/methods/listOutputsKnex.js.map +1 -1
  30. package/out/src/storage/methods/processAction.js +1 -1
  31. package/out/src/storage/methods/processAction.js.map +1 -1
  32. package/out/test/bsv-ts-sdk/LocalKVStore.test.d.ts +2 -0
  33. package/out/test/bsv-ts-sdk/LocalKVStore.test.d.ts.map +1 -0
  34. package/out/test/bsv-ts-sdk/LocalKVStore.test.js +88 -0
  35. package/out/test/bsv-ts-sdk/LocalKVStore.test.js.map +1 -0
  36. package/out/test/storage/idb/update.test.js +2 -2
  37. package/out/test/storage/idb/update.test.js.map +1 -1
  38. package/out/test/storage/update.test.js +2 -2
  39. package/out/test/storage/update.test.js.map +1 -1
  40. package/out/test/wallet/action/createAction2.test.js +2 -2
  41. package/out/tsconfig.all.tsbuildinfo +1 -1
  42. package/package.json +2 -2
  43. package/src/monitor/tasks/TaskReviewStatus.ts +1 -1
  44. package/src/storage/WalletStorageManager.ts +72 -68
  45. package/src/storage/methods/createAction.ts +16 -4
  46. package/src/storage/methods/listOutputsKnex.ts +2 -2
  47. package/src/storage/methods/processAction.ts +1 -1
  48. package/test/bsv-ts-sdk/LocalKVStore.test.ts +94 -0
  49. package/test/storage/idb/update.test.ts +2 -2
  50. package/test/storage/update.test.ts +2 -2
  51. package/test/wallet/action/createAction2.test.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.4.9",
3
+ "version": "1.4.11",
4
4
  "description": "BRC100 conforming wallet, wallet storage and wallet signer components",
5
5
  "main": "./out/src/index.js",
6
6
  "types": "./out/src/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "@bsv/auth-express-middleware": "^1.1.3",
34
34
  "@bsv/payment-express-middleware": "^1.0.6",
35
- "@bsv/sdk": "^1.6.6",
35
+ "@bsv/sdk": "^1.6.7",
36
36
  "express": "^4.21.2",
37
37
  "idb": "^8.0.2",
38
38
  "knex": "^3.1.0",
@@ -8,7 +8,7 @@ import { WalletMonitorTask } from './WalletMonitorTask'
8
8
  *
9
9
  * Looks for aged Transactions with provenTxId with status != 'completed', sets status to 'completed'.
10
10
  *
11
- * Looks for reqs with 'invalid' status that
11
+ * Looks for reqs with 'invalid' status that have corresonding transactions with status other than 'failed'.
12
12
  */
13
13
  export class TaskReviewStatus extends WalletMonitorTask {
14
14
  static taskName = 'ReviewStatus'
@@ -80,30 +80,14 @@ export class WalletStorageManager implements sdk.WalletStorage {
80
80
  * Configured services if any. If valid, shared with stores (which may ignore it).
81
81
  */
82
82
  _services?: sdk.WalletServices
83
+
83
84
  /**
84
- * How many read access operations are pending
85
- */
86
- _readerCount: number = 0
87
- /**
88
- * How many write access operations are pending
89
- */
90
- _writerCount: number = 0
91
- /**
92
- * if true, allow only a single writer to proceed at a time.
93
- * queue the blocked requests so they get executed in order when released.
94
- */
95
- _isSingleWriter: boolean = true
96
- /**
97
- * if true, allow no new reader or writers to proceed.
98
- * queue the blocked requests so they get executed in order when released.
99
- */
100
- _syncLocked: boolean = false
101
- /**
102
- * if true, allow no new reader or writers or sync to proceed.
103
- * queue the blocked requests so they get executed in order when released.
85
+ * Creates a new WalletStorageManager with the given identityKey and optional active and backup storage providers.
86
+ *
87
+ * @param identityKey The identity key of the user for whom this wallet is being managed.
88
+ * @param active An optional active storage provider. If not provided, no active storage will be set.
89
+ * @param backups An optional array of backup storage providers. If not provided, no backups will be set.
104
90
  */
105
- _storageProviderLocked: boolean = false
106
-
107
91
  constructor(identityKey: string, active?: sdk.WalletStorageProvider, backups?: sdk.WalletStorageProvider[]) {
108
92
  const stores = [...(backups || [])]
109
93
  if (active) stores.unshift(active)
@@ -260,64 +244,84 @@ export class WalletStorageManager implements sdk.WalletStorage {
260
244
  return this._stores.map(b => b.settings!.storageIdentityKey)
261
245
  }
262
246
 
263
- async getActiveForWriter(): Promise<sdk.WalletStorageWriter> {
247
+ private readonly readerLocks: Array<(value: void | PromiseLike<void>) => void> = []
248
+ private readonly writerLocks: Array<(value: void | PromiseLike<void>) => void> = []
249
+ private readonly syncLocks: Array<(value: void | PromiseLike<void>) => void> = []
250
+ private readonly spLocks: Array<(value: void | PromiseLike<void>) => void> = []
251
+
252
+ private async getActiveLock(
253
+ lockQueue: Array<(value: void | PromiseLike<void>) => void>
254
+ ): Promise<void> {
264
255
  if (!this.isAvailable()) await this.makeAvailable()
265
- while (
266
- this._storageProviderLocked ||
267
- this._syncLocked ||
268
- (this._isSingleWriter && this._writerCount > 0) ||
269
- this._readerCount > 0
270
- ) {
271
- await wait(100)
256
+
257
+ let resolveNewLock: () => void = () => {}
258
+ const newLock = new Promise<void>(resolve => {
259
+ resolveNewLock = resolve
260
+ lockQueue.push(resolve)
261
+ })
262
+ if (lockQueue.length === 1) {
263
+ resolveNewLock()
272
264
  }
273
- this._writerCount++
274
- return this.getActive()
265
+ await newLock
275
266
  }
276
267
 
277
- async getActiveForReader(): Promise<sdk.WalletStorageReader> {
278
- if (!this.isAvailable()) await this.makeAvailable()
279
- while (this._storageProviderLocked || this._syncLocked || (this._isSingleWriter && this._writerCount > 0)) {
280
- await wait(100)
268
+ private releaseActiveLock(queue: Array<(value: void | PromiseLike<void>) => void>): void {
269
+ queue.shift() // Remove the current lock from the queue
270
+ if (queue.length > 0) {
271
+ queue[0]()
281
272
  }
282
- this._readerCount++
273
+ }
274
+
275
+ private async getActiveForReader(): Promise<sdk.WalletStorageReader> {
276
+ await this.getActiveLock(this.readerLocks)
283
277
  return this.getActive()
284
278
  }
279
+ private releaseActiveForReader(): void {
280
+ this.releaseActiveLock(this.readerLocks)
281
+ }
285
282
 
286
- async getActiveForSync(): Promise<sdk.WalletStorageSync> {
287
- if (!this.isAvailable()) await this.makeAvailable()
288
- // Wait for a current sync task to complete...
289
- while (this._syncLocked) {
290
- await wait(100)
291
- }
292
- // Set syncLocked which prevents any new storageProvider, readers or writers...
293
- this._syncLocked = true
294
- // Wait for any current storageProvider, readers and writers to complete
295
- while (this._storageProviderLocked || this._readerCount > 0 || this._writerCount > 0) {
296
- await wait(100)
297
- }
298
- // Allow the sync to proceed on the active store.
283
+ private async getActiveForWriter(): Promise<sdk.WalletStorageWriter> {
284
+ await this.getActiveLock(this.readerLocks)
285
+ await this.getActiveLock(this.writerLocks)
299
286
  return this.getActive()
300
287
  }
288
+ private releaseActiveForWriter(): void {
289
+ this.releaseActiveLock(this.writerLocks)
290
+ this.releaseActiveLock(this.readerLocks)
291
+ }
301
292
 
302
- async getActiveForStorageProvider(): Promise<StorageProvider> {
303
- if (!this.isAvailable()) await this.makeAvailable()
304
- // Wait for a current storageProvider call to complete...
305
- while (this._storageProviderLocked) {
306
- await wait(100)
307
- }
308
- // Set storageProviderLocked which prevents any new sync, readers or writers...
309
- this._storageProviderLocked = true
310
- // Wait for any current sync, readers and writers to complete
311
- while (this._syncLocked || this._readerCount > 0 || this._writerCount > 0) {
312
- await wait(100)
313
- }
293
+ private async getActiveForSync(): Promise<sdk.WalletStorageSync> {
294
+ await this.getActiveLock(this.readerLocks)
295
+ await this.getActiveLock(this.writerLocks)
296
+ await this.getActiveLock(this.syncLocks)
297
+ return this.getActive()
298
+ }
299
+ private releaseActiveForSync(): void {
300
+ this.releaseActiveLock(this.syncLocks)
301
+ this.releaseActiveLock(this.writerLocks)
302
+ this.releaseActiveLock(this.readerLocks)
303
+ }
304
+
305
+ private async getActiveForStorageProvider(): Promise<StorageProvider> {
306
+ await this.getActiveLock(this.readerLocks)
307
+ await this.getActiveLock(this.writerLocks)
308
+ await this.getActiveLock(this.syncLocks)
309
+ await this.getActiveLock(this.spLocks)
310
+
311
+ const active = this.getActive()
314
312
  // We can finally confirm that active storage is still able to support `StorageProvider`
315
- if (!this.getActive().isStorageProvider())
313
+ if (!active.isStorageProvider())
316
314
  throw new sdk.WERR_INVALID_OPERATION(
317
315
  'Active "WalletStorageProvider" does not support "StorageProvider" interface.'
318
316
  )
319
317
  // Allow the sync to proceed on the active store.
320
- return this.getActive() as unknown as StorageProvider
318
+ return active as unknown as StorageProvider
319
+ }
320
+ private releaseActiveForStorageProvider(): void {
321
+ this.releaseActiveLock(this.spLocks)
322
+ this.releaseActiveLock(this.syncLocks)
323
+ this.releaseActiveLock(this.writerLocks)
324
+ this.releaseActiveLock(this.readerLocks)
321
325
  }
322
326
 
323
327
  async runAsWriter<R>(writer: (active: sdk.WalletStorageWriter) => Promise<R>): Promise<R> {
@@ -326,7 +330,7 @@ export class WalletStorageManager implements sdk.WalletStorage {
326
330
  const r = await writer(active)
327
331
  return r
328
332
  } finally {
329
- this._writerCount--
333
+ this.releaseActiveForWriter()
330
334
  }
331
335
  }
332
336
 
@@ -336,7 +340,7 @@ export class WalletStorageManager implements sdk.WalletStorage {
336
340
  const r = await reader(active)
337
341
  return r
338
342
  } finally {
339
- this._readerCount--
343
+ this.releaseActiveForReader()
340
344
  }
341
345
  }
342
346
 
@@ -355,7 +359,7 @@ export class WalletStorageManager implements sdk.WalletStorage {
355
359
  const r = await sync(active)
356
360
  return r
357
361
  } finally {
358
- if (!activeSync) this._syncLocked = false
362
+ if (!activeSync) this.releaseActiveForSync()
359
363
  }
360
364
  }
361
365
 
@@ -365,7 +369,7 @@ export class WalletStorageManager implements sdk.WalletStorage {
365
369
  const r = await sync(active)
366
370
  return r
367
371
  } finally {
368
- this._storageProviderLocked = false
372
+ this.releaseActiveForStorageProvider()
369
373
  }
370
374
  }
371
375
 
@@ -209,10 +209,22 @@ async function createNewInputs(
209
209
  const o = i.output
210
210
  newInputs.push({ i, o })
211
211
  if (o) {
212
- await storage.updateOutput(o.outputId!, {
213
- spendable: false,
214
- spentBy: ctx.transactionId,
215
- spendingDescription: i.inputDescription
212
+ await storage.transaction(async trx => {
213
+ const o2 = verifyOne(await storage.findOutputs({ partial: { outputId: o.outputId }, trx }))
214
+ if (o2.spendable != true || o2.spentBy !== undefined)
215
+ throw new sdk.WERR_INVALID_PARAMETER(
216
+ `inputs[${i.vin}]`,
217
+ `spendable output. output ${o.txid}:${o.vout} appears to have been spent.`
218
+ )
219
+ await storage.updateOutput(
220
+ o.outputId!,
221
+ {
222
+ spendable: false,
223
+ spentBy: ctx.transactionId,
224
+ spendingDescription: i.inputDescription
225
+ },
226
+ trx
227
+ )
216
228
  })
217
229
  }
218
230
  }
@@ -124,8 +124,8 @@ export async function listOutputs(
124
124
  const noTags = tagIds.length === 0
125
125
  const includeSpent = specOp && specOp.includeSpent ? specOp.includeSpent : false
126
126
 
127
- const txStatusOk = `(select status as tstatus from transactions where transactions.transactionId = outputs.transactionId) in ('completed', 'unproven', 'nosend')`
128
- const txStatusOkCteq = `(select status as tstatus from transactions where transactions.transactionId = o.transactionId) in ('completed', 'unproven', 'nosend')`
127
+ const txStatusOk = `(select status as tstatus from transactions where transactions.transactionId = outputs.transactionId) in ('completed', 'unproven', 'nosend', 'sending')`
128
+ const txStatusOkCteq = `(select status as tstatus from transactions where transactions.transactionId = o.transactionId) in ('completed', 'unproven', 'nosend', 'sending')`
129
129
 
130
130
  const makeWithTagsQueries = () => {
131
131
  let cteqOptions = ''
@@ -393,7 +393,7 @@ async function validateCommitNewTxToStorageArgs(
393
393
  )
394
394
  const update: Partial<TableOutput> = {
395
395
  txid: vargs.txid,
396
- spendable: o.basketId !== undefined, // spendability is gated by transaction status. Remains true until the output is spent.
396
+ spendable: true, // spendability is gated by transaction status. Remains true until the output is spent.
397
397
  scriptLength: offset.length,
398
398
  scriptOffset: offset.offset
399
399
  }
@@ -0,0 +1,94 @@
1
+ import { _tu, logger, TestWalletNoSetup } from '../utils/TestUtilsWalletStorage'
2
+ import { LocalKVStore } from '@bsv/sdk'
3
+
4
+ describe('LocalKVStore tests', () => {
5
+ jest.setTimeout(99999999)
6
+
7
+ const testName = () => expect.getState().currentTestName || 'test'
8
+ let ctxs: TestWalletNoSetup[] = []
9
+ const context = 'test kv store'
10
+ const key1 = 'key1'
11
+ const key2 = 'key2'
12
+
13
+ beforeEach(async () => {
14
+ ctxs = [await _tu.createLegacyWalletSQLiteCopy(`${testName()}`)]
15
+ })
16
+
17
+ afterEach(async () => {
18
+ for (const ctx of ctxs) {
19
+ await ctx.storage.destroy()
20
+ }
21
+ })
22
+
23
+ test('0 get non-existent', async () => {
24
+ for (const { storage, wallet } of ctxs) {
25
+ const kvStore = new LocalKVStore(wallet, context, false, undefined, true)
26
+ const value = await kvStore.get(key1)
27
+ expect(value).toBeUndefined()
28
+ }
29
+ })
30
+
31
+ test('1 set get', async () => {
32
+ for (const { storage, wallet } of ctxs) {
33
+ const kvStore = new LocalKVStore(wallet, context, false, undefined, true)
34
+ await kvStore.set(key1, 'value1')
35
+ const value = await kvStore.get(key1)
36
+ expect(value).toBe('value1')
37
+ }
38
+ })
39
+
40
+ test('3 set x 4 get', async () => {
41
+ for (const { storage, wallet } of ctxs) {
42
+ const kvStore = new LocalKVStore(wallet, context, false, undefined, true)
43
+ const promises = [
44
+ kvStore.set(key1, 'value1'),
45
+ kvStore.set(key1, 'value2'),
46
+ kvStore.set(key1, 'value3'),
47
+ kvStore.set(key1, 'value4')
48
+ ]
49
+ await Promise.all(promises)
50
+ const value = await kvStore.get(key1)
51
+ expect(value).toBe('value4')
52
+ }
53
+ })
54
+
55
+ test('4 promise test', async () => {
56
+ let resolveNewLock: () => void = () => {}
57
+ const newLock = new Promise<void>(resolve => {
58
+ resolveNewLock = resolve
59
+ })
60
+ const t = Date.now()
61
+ setTimeout(() => {
62
+ resolveNewLock()
63
+ }, 1000)
64
+ await newLock
65
+ const elapsed = Date.now() - t
66
+ logger(`Elapsed time: ${elapsed} ms`)
67
+ expect(elapsed).toBeGreaterThanOrEqual(1000)
68
+ })
69
+
70
+ test('5 set x 4 get set x 4 get', async () => {
71
+ for (const { storage, wallet } of ctxs) {
72
+ const kvStore = new LocalKVStore(wallet, context, false, undefined, true)
73
+ let v4: string | undefined
74
+ async function captureValue(): Promise<void> {
75
+ v4 = await kvStore.get(key1)
76
+ }
77
+ const promises = [
78
+ kvStore.set(key1, 'value1'),
79
+ kvStore.set(key1, 'value2'),
80
+ kvStore.set(key1, 'value3'),
81
+ kvStore.set(key1, 'value4'),
82
+ captureValue(),
83
+ kvStore.set(key1, 'value5'),
84
+ kvStore.set(key1, 'value6'),
85
+ kvStore.set(key1, 'value7'),
86
+ kvStore.set(key1, 'value8')
87
+ ]
88
+ await Promise.all(promises)
89
+ const v8 = await kvStore.get(key1)
90
+ expect(v4).toBe('value4')
91
+ expect(v8).toBe('value8')
92
+ }
93
+ })
94
+ })
@@ -25,7 +25,7 @@ import 'fake-indexeddb/auto'
25
25
 
26
26
  setLogging(false)
27
27
 
28
- describe('update tests', () => {
28
+ describe('idb update tests', () => {
29
29
  jest.setTimeout(99999999)
30
30
 
31
31
  const chain: sdk.Chain = 'test'
@@ -501,7 +501,7 @@ describe('update tests', () => {
501
501
  })
502
502
 
503
503
  test('7a updateTransactionStatus', async () => {
504
- const { activeStorage: storage } = await _tu.createLegacyWalletSQLiteCopy('updateTransactionStatus6a')
504
+ const { activeStorage: storage } = await _tu.createLegacyWalletSQLiteCopy('updateTransactionStatus7a')
505
505
 
506
506
  let tx = verifyOne(
507
507
  await storage.findTransactions({
@@ -21,7 +21,7 @@ import {
21
21
 
22
22
  setLogging(false)
23
23
 
24
- describe('idb update tests', () => {
24
+ describe('update tests', () => {
25
25
  jest.setTimeout(99999999)
26
26
 
27
27
  const chain: sdk.Chain = 'test'
@@ -523,7 +523,7 @@ describe('idb update tests', () => {
523
523
  })
524
524
 
525
525
  test('7a updateTransactionStatus', async () => {
526
- const { activeStorage: storage } = await _tu.createLegacyWalletSQLiteCopy('updateTransactionStatus6a')
526
+ const { activeStorage: storage } = await _tu.createLegacyWalletSQLiteCopy('updateTransactionStatus7a')
527
527
 
528
528
  let tx = verifyOne(
529
529
  await storage.findTransactions({
@@ -227,7 +227,7 @@ describe('createAction2 nosend transactions', () => {
227
227
  1: sourceTXID:70afdc54187a1cdb8e35f7d00e5e111cbf5c43c4dc3f1da2cc44479133c75f9e.0 sats:4 desc:'Funding output'
228
228
  lock:(48)76a914abcdef0123456789abcdef0123456789abcdef88ac unlock:(16)47304402207f2e9a seq:4294967295
229
229
  outputs: 2
230
- 0: sats:2 lock:(48)76a914abcdef0123456789abcdef0123456789abcdef88ac index:0 spendable:false desc:'First spending
230
+ 0: sats:2 lock:(48)76a914abcdef0123456789abcdef0123456789abcdef88ac index:0 spendable:true desc:'First spending
231
231
  Output for check on change '
232
232
  1: sats:996 lock:(50)76a9145947e66cdd43c70fb1780116b79e6f7d96e30e0888ac index:1 spendable:true basket:'default'`)
233
233
  }
@@ -287,7 +287,7 @@ describe('createAction2 nosend transactions', () => {
287
287
  lock:(50)76a914ab2b66432503a3681fc5af1502207ca458c8752d88ac
288
288
  unlock:(212)4730440220113a6f72035a6ddcd6930db7e3f3d5c70486f9aaefb095e6fa3557afa916ec37022054... seq:4294967295
289
289
  outputs: 2
290
- 0: sats:4 lock:(48)76a914abcdef0123456789abcdef0123456789abcdef88ac index:0 spendable:false desc:'returnTXIDOnly
290
+ 0: sats:4 lock:(48)76a914abcdef0123456789abcdef0123456789abcdef88ac index:0 spendable:true desc:'returnTXIDOnly
291
291
  false test'
292
292
  1: sats:990 lock:(50)76a9145947e66cdd43c70fb1780116b79e6f7d96e30e0888ac index:1 spendable:true basket:'default'`)
293
293
  }