@bsv/wallet-toolbox 1.7.19 → 1.7.22
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 +26 -2
- package/docs/services.md +13 -1
- package/docs/storage.md +13 -1
- package/docs/wallet.md +26 -2
- package/out/src/WalletPermissionsManager.d.ts +6 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +241 -74
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/services/__tests/getRawTx.test.js +3 -0
- package/out/src/services/__tests/getRawTx.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/src/storage/methods/internalizeAction.d.ts.map +1 -1
- package/out/src/storage/methods/internalizeAction.js +2 -2
- package/out/src/storage/methods/internalizeAction.js.map +1 -1
- package/out/src/storage/schema/entities/__tests/ProvenTxTests.test.js +4 -0
- package/out/src/storage/schema/entities/__tests/ProvenTxTests.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/WalletPermissionsManager.ts +302 -71
- package/src/services/__tests/getRawTx.test.ts +2 -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
- package/src/storage/methods/internalizeAction.ts +4 -3
- package/src/storage/schema/entities/__tests/ProvenTxTests.test.ts +2 -0
package/package.json
CHANGED
|
@@ -831,70 +831,80 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
831
831
|
|
|
832
832
|
const expiry = params.expiry || 0 // default: never expires
|
|
833
833
|
|
|
834
|
+
const toCreate: Array<{ request: PermissionRequest; expiry: number; amount?: number }> = []
|
|
835
|
+
const toRenew: Array<{ oldToken: PermissionToken; request: PermissionRequest; expiry: number; amount?: number }> =
|
|
836
|
+
[]
|
|
837
|
+
|
|
834
838
|
if (params.granted.spendingAuthorization) {
|
|
835
|
-
|
|
836
|
-
{
|
|
839
|
+
toCreate.push({
|
|
840
|
+
request: {
|
|
837
841
|
type: 'spending',
|
|
838
842
|
originator,
|
|
839
843
|
spending: { satoshis: params.granted.spendingAuthorization.amount },
|
|
840
844
|
reason: params.granted.spendingAuthorization.description
|
|
841
845
|
},
|
|
842
|
-
0,
|
|
843
|
-
params.granted.spendingAuthorization.amount
|
|
844
|
-
)
|
|
846
|
+
expiry: 0,
|
|
847
|
+
amount: params.granted.spendingAuthorization.amount
|
|
848
|
+
})
|
|
845
849
|
}
|
|
846
|
-
|
|
850
|
+
|
|
851
|
+
const grantedProtocols = params.granted.protocolPermissions || []
|
|
852
|
+
const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async p => {
|
|
847
853
|
const token = await this.findProtocolToken(
|
|
848
854
|
originator,
|
|
849
|
-
false,
|
|
855
|
+
false,
|
|
850
856
|
p.protocolID,
|
|
851
857
|
p.counterparty || 'self',
|
|
852
858
|
true,
|
|
853
859
|
originLookupValues
|
|
854
860
|
)
|
|
861
|
+
return { p, token }
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
for (const { p, token } of protocolTokens) {
|
|
865
|
+
const request: PermissionRequest = {
|
|
866
|
+
type: 'protocol',
|
|
867
|
+
originator,
|
|
868
|
+
privileged: false,
|
|
869
|
+
protocolID: p.protocolID,
|
|
870
|
+
counterparty: p.counterparty || 'self',
|
|
871
|
+
reason: p.description
|
|
872
|
+
}
|
|
855
873
|
if (token) {
|
|
856
|
-
|
|
857
|
-
type: 'protocol',
|
|
858
|
-
originator,
|
|
859
|
-
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
860
|
-
protocolID: p.protocolID,
|
|
861
|
-
counterparty: p.counterparty || 'self',
|
|
862
|
-
reason: p.description
|
|
863
|
-
}
|
|
864
|
-
await this.renewPermissionOnChain(token, request, expiry)
|
|
865
|
-
this.markRecentGrant(request)
|
|
874
|
+
toRenew.push({ oldToken: token, request, expiry })
|
|
866
875
|
} else {
|
|
867
|
-
|
|
868
|
-
type: 'protocol',
|
|
869
|
-
originator,
|
|
870
|
-
privileged: false, // No privileged protocols allowed in groups for added security.
|
|
871
|
-
protocolID: p.protocolID,
|
|
872
|
-
counterparty: p.counterparty || 'self',
|
|
873
|
-
reason: p.description
|
|
874
|
-
}
|
|
875
|
-
await this.createPermissionOnChain(request, expiry)
|
|
876
|
-
this.markRecentGrant(request)
|
|
876
|
+
toCreate.push({ request, expiry })
|
|
877
877
|
}
|
|
878
878
|
}
|
|
879
|
+
|
|
879
880
|
for (const b of params.granted.basketAccess || []) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
881
|
+
toCreate.push({
|
|
882
|
+
request: { type: 'basket', originator, basket: b.basket, reason: b.description },
|
|
883
|
+
expiry
|
|
884
|
+
})
|
|
883
885
|
}
|
|
886
|
+
|
|
884
887
|
for (const c of params.granted.certificateAccess || []) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
888
|
+
toCreate.push({
|
|
889
|
+
request: {
|
|
890
|
+
type: 'certificate',
|
|
891
|
+
originator,
|
|
892
|
+
privileged: false,
|
|
893
|
+
certificate: {
|
|
894
|
+
verifier: c.verifierPublicKey,
|
|
895
|
+
certType: c.type,
|
|
896
|
+
fields: c.fields
|
|
897
|
+
},
|
|
898
|
+
reason: c.description
|
|
893
899
|
},
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
|
|
900
|
+
expiry
|
|
901
|
+
})
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const created = await this.createPermissionTokensBestEffort(toCreate)
|
|
905
|
+
const renewed = await this.renewPermissionTokensBestEffort(toRenew)
|
|
906
|
+
for (const req of [...created, ...renewed]) {
|
|
907
|
+
this.markRecentGrant(req)
|
|
898
908
|
}
|
|
899
909
|
|
|
900
910
|
// Resolve all pending promises for this request
|
|
@@ -958,7 +968,12 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
958
968
|
|
|
959
969
|
const expiry = params.expiry || 0
|
|
960
970
|
|
|
961
|
-
|
|
971
|
+
const toCreate: Array<{ request: PermissionRequest; expiry: number; amount?: number }> = []
|
|
972
|
+
const toRenew: Array<{ oldToken: PermissionToken; request: PermissionRequest; expiry: number; amount?: number }> =
|
|
973
|
+
[]
|
|
974
|
+
|
|
975
|
+
const grantedProtocols = params.granted.protocols || []
|
|
976
|
+
const protocolTokens = await this.mapWithConcurrency(grantedProtocols, 8, async p => {
|
|
962
977
|
const token = await this.findProtocolToken(
|
|
963
978
|
originator,
|
|
964
979
|
false,
|
|
@@ -967,32 +982,30 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
967
982
|
true,
|
|
968
983
|
originLookupValues
|
|
969
984
|
)
|
|
985
|
+
return { p, token }
|
|
986
|
+
})
|
|
987
|
+
|
|
988
|
+
for (const { p, token } of protocolTokens) {
|
|
989
|
+
const request: PermissionRequest = {
|
|
990
|
+
type: 'protocol',
|
|
991
|
+
originator,
|
|
992
|
+
privileged: false,
|
|
993
|
+
protocolID: p.protocolID,
|
|
994
|
+
counterparty,
|
|
995
|
+
reason: p.description
|
|
996
|
+
}
|
|
970
997
|
if (token) {
|
|
971
|
-
|
|
972
|
-
type: 'protocol',
|
|
973
|
-
originator,
|
|
974
|
-
privileged: false,
|
|
975
|
-
protocolID: p.protocolID,
|
|
976
|
-
counterparty,
|
|
977
|
-
reason: p.description
|
|
978
|
-
}
|
|
979
|
-
await this.renewPermissionOnChain(token, request, expiry)
|
|
980
|
-
this.markRecentGrant(request)
|
|
998
|
+
toRenew.push({ oldToken: token, request, expiry })
|
|
981
999
|
} else {
|
|
982
|
-
|
|
983
|
-
type: 'protocol',
|
|
984
|
-
originator,
|
|
985
|
-
privileged: false,
|
|
986
|
-
protocolID: p.protocolID,
|
|
987
|
-
counterparty,
|
|
988
|
-
reason: p.description
|
|
989
|
-
}
|
|
990
|
-
await this.createPermissionOnChain(request, expiry)
|
|
991
|
-
this.markRecentGrant(request)
|
|
1000
|
+
toCreate.push({ request, expiry })
|
|
992
1001
|
}
|
|
993
1002
|
}
|
|
994
1003
|
|
|
995
|
-
this.
|
|
1004
|
+
const created = await this.createPermissionTokensBestEffort(toCreate)
|
|
1005
|
+
const renewed = await this.renewPermissionTokensBestEffort(toRenew)
|
|
1006
|
+
for (const req of [...created, ...renewed]) {
|
|
1007
|
+
this.markRecentGrant(req)
|
|
1008
|
+
}
|
|
996
1009
|
|
|
997
1010
|
for (const p of matching.pending) {
|
|
998
1011
|
p.resolve(true)
|
|
@@ -2574,13 +2587,178 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
2574
2587
|
}
|
|
2575
2588
|
],
|
|
2576
2589
|
options: {
|
|
2577
|
-
acceptDelayedBroadcast:
|
|
2590
|
+
acceptDelayedBroadcast: true
|
|
2578
2591
|
}
|
|
2579
2592
|
},
|
|
2580
2593
|
this.adminOriginator
|
|
2581
2594
|
)
|
|
2582
2595
|
}
|
|
2583
2596
|
|
|
2597
|
+
private async mapWithConcurrency<T, R>(items: T[], concurrency: number, fn: (item: T) => Promise<R>): Promise<R[]> {
|
|
2598
|
+
if (!items.length) return []
|
|
2599
|
+
const results: R[] = new Array(items.length)
|
|
2600
|
+
let i = 0
|
|
2601
|
+
const worker = async () => {
|
|
2602
|
+
while (true) {
|
|
2603
|
+
const idx = i++
|
|
2604
|
+
if (idx >= items.length) return
|
|
2605
|
+
results[idx] = await fn(items[idx])
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()))
|
|
2609
|
+
return results
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
private async runBestEffortBatches<T, R>(
|
|
2613
|
+
items: T[],
|
|
2614
|
+
chunkSize: number,
|
|
2615
|
+
runChunk: (chunk: T[]) => Promise<R[]>
|
|
2616
|
+
): Promise<R[]> {
|
|
2617
|
+
if (!items.length) return []
|
|
2618
|
+
const out: R[] = []
|
|
2619
|
+
for (let i = 0; i < items.length; i += chunkSize) {
|
|
2620
|
+
const chunk = items.slice(i, i + chunkSize)
|
|
2621
|
+
out.push(...(await this.runBestEffortChunk(chunk, runChunk)))
|
|
2622
|
+
}
|
|
2623
|
+
return out
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
private async runBestEffortChunk<T, R>(chunk: T[], runChunk: (chunk: T[]) => Promise<R[]>): Promise<R[]> {
|
|
2627
|
+
try {
|
|
2628
|
+
return await runChunk(chunk)
|
|
2629
|
+
} catch (e) {
|
|
2630
|
+
if (chunk.length <= 1) {
|
|
2631
|
+
console.error('Permission batch failed:', e)
|
|
2632
|
+
return []
|
|
2633
|
+
}
|
|
2634
|
+
const mid = Math.ceil(chunk.length / 2)
|
|
2635
|
+
const left = await this.runBestEffortChunk(chunk.slice(0, mid), runChunk)
|
|
2636
|
+
const right = await this.runBestEffortChunk(chunk.slice(mid), runChunk)
|
|
2637
|
+
return [...left, ...right]
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
private async buildPermissionOutput(
|
|
2642
|
+
r: PermissionRequest,
|
|
2643
|
+
expiry: number,
|
|
2644
|
+
amount?: number
|
|
2645
|
+
): Promise<{
|
|
2646
|
+
output: { lockingScript: string; satoshis: number; outputDescription: string; basket: string; tags: string[] }
|
|
2647
|
+
request: PermissionRequest
|
|
2648
|
+
}> {
|
|
2649
|
+
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator
|
|
2650
|
+
r.originator = normalizedOriginator
|
|
2651
|
+
const basketName = BASKET_MAP[r.type]
|
|
2652
|
+
if (!basketName) {
|
|
2653
|
+
throw new Error(`Unsupported permission type: ${r.type}`)
|
|
2654
|
+
}
|
|
2655
|
+
const fields: number[][] = await this.buildPushdropFields(r, expiry, amount)
|
|
2656
|
+
const script = await new PushDrop(this.underlying).lock(
|
|
2657
|
+
fields,
|
|
2658
|
+
WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL,
|
|
2659
|
+
'1',
|
|
2660
|
+
'self',
|
|
2661
|
+
true,
|
|
2662
|
+
true
|
|
2663
|
+
)
|
|
2664
|
+
const tags = this.buildTagsForRequest(r)
|
|
2665
|
+
return {
|
|
2666
|
+
request: r,
|
|
2667
|
+
output: {
|
|
2668
|
+
lockingScript: script.toHex(),
|
|
2669
|
+
satoshis: 1,
|
|
2670
|
+
outputDescription: `${r.type} permission token`,
|
|
2671
|
+
basket: basketName,
|
|
2672
|
+
tags
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
private async createPermissionTokensBestEffort(
|
|
2678
|
+
items: Array<{ request: PermissionRequest; expiry: number; amount?: number }>
|
|
2679
|
+
): Promise<PermissionRequest[]> {
|
|
2680
|
+
const CHUNK = 25
|
|
2681
|
+
return this.runBestEffortBatches(items, CHUNK, async chunk => {
|
|
2682
|
+
const built = await this.mapWithConcurrency(chunk, 8, c =>
|
|
2683
|
+
this.buildPermissionOutput(c.request, c.expiry, c.amount)
|
|
2684
|
+
)
|
|
2685
|
+
await this.createAction(
|
|
2686
|
+
{
|
|
2687
|
+
description: `Grant ${built.length} permissions`,
|
|
2688
|
+
outputs: built.map(b => b.output),
|
|
2689
|
+
options: { acceptDelayedBroadcast: true }
|
|
2690
|
+
},
|
|
2691
|
+
this.adminOriginator
|
|
2692
|
+
)
|
|
2693
|
+
return built.map(b => b.request)
|
|
2694
|
+
})
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
private async renewPermissionTokensBestEffort(
|
|
2698
|
+
items: Array<{ oldToken: PermissionToken; request: PermissionRequest; expiry: number; amount?: number }>
|
|
2699
|
+
): Promise<PermissionRequest[]> {
|
|
2700
|
+
const CHUNK = 15
|
|
2701
|
+
return this.runBestEffortBatches(items, CHUNK, async chunk => {
|
|
2702
|
+
const built = await this.mapWithConcurrency(chunk, 8, c =>
|
|
2703
|
+
this.buildPermissionOutput(c.request, c.expiry, c.amount)
|
|
2704
|
+
)
|
|
2705
|
+
|
|
2706
|
+
const inputBeef = new Beef()
|
|
2707
|
+
for (const c of chunk) {
|
|
2708
|
+
inputBeef.mergeBeef(Beef.fromBinary(c.oldToken.tx))
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
const { signableTransaction } = await this.createAction(
|
|
2712
|
+
{
|
|
2713
|
+
description: `Renew ${chunk.length} permissions`,
|
|
2714
|
+
inputBEEF: inputBeef.toBinary(),
|
|
2715
|
+
inputs: chunk.map((c, i) => ({
|
|
2716
|
+
outpoint: `${c.oldToken.txid}.${c.oldToken.outputIndex}`,
|
|
2717
|
+
unlockingScriptLength: 73,
|
|
2718
|
+
inputDescription: `Consume old permission token #${i + 1}`
|
|
2719
|
+
})),
|
|
2720
|
+
outputs: built.map(b => b.output),
|
|
2721
|
+
options: {
|
|
2722
|
+
acceptDelayedBroadcast: true,
|
|
2723
|
+
randomizeOutputs: false,
|
|
2724
|
+
signAndProcess: false
|
|
2725
|
+
}
|
|
2726
|
+
},
|
|
2727
|
+
this.adminOriginator
|
|
2728
|
+
)
|
|
2729
|
+
|
|
2730
|
+
if (!signableTransaction?.reference || !signableTransaction.tx) {
|
|
2731
|
+
throw new Error('Failed to create signable transaction')
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
const partialTx = Transaction.fromAtomicBEEF(signableTransaction.tx)
|
|
2735
|
+
const pushdrop = new PushDrop(this.underlying)
|
|
2736
|
+
const spends: Record<number, { unlockingScript: string }> = {}
|
|
2737
|
+
|
|
2738
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
2739
|
+
const token = chunk[i].oldToken
|
|
2740
|
+
const unlocker = pushdrop.unlock(
|
|
2741
|
+
WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL,
|
|
2742
|
+
'1',
|
|
2743
|
+
'self',
|
|
2744
|
+
'all',
|
|
2745
|
+
false,
|
|
2746
|
+
1,
|
|
2747
|
+
LockingScript.fromHex(token.outputScript)
|
|
2748
|
+
)
|
|
2749
|
+
const unlockingScript = await unlocker.sign(partialTx, i)
|
|
2750
|
+
spends[i] = { unlockingScript: unlockingScript.toHex() }
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
const { txid } = await this.underlying.signAction({
|
|
2754
|
+
reference: signableTransaction.reference,
|
|
2755
|
+
spends
|
|
2756
|
+
})
|
|
2757
|
+
if (!txid) throw new Error('Failed to finalize renewal transaction')
|
|
2758
|
+
return built.map(b => b.request)
|
|
2759
|
+
})
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2584
2762
|
private async coalescePermissionTokens(
|
|
2585
2763
|
oldTokens: PermissionToken[],
|
|
2586
2764
|
newScript: LockingScript,
|
|
@@ -2618,7 +2796,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
2618
2796
|
}
|
|
2619
2797
|
],
|
|
2620
2798
|
options: {
|
|
2621
|
-
acceptDelayedBroadcast:
|
|
2799
|
+
acceptDelayedBroadcast: true,
|
|
2622
2800
|
randomizeOutputs: false,
|
|
2623
2801
|
signAndProcess: false
|
|
2624
2802
|
}
|
|
@@ -2730,7 +2908,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
2730
2908
|
}
|
|
2731
2909
|
],
|
|
2732
2910
|
options: {
|
|
2733
|
-
acceptDelayedBroadcast:
|
|
2911
|
+
acceptDelayedBroadcast: true
|
|
2734
2912
|
}
|
|
2735
2913
|
},
|
|
2736
2914
|
this.adminOriginator
|
|
@@ -2898,7 +3076,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
2898
3076
|
tags,
|
|
2899
3077
|
tagQueryMode: 'all',
|
|
2900
3078
|
include: 'entire transactions',
|
|
2901
|
-
limit:
|
|
3079
|
+
limit: 10000
|
|
2902
3080
|
},
|
|
2903
3081
|
this.adminOriginator
|
|
2904
3082
|
)
|
|
@@ -3245,12 +3423,65 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
3245
3423
|
}
|
|
3246
3424
|
],
|
|
3247
3425
|
options: {
|
|
3248
|
-
acceptDelayedBroadcast:
|
|
3426
|
+
acceptDelayedBroadcast: true
|
|
3249
3427
|
}
|
|
3250
3428
|
},
|
|
3251
3429
|
this.adminOriginator
|
|
3252
3430
|
)
|
|
3253
3431
|
const tx = Transaction.fromBEEF(signableTransaction!.tx)
|
|
3432
|
+
|
|
3433
|
+
const normalizeTxid = (txid?: string) => (txid ?? '').toLowerCase()
|
|
3434
|
+
const reverseHexTxid = (txid: string) => {
|
|
3435
|
+
const hex = normalizeTxid(txid)
|
|
3436
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) return hex
|
|
3437
|
+
const bytes = hex.match(/../g)
|
|
3438
|
+
return bytes ? bytes.reverse().join('') : hex
|
|
3439
|
+
}
|
|
3440
|
+
const matchesOutpointString = (outpoint: string) => {
|
|
3441
|
+
const dot = outpoint.lastIndexOf('.')
|
|
3442
|
+
const colon = outpoint.lastIndexOf(':')
|
|
3443
|
+
const sep = dot > colon ? dot : colon
|
|
3444
|
+
if (sep === -1) return false
|
|
3445
|
+
const txidPart = outpoint.slice(0, sep)
|
|
3446
|
+
const indexPart = outpoint.slice(sep + 1)
|
|
3447
|
+
const vout = Number(indexPart)
|
|
3448
|
+
if (!Number.isFinite(vout)) return false
|
|
3449
|
+
return normalizeTxid(txidPart) === normalizeTxid(oldToken.txid) && vout === oldToken.outputIndex
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
let permInputIndex = tx.inputs.findIndex((input: any) => {
|
|
3453
|
+
const txidCandidate: unknown =
|
|
3454
|
+
input?.sourceTXID ??
|
|
3455
|
+
input?.sourceTxid ??
|
|
3456
|
+
input?.sourceTxId ??
|
|
3457
|
+
input?.prevTxId ??
|
|
3458
|
+
input?.prevTxid ??
|
|
3459
|
+
input?.prevTXID ??
|
|
3460
|
+
input?.txid ??
|
|
3461
|
+
input?.txID
|
|
3462
|
+
|
|
3463
|
+
const voutCandidate: unknown =
|
|
3464
|
+
input?.sourceOutputIndex ?? input?.sourceOutput ?? input?.outputIndex ?? input?.vout ?? input?.prevOutIndex
|
|
3465
|
+
|
|
3466
|
+
if (typeof txidCandidate === 'string' && typeof voutCandidate === 'number') {
|
|
3467
|
+
const cand = normalizeTxid(txidCandidate)
|
|
3468
|
+
const target = normalizeTxid(oldToken.txid)
|
|
3469
|
+
if (cand === target && voutCandidate === oldToken.outputIndex) return true
|
|
3470
|
+
if (cand === reverseHexTxid(oldToken.txid) && voutCandidate === oldToken.outputIndex) return true
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
const outpointCandidate: unknown = input?.outpoint ?? input?.sourceOutpoint ?? input?.prevOutpoint
|
|
3474
|
+
if (typeof outpointCandidate === 'string' && matchesOutpointString(outpointCandidate)) return true
|
|
3475
|
+
|
|
3476
|
+
return false
|
|
3477
|
+
})
|
|
3478
|
+
|
|
3479
|
+
if (permInputIndex === -1 && tx.inputs.length === 1) {
|
|
3480
|
+
permInputIndex = 0
|
|
3481
|
+
}
|
|
3482
|
+
if (permInputIndex === -1) {
|
|
3483
|
+
throw new Error('Unable to locate permission token input for revocation.')
|
|
3484
|
+
}
|
|
3254
3485
|
const unlocker = new PushDrop(this.underlying).unlock(
|
|
3255
3486
|
WalletPermissionsManager.PERM_TOKEN_ENCRYPTION_PROTOCOL,
|
|
3256
3487
|
'1',
|
|
@@ -3260,11 +3491,11 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
3260
3491
|
1,
|
|
3261
3492
|
LockingScript.fromHex(oldToken.outputScript)
|
|
3262
3493
|
)
|
|
3263
|
-
const unlockingScript = await unlocker.sign(tx,
|
|
3494
|
+
const unlockingScript = await unlocker.sign(tx, permInputIndex)
|
|
3264
3495
|
await this.underlying.signAction({
|
|
3265
3496
|
reference: signableTransaction!.reference,
|
|
3266
3497
|
spends: {
|
|
3267
|
-
|
|
3498
|
+
[permInputIndex]: {
|
|
3268
3499
|
unlockingScript: unlockingScript.toHex()
|
|
3269
3500
|
}
|
|
3270
3501
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { _tu } from '../../../test/utils/TestUtilsWalletStorage'
|
|
1
2
|
import { Services } from '../../index.client'
|
|
2
3
|
|
|
3
4
|
describe('getRawTx service tests', () => {
|
|
4
5
|
jest.setTimeout(99999999)
|
|
5
6
|
|
|
6
7
|
test('0', async () => {
|
|
8
|
+
if (_tu.noEnv('test')) return
|
|
7
9
|
const options = Services.createDefaultOptions('test')
|
|
8
10
|
const services = new Services(options)
|
|
9
11
|
|
|
@@ -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
|
}
|