@bsv/wallet-toolbox 1.6.26 → 1.6.27

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 (34) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/docs/client.md +7 -2
  3. package/docs/storage.md +7 -2
  4. package/docs/wallet.md +7 -2
  5. package/mobile/out/src/signer/methods/internalizeAction.d.ts.map +1 -1
  6. package/mobile/out/src/signer/methods/internalizeAction.js +9 -1
  7. package/mobile/out/src/signer/methods/internalizeAction.js.map +1 -1
  8. package/mobile/out/src/storage/methods/internalizeAction.d.ts.map +1 -1
  9. package/mobile/out/src/storage/methods/internalizeAction.js +92 -18
  10. package/mobile/out/src/storage/methods/internalizeAction.js.map +1 -1
  11. package/mobile/out/src/storage/methods/processAction.d.ts +2 -1
  12. package/mobile/out/src/storage/methods/processAction.d.ts.map +1 -1
  13. package/mobile/out/src/storage/methods/processAction.js +4 -3
  14. package/mobile/out/src/storage/methods/processAction.js.map +1 -1
  15. package/mobile/package-lock.json +2 -2
  16. package/mobile/package.json +1 -1
  17. package/out/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.js +5 -0
  18. package/out/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.js.map +1 -1
  19. package/out/src/signer/methods/internalizeAction.d.ts.map +1 -1
  20. package/out/src/signer/methods/internalizeAction.js +9 -1
  21. package/out/src/signer/methods/internalizeAction.js.map +1 -1
  22. package/out/src/storage/methods/internalizeAction.d.ts.map +1 -1
  23. package/out/src/storage/methods/internalizeAction.js +92 -18
  24. package/out/src/storage/methods/internalizeAction.js.map +1 -1
  25. package/out/src/storage/methods/processAction.d.ts +2 -1
  26. package/out/src/storage/methods/processAction.d.ts.map +1 -1
  27. package/out/src/storage/methods/processAction.js +4 -3
  28. package/out/src/storage/methods/processAction.js.map +1 -1
  29. package/out/tsconfig.all.tsbuildinfo +1 -1
  30. package/package.json +1 -1
  31. package/src/services/chaintracker/chaintracks/__tests/Chaintracks.test.ts +3 -0
  32. package/src/signer/methods/internalizeAction.ts +9 -1
  33. package/src/storage/methods/internalizeAction.ts +100 -22
  34. package/src/storage/methods/processAction.ts +5 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/wallet-toolbox",
3
- "version": "1.6.26",
3
+ "version": "1.6.27",
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",
@@ -5,6 +5,7 @@ import { Chain } from '../../../../sdk'
5
5
  import { createDefaultNoDbChaintracksOptions } from '../createDefaultNoDbChaintracksOptions'
6
6
  import { ChaintracksFs } from '../util/ChaintracksFs'
7
7
  import { LocalCdnServer } from './LocalCdnServer'
8
+ import { _tu } from '../../../../../test/utils/TestUtilsWalletStorage'
8
9
 
9
10
  const rootFolder = './src/services/chaintracker/chaintracks/__tests/data'
10
11
 
@@ -25,10 +26,12 @@ describe('Chaintracks tests', () => {
25
26
  })
26
27
 
27
28
  test('1 NoDb mainnet', async () => {
29
+ if (_tu.noEnv('main')) return
28
30
  await NoDbBody('main')
29
31
  })
30
32
 
31
33
  test('2 NoDb testnet', async () => {
34
+ if (_tu.noEnv('main')) return
32
35
  await NoDbBody('test')
33
36
  })
34
37
 
@@ -79,10 +79,18 @@ export async function internalizeAction(
79
79
  */
80
80
  }
81
81
 
82
+ /**
83
+ * Verifies that the `tx` argument passed to internalizeAction is a valid AtomicBEEF,
84
+ * and the proofs are valid according to the wallet's configured chainTracker.
85
+ * THIS DOES NOT GUARANTEE:
86
+ * 1. That the transaction has been broadcast. (Is known to the network).
87
+ * 2. That the proofs are for the same block as recorded in the wallet's configured storage in the event of a reorg.
88
+ */
82
89
  async function validateAtomicBeef() {
83
90
  const ab = Beef.fromBinary(vargs.tx)
84
91
 
85
- // TODO: Add support for known txids...
92
+ // TODO: Add support for known txids...which would speed up processing by avoiding a network call,
93
+ // unless a local chaintracker is used.
86
94
 
87
95
  const txValid = await ab.verify(await wallet.getServices().getChainTracker(), false)
88
96
  if (!txValid || !ab.atomicTxid) {
@@ -6,9 +6,9 @@ import {
6
6
  TransactionOutput,
7
7
  Beef
8
8
  } from '@bsv/sdk'
9
- import { shareReqsWithWorld } from './processAction'
9
+ import { GetReqsAndBeefResult, shareReqsWithWorld } from './processAction'
10
10
  import { StorageProvider } from '../StorageProvider'
11
- import { AuthId, StorageInternalizeActionResult } from '../../sdk/WalletStorage.interfaces'
11
+ import { AuthId, StorageInternalizeActionResult, StorageProvenOrReq } from '../../sdk/WalletStorage.interfaces'
12
12
  import { TableOutput } from '../schema/tables/TableOutput'
13
13
  import { TableOutputBasket } from '../schema/tables/TableOutputBasket'
14
14
  import { TableTransaction } from '../schema/tables/TableTransaction'
@@ -17,6 +17,8 @@ import { WERR_INTERNAL, WERR_INVALID_PARAMETER } from '../../sdk/WERR_errors'
17
17
  import { randomBytesBase64, verifyId, verifyOne, verifyOneOrNone } from '../../utility/utilityHelpers'
18
18
  import { TransactionStatus } from '../../sdk/types'
19
19
  import { EntityProvenTxReq } from '../schema/entities/EntityProvenTxReq'
20
+ import { blockHash } from '../../services/chaintracker/chaintracks/util/blockHeaderUtilities'
21
+ import { TableProvenTx } from '../index.client'
20
22
 
21
23
  /**
22
24
  * Internalize Action allows a wallet to take ownership of outputs in a pre-existing transaction.
@@ -252,6 +254,21 @@ class InternalizeActionContext {
252
254
  }
253
255
  }
254
256
 
257
+ /**
258
+ * This is the second time the atomic beef is validated against a chaintracker.
259
+ * The first validation used the originating wallet's configured chaintracker.
260
+ * Now the chaintracker configured for this storage is used.
261
+ * These may be the same, or different.
262
+ *
263
+ * THIS DOES NOT GUARANTEE:
264
+ * 1. That the transaction has been broadcast. (Is known to the network).
265
+ * 2. That the proof(s) are for the same block as recorded in this storage in the event of a reorg.
266
+ *
267
+ * In the event of a reorg, we CAN assume that the proof contained in this beef should replace the proof in storage.
268
+ *
269
+ * @param atomicBeef
270
+ * @returns
271
+ */
255
272
  async validateAtomicBeef(atomicBeef: number[]) {
256
273
  const ab = Beef.fromBinary(atomicBeef)
257
274
  const txValid = await ab.verify(await this.storage.getServices().getChainTracker(), false)
@@ -277,14 +294,18 @@ class InternalizeActionContext {
277
294
  return { ab, tx, txid }
278
295
  }
279
296
 
280
- async findOrInsertTargetTransaction(satoshis: number, status: TransactionStatus): Promise<TableTransaction> {
297
+ async findOrInsertTargetTransaction(satoshis: number, provenTx?: TableProvenTx): Promise<TableTransaction> {
281
298
  const now = new Date()
299
+ const provenTxId = provenTx?.provenTxId
300
+ const status: TransactionStatus = provenTx ? 'completed' : 'unproven'
282
301
  const newTx: TableTransaction = {
283
302
  created_at: now,
284
303
  updated_at: now,
285
304
  transactionId: 0,
286
305
 
306
+ provenTxId,
287
307
  status,
308
+
288
309
  satoshis,
289
310
 
290
311
  version: this.tx.version,
@@ -301,10 +322,14 @@ class InternalizeActionContext {
301
322
  const tr = await this.storage.findOrInsertTransaction(newTx)
302
323
  if (!tr.isNew) {
303
324
  if (!this.isMerge)
325
+ // For now, only allow transaction record to pre-exist if it was there at the start.
304
326
  throw new WERR_INVALID_PARAMETER('tx', `target transaction of internalizeAction is undergoing active changes.`)
305
- await this.storage.updateTransaction(tr.tx.transactionId!, {
306
- satoshis: tr.tx.satoshis + satoshis
307
- })
327
+ const update: Partial<TableTransaction> = { satoshis: tr.tx.satoshis + satoshis }
328
+ if (provenTx) {
329
+ update.provenTxId = provenTxId
330
+ update.status = status
331
+ }
332
+ await this.storage.updateTransaction(tr.tx.transactionId!, update)
308
333
  }
309
334
  return tr.tx
310
335
  }
@@ -325,30 +350,83 @@ class InternalizeActionContext {
325
350
  }
326
351
  }
327
352
 
353
+ /**
354
+ * internalize output(s) from a transaction with txid unknown to storage.
355
+ */
328
356
  async newInternalize() {
329
- this.etx = await this.findOrInsertTargetTransaction(this.satoshis, 'unproven')
357
+ // Check if the transaction has a merkle path proof (BUMP)
358
+ const btx = this.ab.findTxid(this.txid)
359
+ if (!btx) throw new WERR_INTERNAL(`Could not find transaction ${this.txid} in AtomicBEEF`)
360
+ const bump = this.ab.findBump(this.txid)
361
+
362
+ let pr: StorageProvenOrReq = { isNew: false, proven: undefined, req: undefined }
363
+
364
+ if (bump) {
365
+ // The presence bump indicates the transaction has already been mined.
366
+ // Verify a provenTx record exist before creating a new transaction with completed status...
367
+ // Which normally means creating a new provenTx record.
368
+ const now = new Date()
369
+ const merkleRoot = bump.computeRoot(this.txid)
370
+ const indexEntry = bump.path[0].find(p => p.hash === this.txid)
371
+ if (!indexEntry) {
372
+ throw new WERR_INTERNAL(
373
+ `Could not determine transaction index for txid ${this.txid} in bump path. Expected to find txid in bump.path[0]: ${JSON.stringify(bump.path[0])}`
374
+ )
375
+ }
376
+ const index = indexEntry.offset
377
+ const header = await this.storage.getServices().getHeaderForHeight(bump.blockHeight)
378
+ if (!header) {
379
+ throw new WERR_INTERNAL(`Block header not found for height ${bump.blockHeight}`)
380
+ }
381
+ const hash = blockHash(header)
382
+ const provenTxR = await this.storage.findOrInsertProvenTx({
383
+ created_at: now,
384
+ updated_at: now,
385
+ provenTxId: 0,
386
+ txid: this.txid,
387
+ height: bump.blockHeight,
388
+ index,
389
+ merklePath: bump.toBinary(),
390
+ rawTx: btx.rawTx!,
391
+ blockHash: hash,
392
+ merkleRoot: merkleRoot
393
+ })
394
+ pr.proven = provenTxR.proven
395
+ }
396
+
397
+ this.etx = await this.findOrInsertTargetTransaction(this.satoshis, pr.proven)
330
398
 
331
399
  const transactionId = this.etx!.transactionId!
332
400
 
333
- // transaction record for user is new, but the txid may not be new to storage
334
- // make sure storage pursues getting a proof for it.
335
- const newReq = EntityProvenTxReq.fromTxid(this.txid, this.tx.toBinary(), this.args.tx)
336
- // this status is only relevant if the transaction is new to storage.
337
- newReq.status = 'unsent'
338
- // this history and notify will be merged into an existing req if it exists.
339
- newReq.addHistoryNote({ what: 'internalizeAction', userId: this.userId })
340
- newReq.addNotifyTransactionId(transactionId)
341
- const pr = await this.storage.getProvenOrReq(this.txid, newReq.toApi())
401
+ if (!pr.proven) {
402
+ // beef doesn't include proof of mining for the transaction (etx).
403
+ // the new transaction record has been added to storage, but (baring race conditions)
404
+ // there should be no provenTx or provenTxReq records for this txid.
405
+ //
406
+ // Attempt to create a provenTxReq record for the txid to obtain a proof,
407
+ // while allowing for possible race conditions...
408
+ const newReq = EntityProvenTxReq.fromTxid(this.txid, this.tx.toBinary(), this.args.tx)
409
+ newReq.status = 'unsent'
410
+ // this history and notify will be merged into an existing req if it exists.
411
+ newReq.addHistoryNote({ what: 'internalizeAction', userId: this.userId })
412
+ newReq.addNotifyTransactionId(transactionId)
413
+ pr = await this.storage.getProvenOrReq(this.txid, newReq.toApi())
414
+ }
342
415
 
343
416
  if (pr.isNew) {
344
- // This storage doesn't know about this txid yet.
345
-
346
- // TODO Can we immediately prove this txid?
347
- // TODO Do full validation on the transaction?
348
-
417
+ // This storage didn't know about this txid and the beef didn't include a mining proof.
418
+ // Assume the transaction has never been broadcast.
349
419
  // Attempt to broadcast it to the network, throwing an error if it fails.
350
420
 
351
- const { swr, ndr } = await shareReqsWithWorld(this.storage, this.userId, [this.txid], false)
421
+ // Skip looking up txids and building an aggregate beef,
422
+ // just this one txid and the already validated atomic beef.
423
+ // The beef may contain additional unbroadcast transactions which
424
+ // we don't care about.
425
+ const r: GetReqsAndBeefResult = {
426
+ beef: Beef.fromBinary(this.args.tx),
427
+ details: [{ txid: this.txid, status: 'readyToSend', req: pr.req }]
428
+ }
429
+ const { swr, ndr } = await shareReqsWithWorld(this.storage, this.userId, [], false, r)
352
430
  if (ndr![0].status !== 'success') {
353
431
  this.r.sendWithResults = swr
354
432
  this.r.notDelayedResults = ndr
@@ -107,20 +107,22 @@ export interface PostBeefResultForTxidApi {
107
107
  * @param userId
108
108
  * @param txids
109
109
  * @param isDelayed
110
+ * @param r Optional. Ignores txids and allows ProvenTxReqs and merged beef to be passed in.
110
111
  */
111
112
  export async function shareReqsWithWorld(
112
113
  storage: StorageProvider,
113
114
  userId: number,
114
115
  txids: string[],
115
- isDelayed: boolean
116
+ isDelayed: boolean,
117
+ r?: GetReqsAndBeefResult
116
118
  ): Promise<{ swr: SendWithResult[]; ndr: ReviewActionResult[] | undefined }> {
117
119
  let swr: SendWithResult[] = []
118
120
  let ndr: ReviewActionResult[] | undefined = undefined
119
121
 
120
- if (txids.length < 1) return { swr, ndr }
122
+ if (!r && txids.length < 1) return { swr, ndr }
121
123
 
122
124
  // Collect what we know about these sendWith transaction txids from storage.
123
- const r = await storage.getReqsAndBeefToShareWithWorld(txids, [])
125
+ r ||= await storage.getReqsAndBeefToShareWithWorld(txids, [])
124
126
 
125
127
  const readyToSendReqs: EntityProvenTxReq[] = []
126
128
  for (const getReq of r.details) {