@bsv/overlay 2.2.0 → 2.2.7

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 (155) hide show
  1. package/LICENSE.txt +57 -27
  2. package/README.md +102 -266
  3. package/dist/cjs/mod.d.ts +15 -0
  4. package/dist/cjs/mod.d.ts.map +1 -0
  5. package/dist/cjs/mod.js +4 -1
  6. package/dist/cjs/mod.js.map +1 -1
  7. package/dist/cjs/package.json +46 -18
  8. package/dist/cjs/src/Advertisement.d.ts +9 -0
  9. package/dist/cjs/src/Advertisement.d.ts.map +1 -0
  10. package/dist/cjs/src/Advertiser.d.ts +36 -0
  11. package/dist/cjs/src/Advertiser.d.ts.map +1 -0
  12. package/dist/cjs/src/BASM.d.ts +110 -0
  13. package/dist/cjs/src/BASM.d.ts.map +1 -0
  14. package/dist/cjs/src/BASM.js.map +1 -1
  15. package/dist/cjs/src/BASMRemote.d.ts +14 -0
  16. package/dist/cjs/src/BASMRemote.d.ts.map +1 -0
  17. package/dist/cjs/src/BASMRemote.js.map +1 -1
  18. package/dist/cjs/src/Engine.d.ts +377 -0
  19. package/dist/cjs/src/Engine.d.ts.map +1 -0
  20. package/dist/cjs/src/Engine.js +430 -436
  21. package/dist/cjs/src/Engine.js.map +1 -1
  22. package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts +24 -0
  23. package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts.map +1 -0
  24. package/dist/cjs/src/GASP/OverlayGASPRemote.js +7 -10
  25. package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
  26. package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts +103 -0
  27. package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts.map +1 -0
  28. package/dist/cjs/src/GASP/OverlayGASPStorage.js +34 -31
  29. package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
  30. package/dist/cjs/src/LookupFormula.d.ts +29 -0
  31. package/dist/cjs/src/LookupFormula.d.ts.map +1 -0
  32. package/dist/cjs/src/LookupService.d.ts +85 -0
  33. package/dist/cjs/src/LookupService.d.ts.map +1 -0
  34. package/dist/cjs/src/Output.d.ts +32 -0
  35. package/dist/cjs/src/Output.d.ts.map +1 -0
  36. package/dist/cjs/src/SafeLog.d.ts +14 -0
  37. package/dist/cjs/src/SafeLog.d.ts.map +1 -0
  38. package/dist/cjs/src/SafeLog.js +44 -0
  39. package/dist/cjs/src/SafeLog.js.map +1 -0
  40. package/{src/SyncConfiguration.ts → dist/cjs/src/SyncConfiguration.d.ts} +2 -1
  41. package/dist/cjs/src/SyncConfiguration.d.ts.map +1 -0
  42. package/dist/cjs/src/TopicManager.d.ts +35 -0
  43. package/dist/cjs/src/TopicManager.d.ts.map +1 -0
  44. package/dist/cjs/src/storage/Storage.d.ts +237 -0
  45. package/dist/cjs/src/storage/Storage.d.ts.map +1 -0
  46. package/dist/cjs/src/storage/knex/KnexStorage.d.ts +69 -0
  47. package/dist/cjs/src/storage/knex/KnexStorage.d.ts.map +1 -0
  48. package/dist/cjs/src/storage/knex/KnexStorage.js +7 -10
  49. package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
  50. package/dist/cjs/src/storage/knex/all-migrations.d.ts +11 -0
  51. package/dist/cjs/src/storage/knex/all-migrations.d.ts.map +1 -0
  52. package/dist/cjs/src/storage/knex/all-migrations.js.map +1 -1
  53. package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.d.ts +4 -0
  54. package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.d.ts.map +1 -0
  55. package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.js.map +1 -1
  56. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts +4 -0
  57. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts.map +1 -0
  58. package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -1
  59. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts +4 -0
  60. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -0
  61. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js +36 -12
  62. package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
  63. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts +4 -0
  64. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts.map +1 -0
  65. package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -1
  66. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.d.ts +4 -0
  67. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.d.ts.map +1 -0
  68. package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.js.map +1 -1
  69. package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.d.ts +4 -0
  70. package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.d.ts.map +1 -0
  71. package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.js.map +1 -1
  72. package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.d.ts +4 -0
  73. package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.d.ts.map +1 -0
  74. package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.js.map +1 -1
  75. package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.d.ts +9 -0
  76. package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.d.ts.map +1 -0
  77. package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.js.map +1 -1
  78. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts +4 -0
  79. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts.map +1 -0
  80. package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.js.map +1 -1
  81. package/dist/esm/mod.js +2 -1
  82. package/dist/esm/mod.js.map +1 -1
  83. package/dist/esm/src/BASMRemote.js.map +1 -1
  84. package/dist/esm/src/Engine.js +408 -414
  85. package/dist/esm/src/Engine.js.map +1 -1
  86. package/dist/esm/src/GASP/OverlayGASPRemote.js +2 -2
  87. package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
  88. package/dist/esm/src/GASP/OverlayGASPStorage.js +23 -19
  89. package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
  90. package/dist/esm/src/SafeLog.js +40 -0
  91. package/dist/esm/src/SafeLog.js.map +1 -0
  92. package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
  93. package/dist/esm/src/storage/knex/migrations/2024-07-17-001-transactions.js +36 -12
  94. package/dist/esm/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
  95. package/dist/types/mod.d.ts +5 -4
  96. package/dist/types/mod.d.ts.map +1 -1
  97. package/dist/types/src/BASMRemote.d.ts.map +1 -1
  98. package/dist/types/src/Engine.d.ts +15 -0
  99. package/dist/types/src/Engine.d.ts.map +1 -1
  100. package/dist/types/src/GASP/OverlayGASPRemote.d.ts +2 -2
  101. package/dist/types/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
  102. package/dist/types/src/GASP/OverlayGASPStorage.d.ts +3 -1
  103. package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
  104. package/dist/types/src/SafeLog.d.ts +14 -0
  105. package/dist/types/src/SafeLog.d.ts.map +1 -0
  106. package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
  107. package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
  108. package/dist/types/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -1
  109. package/package.json +87 -30
  110. package/dist/cjs/tsconfig.cjs.tsbuildinfo +0 -1
  111. package/dist/esm/tsconfig.esm.tsbuildinfo +0 -1
  112. package/dist/types/tsconfig.types.tsbuildinfo +0 -1
  113. package/docs/API.md +0 -944
  114. package/docs/BRC-136-BASM.md +0 -327
  115. package/docs/README.md +0 -16
  116. package/docs/Synchronization.md +0 -212
  117. package/docs/concepts/00-overview.md +0 -85
  118. package/docs/concepts/01-best-practices.md +0 -202
  119. package/docs/concepts/02-query-performance.md +0 -345
  120. package/docs/concepts/03-database-monitoring.md +0 -211
  121. package/docs/concepts/04-pagination-example.md +0 -186
  122. package/docs/concepts/05-recommendations-summary.md +0 -158
  123. package/docs/concepts/README.md +0 -20
  124. package/docs/examples/README.md +0 -15
  125. package/docs/examples/gs-wip.md +0 -111
  126. package/docs/internal/README.md +0 -11
  127. package/mod.ts +0 -40
  128. package/src/Advertisement.ts +0 -8
  129. package/src/Advertiser.ts +0 -41
  130. package/src/BASM.ts +0 -208
  131. package/src/BASMRemote.ts +0 -54
  132. package/src/Engine.ts +0 -2199
  133. package/src/GASP/OverlayGASPRemote.ts +0 -108
  134. package/src/GASP/OverlayGASPStorage.ts +0 -383
  135. package/src/LookupFormula.ts +0 -31
  136. package/src/LookupService.ts +0 -136
  137. package/src/Output.ts +0 -31
  138. package/src/TopicManager.ts +0 -40
  139. package/src/__tests/BASM.test.ts +0 -44
  140. package/src/__tests/BASMChain.test.ts +0 -290
  141. package/src/__tests/Engine.test.ts +0 -1345
  142. package/src/__tests/OverlayGASPRemote.test.ts +0 -135
  143. package/src/__tests/OverlayGASPStorage.test.ts +0 -171
  144. package/src/storage/Storage.ts +0 -260
  145. package/src/storage/knex/KnexStorage.ts +0 -651
  146. package/src/storage/knex/all-migrations.ts +0 -32
  147. package/src/storage/knex/migrations/2024-05-18-001-initial.ts +0 -30
  148. package/src/storage/knex/migrations/2024-07-10-001-block-height.ts +0 -15
  149. package/src/storage/knex/migrations/2024-07-17-001-transactions.ts +0 -40
  150. package/src/storage/knex/migrations/2024-07-18-001-indexes.ts +0 -30
  151. package/src/storage/knex/migrations/2025-05-28-001-enlarge.ts +0 -42
  152. package/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.ts +0 -46
  153. package/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.ts +0 -24
  154. package/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.ts +0 -18
  155. package/src/storage/knex/migrations/2026-05-29-001-brc136-basm.ts +0 -123
@@ -1,1345 +0,0 @@
1
- // Note: References Engine from dist due to issues with compiled code references in Engine.ts
2
- import { Engine } from '../../dist/cjs/src/Engine.js'
3
- import { LookupService } from '../LookupService'
4
- import { TopicManager } from '../TopicManager'
5
- import { Storage } from '../storage/Storage'
6
- import { Transaction, Utils, TaggedBEEF, AdmittanceInstructions, STEAK } from '@bsv/sdk'
7
- import { Output } from '../Output'
8
- import { SyncConfiguration } from '../SyncConfiguration'
9
- import { Advertiser } from '../Advertiser'
10
-
11
- const mockChainTracker = {
12
- isValidRootForHeight: jest.fn(async () => true),
13
- currentHeight: jest.fn(async () => 800000)
14
- }
15
-
16
- // Call the method that would normally trigger syncAdvertisements
17
- const engineSubmit = jest.fn(async (taggedBEEF: TaggedBEEF, onSteakReady: any, mode?: string): Promise<STEAK> => {
18
- const result: STEAK = { tm_helloworld: { outputsToAdmit: [], coinsToRetain: [], coinsRemoved: [] } }
19
- return result
20
- })
21
-
22
- const BRC62Hex = '0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000'
23
- const exampleTX = Transaction.fromHexBEEF(BRC62Hex)
24
-
25
- const exampleBeef = exampleTX.toBEEF()
26
- const exampleTXID = exampleTX.id('hex')
27
- const examplePreviousTXID = '3ecead27a44d013ad1aae40038acbb1883ac9242406808bb4667c15b4f164eac'
28
- let mockTopicManager: TopicManager, mockLookupService: LookupService, mockStorageEngine: Storage
29
- const mockOutput: Output = {
30
- txid: exampleTXID,
31
- outputIndex: 0,
32
- outputScript: exampleTX.outputs[0].lockingScript.toBinary(),
33
- topic: 'hello',
34
- satoshis: exampleTX.outputs[0].satoshis,
35
- beef: exampleBeef,
36
- spent: false,
37
- outputsConsumed: [],
38
- consumedBy: [],
39
- score: 1234567890
40
- }
41
-
42
- const invalidHostingUrls = [
43
- 'http://example.com', // Invalid: http
44
- 'https://localhost:3000', // Invalid: localhost
45
- 'https://192.168.1.1', // Invalid: internal private IP
46
- 'https://127.0.0.1', // Invalid: loopback IP
47
- 'https://0.0.0.0', // Invalid: non-routable IP
48
- 'http://172.16.0.1', // Invalid: private IP
49
- '[::1]'
50
- ]
51
-
52
- const validHostingUrls = [
53
- 'https://example.com', // Valid: public URL
54
- 'https://8.8.8.8', // Valid: public routable IP
55
- 'https://255.255.255.255' // Valid: public routable IP
56
- ]
57
-
58
- const mockAdvertiser: Advertiser = {
59
- createAdvertisements: jest.fn(async (): Promise<TaggedBEEF> => ({ beef: [0, 1, 2], topics: [] })),
60
- revokeAdvertisements: jest.fn(async (): Promise<TaggedBEEF> => ({ beef: [0, 1, 2], topics: [] })),
61
- findAllAdvertisements: jest.fn(async () => []),
62
- parseAdvertisement: jest.fn()
63
- }
64
-
65
- describe('BSV Overlay Services Engine', () => {
66
- beforeEach(() => {
67
- jest.clearAllMocks()
68
-
69
- mockTopicManager = {
70
- identifyAdmissibleOutputs: jest.fn(async (): Promise<AdmittanceInstructions> => ({
71
- outputsToAdmit: [0],
72
- coinsToRetain: []
73
- })),
74
- getDocumentation: async () => 'Topical Documentation',
75
- getMetaData: async () => ({ name: 'Mock Manager', shortDescription: 'Mock Short Manager Description' })
76
- }
77
-
78
- mockLookupService = {
79
- outputAdmittedByTopic: jest.fn(),
80
- outputSpent: jest.fn(),
81
- lookup: jest.fn(),
82
- outputNoLongerRetainedInHistory: jest.fn(),
83
- outputEvicted: jest.fn(),
84
- admissionMode: 'locking-script',
85
- spendNotificationMode: 'none',
86
- getDocumentation: async () => 'Service Documentation',
87
- getMetaData: async () => ({ name: 'Mock Service', shortDescription: 'Mock Short Service Description' })
88
- }
89
-
90
- mockStorageEngine = {
91
- doesAppliedTransactionExist: jest.fn(async () => false),
92
- insertAppliedTransaction: jest.fn(),
93
- insertOutput: jest.fn(),
94
- findOutput: jest.fn(async () => null),
95
- findOutputsForTransaction: jest.fn(async () => []),
96
- markUTXOAsSpent: jest.fn(),
97
- updateConsumedBy: jest.fn(),
98
- updateTransactionBEEF: jest.fn(),
99
- deleteOutput: jest.fn(),
100
- findUTXOsForTopic: jest.fn(),
101
- updateLastInteraction: jest.fn(),
102
- getLastInteraction: jest.fn(async () => 0)
103
- }
104
- })
105
-
106
- it('engine.syncAdvertisements should return void when invalid hostingURL is provided', async () => {
107
- for (const url of invalidHostingUrls) {
108
- const engine = new Engine(
109
- { tm_helloworld: mockTopicManager },
110
- { ls_helloworld: mockLookupService },
111
- mockStorageEngine,
112
- mockChainTracker,
113
- url, // Invalid hostingURL
114
- ['tracker1'], // shipTrackers
115
- ['tracker2'], // slapTrackers
116
- undefined,
117
- mockAdvertiser,
118
- undefined
119
- )
120
-
121
- engine.submit = engineSubmit
122
-
123
- // Call the method that would normally trigger syncAdvertisements
124
- const result = await engine.syncAdvertisements()
125
-
126
- expect(result).toBeUndefined()
127
-
128
- // Verify that Advertiser methods are NOT called
129
- expect(mockAdvertiser.createAdvertisements).not.toHaveBeenCalled()
130
- expect(mockAdvertiser.findAllAdvertisements).not.toHaveBeenCalledWith('SHIP') // Assuming 'SHIP' is expected
131
- }
132
- })
133
-
134
- it('should allow engine.syncAdvertisements to proceed with valid hostingURLs', async () => {
135
- const mockAdvertiser: Advertiser = {
136
- createAdvertisements: jest.fn().mockResolvedValue({ tag: 'MOCK_TAG', data: Buffer.from('mock data') }),
137
- findAllAdvertisements: jest.fn().mockResolvedValue([]),
138
- revokeAdvertisements: jest.fn().mockResolvedValue({ tag: 'MOCK_REVOKE_TAG', data: Buffer.from('revocation data') }),
139
- parseAdvertisement: jest.fn().mockReturnValue({
140
- protocol: 'SHIP',
141
- topicOrServiceName: 'mock-topic',
142
- timestamp: Date.now()
143
- })
144
- }
145
-
146
- for (const url of validHostingUrls) {
147
- const engine = new Engine(
148
- { tm_helloworld: mockTopicManager },
149
- { ls_helloworld: mockLookupService },
150
- mockStorageEngine,
151
- mockChainTracker,
152
- url, // Valid hostingURL
153
- ['tracker1'], // shipTrackers
154
- ['tracker2'], // slapTrackers
155
- undefined,
156
- mockAdvertiser, // Pass the mock Advertiser
157
- undefined
158
- )
159
-
160
- engine.submit = engineSubmit
161
- await engine.syncAdvertisements()
162
- expect(engine.submit).toHaveBeenCalled()
163
-
164
- // Verify that Advertiser methods are called
165
- expect(mockAdvertiser.createAdvertisements).toHaveBeenCalled()
166
- expect(mockAdvertiser.findAllAdvertisements).toHaveBeenCalledWith('SHIP') // Assuming 'SHIP' is expected
167
- }
168
- })
169
-
170
- it('refreshes old unproven transaction proofs before eviction', async () => {
171
- const storage = {
172
- ...mockStorageEngine,
173
- findUnprovenAppliedTransactions: jest.fn(async () => [
174
- {
175
- txid: exampleTXID,
176
- topic: 'Hello',
177
- firstSeenHeight: 799000,
178
- outputs: [{ txid: exampleTXID, outputIndex: 0 }]
179
- }
180
- ])
181
- }
182
- const engine = new Engine(
183
- { tm_helloworld: mockTopicManager },
184
- { ls_helloworld: mockLookupService },
185
- storage,
186
- mockChainTracker,
187
- 'https://example.com'
188
- )
189
- const merklePath = {} as any
190
- ;(engine as any).handleNewMerkleProof = jest.fn(async () => undefined)
191
-
192
- const report = await engine.refreshUnprovenTransactionProofs({
193
- thresholdBlocks: 144,
194
- proofProvider: jest.fn(async () => ({ merklePath, blockHeight: 799900 }))
195
- })
196
-
197
- expect(report.refreshedTransactions).toBe(1)
198
- expect(report.missingProofs).toBe(0)
199
- expect((engine as any).handleNewMerkleProof).toHaveBeenCalledWith(exampleTXID, merklePath, 799900)
200
- })
201
-
202
- it('evicts provider-invalidated applied transactions', async () => {
203
- const deleteAppliedTransaction = jest.fn(async () => undefined)
204
- const deleteOutput = jest.fn(async () => undefined)
205
- const engine = new Engine(
206
- { tm_helloworld: mockTopicManager },
207
- { ls_helloworld: mockLookupService },
208
- {
209
- ...mockStorageEngine,
210
- findOutputsForTransaction: jest.fn(async () => [
211
- {
212
- ...mockOutput,
213
- topic: 'Hello'
214
- }
215
- ]),
216
- deleteOutput,
217
- deleteAppliedTransaction
218
- },
219
- mockChainTracker,
220
- 'https://example.com'
221
- )
222
-
223
- const report = await engine.evictAppliedTransaction(exampleTXID, {
224
- reason: 'DOUBLE_SPEND_ATTEMPTED'
225
- })
226
-
227
- expect(report.evictedTransactions).toBe(1)
228
- expect(report.evictedOutputs).toBe(1)
229
- expect(mockLookupService.outputEvicted).toHaveBeenCalledWith(exampleTXID, 0)
230
- expect(deleteOutput).toHaveBeenCalledWith(exampleTXID, 0, 'Hello')
231
- expect(deleteAppliedTransaction).toHaveBeenCalledWith(exampleTXID, 'Hello')
232
- })
233
-
234
- it('Uses SHIP sync configuration by default if no syncConfiguration was provided', () => {
235
- const engine = new Engine(
236
- { tm_helloworld: mockTopicManager },
237
- { ls_helloworld: mockLookupService },
238
- mockStorageEngine,
239
- mockChainTracker,
240
- undefined, // hostingURL
241
- ['tracker1'], // shipTrackers
242
- ['tracker2'], // slapTrackers
243
- undefined,
244
- undefined,
245
- undefined
246
- )
247
-
248
- expect(engine.syncConfiguration).toEqual({ tm_helloworld: 'SHIP' })
249
- })
250
-
251
- it('Does not set sync method to "SHIP" for topic managers set to false in the syncConfiguration', () => {
252
- const syncConfiguration: SyncConfiguration = { tm_helloworld: false }
253
- const engine = new Engine(
254
- { tm_helloworld: mockTopicManager },
255
- { ls_helloworld: mockLookupService },
256
- mockStorageEngine,
257
- mockChainTracker,
258
- undefined, // hostingURL
259
- ['tracker1'], // shipTrackers
260
- ['tracker2'], // slapTrackers
261
- undefined,
262
- undefined,
263
- syncConfiguration
264
- )
265
-
266
- expect(engine.syncConfiguration).toEqual({ tm_helloworld: false })
267
- })
268
-
269
- it('Combines existing trackers with provided shipTrackers and slapTrackers, ensuring no duplicates', () => {
270
- const syncConfiguration: SyncConfiguration = { tm_ship: ['existingTracker1'], tm_slap: ['existingTracker2'] }
271
- const engine = new Engine(
272
- { tm_ship: mockTopicManager, tm_slap: mockTopicManager },
273
- { ls_ship: mockLookupService, ls_slap: mockLookupService },
274
- mockStorageEngine,
275
- mockChainTracker,
276
- undefined, // hostingURL
277
- ['tracker1', 'existingTracker1'], // shipTrackers
278
- ['tracker2', 'existingTracker2'], // slapTrackers
279
- undefined,
280
- undefined,
281
- syncConfiguration
282
- )
283
-
284
- expect(engine.syncConfiguration).toEqual({
285
- tm_ship: ['existingTracker1', 'tracker1'],
286
- tm_slap: ['existingTracker2', 'tracker2']
287
- })
288
- })
289
-
290
- it('Sets undefined topic managers in syncConfiguration to sync method of "SHIP" by default', () => {
291
- const syncConfiguration: SyncConfiguration = { tm_helloworld: 'SHIP' }
292
- const engine = new Engine(
293
- { tm_helloworld: mockTopicManager, tm_ship: mockTopicManager, tm_slap: mockTopicManager },
294
- { ls_helloworld: mockLookupService, ls_ship: mockLookupService, ls_slap: mockLookupService },
295
- mockStorageEngine,
296
- mockChainTracker,
297
- undefined, // hostingURL
298
- ['tracker1'], // shipTrackers
299
- ['tracker2'], // slapTrackers
300
- undefined,
301
- undefined,
302
- syncConfiguration
303
- )
304
-
305
- expect(engine.syncConfiguration).toEqual({
306
- tm_helloworld: 'SHIP',
307
- tm_ship: ['tracker1'],
308
- tm_slap: ['tracker2']
309
- })
310
- })
311
- describe('handleNewMerkleProof tests', () => {
312
- it('0 simple proof', async () => {
313
- const beef = beef27c8f1
314
- const txid = txid27c8f
315
- const tx = Transaction.fromHexBEEF(beef)
316
-
317
- const output27c8fZero: Output = {
318
- txid,
319
- outputIndex: 0,
320
- outputScript: tx.outputs[0].lockingScript.toBinary(),
321
- topic: 'hello',
322
- satoshis: tx.outputs[0].satoshis,
323
- beef: tx.toBEEF(),
324
- spent: false,
325
- outputsConsumed: [],
326
- consumedBy: [],
327
- score: Date.now()
328
- }
329
-
330
- mockLookupService.lookup = jest.fn(async () => [{
331
- txid,
332
- outputIndex: 0,
333
- history: 1
334
- }])
335
- let newBEEF: number[] = []
336
- mockStorageEngine.findOutput = jest.fn(async () => output27c8fZero)
337
- mockStorageEngine.findOutputsForTransaction = jest.fn(async () => [output27c8fZero])
338
- mockStorageEngine.updateTransactionBEEF = jest.fn(async (txid: string, beef: number[]) => {
339
- newBEEF = beef
340
- })
341
- const engine = new Engine(
342
- {
343
- Hello: mockTopicManager
344
- },
345
- {
346
- Hello: mockLookupService
347
- },
348
- mockStorageEngine,
349
- mockChainTracker
350
- )
351
-
352
- const merklePath = Transaction.fromHexBEEF(beef27c8f0).merklePath
353
- if (merklePath == null) throw new Error('improper test setup')
354
- await engine.handleNewMerkleProof(txid, merklePath)
355
- expect(newBEEF.length > 0 && newBEEF.length < beef.length / 2).toBe(true)
356
- })
357
-
358
- it('1 recurse proof', async () => {
359
- const outputs: Output[] = []
360
- const findOutput = (txid: string, outputIndex: number, includeBEEF?: boolean): Output => {
361
- const i = outputs.findIndex(o => o.txid === txid && o.outputIndex === outputIndex)
362
- if (i < 0) throw new Error(`missing output ${txid} ${outputIndex}`)
363
- return outputs[i]
364
- }
365
-
366
- const addConsumingOutput = (tx: Transaction, outputIndex: number, consumes?: Output): Output => {
367
- const o: Output = {
368
- txid: tx.id('hex'),
369
- outputIndex,
370
- outputScript: tx.outputs[outputIndex].lockingScript.toBinary(),
371
- topic: 'hello',
372
- satoshis: tx.outputs[outputIndex].satoshis,
373
- beef: tx.toBEEF(),
374
- spent: false,
375
- outputsConsumed: [],
376
- consumedBy: [],
377
- score: Date.now()
378
- }
379
- if (consumes != null) {
380
- const c = findOutput(consumes.txid, consumes.outputIndex)
381
- c.consumedBy.push(o)
382
- o.outputsConsumed.push(c)
383
- }
384
- outputs.push(o)
385
- return o
386
- }
387
-
388
- mockLookupService.lookup = jest.fn(async () => [{ txid: txid17d182, outputIndex: 0, history: 1 }])
389
- const newBEEF: Record<string, string> = {}
390
- mockStorageEngine.findOutput = jest.fn(async (txid: string, outputIndex: number, topic?: string, spent?: boolean, includeBEEF?: boolean) => {
391
- return findOutput(txid, outputIndex, true)
392
- })
393
- mockStorageEngine.findOutputsForTransaction = jest.fn(async (txid: string, includeBEEF?: boolean) => {
394
- const os = outputs.filter(o => o.txid === txid)
395
- return os
396
- })
397
- mockStorageEngine.updateTransactionBEEF = jest.fn(async (txid: string, beef: number[]) => {
398
- newBEEF[txid] = Utils.toHex(beef)
399
- })
400
- const engine = new Engine({ Hello: mockTopicManager }, { Hello: mockLookupService }, mockStorageEngine, mockChainTracker)
401
-
402
- /*
403
- txid 17d1829ba8424b97369ee8b528ee8b65d9d4b9c08d037d224a7be7c025f78f78 output 0 1196 sats
404
- txid 9426201003b01e5bb66e9240a1cc337174238e816a25a06ba532fa67af4457d7 output 0 1197 sats
405
- txid 509f5ef79c18504fdfe21e67608f56bacb8d8a200634e46f60a9dc8430dcd6e9 output 0 1198 sats
406
- txid 877734db8eb917d0e2174a4bdddc085b34f67cedd6b94628c4feb1b979a2b2c5 output 0 1199 sats
407
- txid 37abad7168d47d4e107d0f9f96813a4feef6ea346482fce554c3fade85d45409 output 0 1200 sats
408
- txid d786db393ec7f1315bee2cbd3aa47634394f57c861feed8c075563308f14c237 output 0 1568 sats
409
- txid a7d4cac391f5b3d8dedaacb3cd8b5afac54df2d8e043ae0452abcfe190686494 output 17 1132 sats
410
- */
411
-
412
- const tx17d182 = Transaction.fromHexBEEF(beef17d1824)
413
- const tx942620 = tx17d182.inputs[0].sourceTransaction
414
- const tx509f5e = tx942620.inputs[0].sourceTransaction
415
- const tx877734 = tx509f5e.inputs[0].sourceTransaction
416
- const tx37abad = tx877734.inputs[0].sourceTransaction
417
-
418
- const output37abadZero = addConsumingOutput(tx37abad, 0)
419
- const output877734Zero = addConsumingOutput(tx877734, 0, output37abadZero)
420
- const output509f5eZero = addConsumingOutput(tx509f5e, 0, output877734Zero)
421
- const output942620Zero = addConsumingOutput(tx942620, 0, output509f5eZero)
422
- addConsumingOutput(tx17d182, 0, output942620Zero)
423
-
424
- const mp37abad = Transaction.fromHexBEEF(beef37abad0).merklePath
425
- await engine.handleNewMerkleProof(txid37abad, mp37abad)
426
- expect(Object.keys(newBEEF).length).toBe(0)
427
-
428
- const mp877734 = Transaction.fromHexBEEF(beef8777340).merklePath
429
- await engine.handleNewMerkleProof(txid877734, mp877734)
430
- expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
431
- expect(newBEEF[`${txid509f5e}`].length).toBeGreaterThan(beef509f5e0.length)
432
- expect(newBEEF[`${txid942620}`].length).toBeGreaterThan(beef9426200.length)
433
- expect(newBEEF[`${txid17d182}`].length).toBeGreaterThan(beef17d1820.length)
434
- expect(Object.keys(newBEEF).length).toBe(4)
435
-
436
- const mp509f5e = Transaction.fromHexBEEF(beef509f5e0).merklePath
437
- await engine.handleNewMerkleProof(txid509f5e, mp509f5e)
438
- expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
439
- expect(newBEEF[`${txid509f5e}`]).toBe(beef509f5e0)
440
- expect(newBEEF[`${txid942620}`].length).toBeGreaterThan(beef9426200.length)
441
- expect(newBEEF[`${txid17d182}`].length).toBeGreaterThan(beef17d1820.length)
442
- expect(Object.keys(newBEEF).length).toBe(4)
443
-
444
- const mp942620 = Transaction.fromHexBEEF(beef9426200).merklePath
445
- await engine.handleNewMerkleProof(txid942620, mp942620)
446
- expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
447
- expect(newBEEF[`${txid509f5e}`]).toBe(beef509f5e0)
448
- expect(newBEEF[`${txid942620}`]).toBe(beef9426200)
449
- expect(newBEEF[`${txid17d182}`].length).toBeGreaterThan(beef17d1820.length)
450
- expect(Object.keys(newBEEF).length).toBe(4)
451
-
452
- const mp17d182 = Transaction.fromHexBEEF(beef17d1820).merklePath
453
- await engine.handleNewMerkleProof(txid17d182, mp17d182)
454
- expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
455
- expect(newBEEF[`${txid509f5e}`]).toBe(beef509f5e0)
456
- expect(newBEEF[`${txid942620}`]).toBe(beef9426200)
457
- expect(newBEEF[`${txid17d182}`]).toBe(beef17d1820)
458
- expect(Object.keys(newBEEF).length).toBe(4)
459
- })
460
- })
461
-
462
- describe('submit', () => {
463
- it('Throws an error if the user submits to a topic that is not supported', async () => {
464
- const engine = new Engine(
465
- {
466
- Hello: mockTopicManager
467
- },
468
- {},
469
- mockStorageEngine,
470
- mockChainTracker
471
- )
472
- await expect(engine.submit({
473
- beef: exampleBeef,
474
- topics: ['hello']
475
- })).rejects.toHaveProperty('message', 'This server does not support this topic: hello')
476
- })
477
- it('Verifies the BEEF for the provided transaction', async () => {
478
- const engine = new Engine(
479
- {
480
- Hello: mockTopicManager
481
- },
482
- {},
483
- mockStorageEngine,
484
- mockChainTracker
485
- )
486
- await engine.submit({
487
- beef: exampleBeef,
488
- topics: ['Hello']
489
- })
490
- expect(mockChainTracker.isValidRootForHeight).toHaveBeenCalledWith('bb6f640cc4ee56bf38eb5a1969ac0c16caa2d3d202b22bf3735d10eec0ca6e00', 814435)
491
- })
492
- it('Throws an error if an invalid envelope is provided', async () => {
493
- const engine = new Engine(
494
- {
495
- Hello: mockTopicManager
496
- },
497
- {},
498
- mockStorageEngine,
499
- mockChainTracker
500
- )
501
- mockChainTracker.isValidRootForHeight.mockReturnValueOnce(Promise.resolve(false))
502
- await expect(engine.submit({
503
- beef: exampleBeef,
504
- topics: ['Hello']
505
- })).rejects.toHaveProperty('message', 'Invalid merkle path for transaction 3ecead27a44d013ad1aae40038acbb1883ac9242406808bb4667c15b4f164eac')
506
- })
507
- describe('Broadcast gating (PHASE 2)', () => {
508
- const makeBroadcaster = (): { broadcast: jest.Mock } => ({
509
- broadcast: jest.fn(async () => ({ status: 'success', txid: exampleTXID, message: 'ok' }))
510
- })
511
- const makeEngine = (broadcaster: any): Engine => new Engine(
512
- { Hello: mockTopicManager },
513
- {},
514
- mockStorageEngine,
515
- mockChainTracker,
516
- undefined,
517
- undefined,
518
- undefined,
519
- broadcaster
520
- )
521
-
522
- it('broadcasts when at least one topic admits outputs', async () => {
523
- const broadcaster = makeBroadcaster()
524
- const engine = makeEngine(broadcaster)
525
- await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
526
- expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
527
- })
528
-
529
- it('never broadcasts a transaction every topic manager rejected', async () => {
530
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => ({
531
- outputsToAdmit: [],
532
- coinsToRetain: []
533
- }))
534
- const broadcaster = makeBroadcaster()
535
- const engine = makeEngine(broadcaster)
536
- await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
537
- expect(broadcaster.broadcast).not.toHaveBeenCalled()
538
- })
539
-
540
- it('never broadcasts when topic validation throws, even with tracked coins consumed', async () => {
541
- // The rejected tx spends previously-admitted coins — a throw must
542
- // still gate the broadcast (this is the rejected-transfer shape).
543
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
544
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
545
- throw new Error('rule violation')
546
- })
547
- const broadcaster = makeBroadcaster()
548
- const engine = makeEngine(broadcaster)
549
- await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
550
- expect(broadcaster.broadcast).not.toHaveBeenCalled()
551
- })
552
-
553
- it('broadcasts a consume-only transaction that retains nothing (history purge)', async () => {
554
- // e.g. a KVStore remove that also purges history: previously-admitted
555
- // coins are consumed, nothing admitted, nothing retained. That is an
556
- // acceptance, not a rejection (rejection = throw).
557
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
558
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => ({
559
- outputsToAdmit: [],
560
- coinsToRetain: []
561
- }))
562
- const broadcaster = makeBroadcaster()
563
- const engine = makeEngine(broadcaster)
564
- await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
565
- expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
566
- })
567
-
568
- it('still broadcasts a duplicate (previously accepted) transaction', async () => {
569
- mockStorageEngine.doesAppliedTransactionExist = jest.fn(async () => true)
570
- const broadcaster = makeBroadcaster()
571
- const engine = makeEngine(broadcaster)
572
- await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
573
- expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
574
- })
575
-
576
- it('broadcasts when outputs are consumed but none admitted (coinsToRetain only)', async () => {
577
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
578
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => ({
579
- outputsToAdmit: [],
580
- coinsToRetain: [0]
581
- }))
582
- const broadcaster = makeBroadcaster()
583
- const engine = makeEngine(broadcaster)
584
- await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
585
- expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
586
- })
587
- })
588
-
589
- describe('For each topic being processed', () => {
590
- it('Checks for duplicate transactions', async () => {
591
- const engine = new Engine(
592
- {
593
- Hello: mockTopicManager
594
- },
595
- {},
596
- mockStorageEngine,
597
- mockChainTracker
598
- )
599
- await engine.submit({
600
- beef: exampleBeef,
601
- topics: ['Hello']
602
- })
603
- expect(mockStorageEngine.doesAppliedTransactionExist).toHaveBeenCalledWith({ txid: exampleTXID, topic: 'Hello' })
604
- })
605
- it('Does not process the output if the transaction was already applied to the topic', async () => {
606
- mockStorageEngine.doesAppliedTransactionExist = jest.fn(async () => true)
607
- const engine = new Engine(
608
- {
609
- Hello: mockTopicManager
610
- },
611
- {},
612
- mockStorageEngine,
613
- mockChainTracker
614
- )
615
- await engine.submit({
616
- beef: exampleBeef,
617
- topics: ['Hello']
618
- })
619
- expect(mockStorageEngine.findOutput).not.toHaveBeenCalled()
620
- expect(mockStorageEngine.markUTXOAsSpent).not.toHaveBeenCalled()
621
- expect(mockStorageEngine.insertOutput).not.toHaveBeenCalled()
622
- })
623
- describe('For each input of the transaction', () => {
624
- it('Acquires the appropriate previous topical UTXOs from the storage engine', async () => {
625
- // Mock findUTXO to return a UTXO
626
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
627
- const engine = new Engine(
628
- {
629
- Hello: mockTopicManager
630
- },
631
- {},
632
- mockStorageEngine,
633
- mockChainTracker
634
- )
635
-
636
- // Submit the utxo
637
- await engine.submit({
638
- beef: exampleBeef,
639
- topics: ['Hello']
640
- })
641
- expect(mockStorageEngine.findOutput).toHaveBeenCalled()
642
- })
643
- it('Includes the appropriate previous topical UTXOs when they are returned from the storage engine', async () => {
644
- // Mock findUTXO to return a UTXO
645
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
646
- const engine = new Engine(
647
- {
648
- Hello: mockTopicManager
649
- },
650
- {},
651
- mockStorageEngine,
652
- mockChainTracker
653
- )
654
-
655
- // Submit the utxo
656
- await engine.submit({
657
- beef: exampleBeef,
658
- topics: ['Hello']
659
- })
660
- expect(mockStorageEngine.findOutput).toHaveBeenCalled()
661
- expect(mockStorageEngine.markUTXOAsSpent).toHaveBeenCalledWith(exampleTXID, 0, 'Hello')
662
- })
663
- })
664
- it('Identifies admissible outputs with the appropriate topic manager', async () => {
665
- // Mock findUTXO to return a UTXO
666
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
667
- const engine = new Engine(
668
- {
669
- Hello: mockTopicManager
670
- },
671
- {},
672
- mockStorageEngine,
673
- mockChainTracker
674
- )
675
-
676
- await engine.submit({
677
- beef: exampleBeef,
678
- topics: ['Hello']
679
- })
680
- expect(engine.managers.Hello.identifyAdmissibleOutputs).toHaveBeenCalledWith(
681
- exampleBeef,
682
- [0],
683
- undefined,
684
- 'current-tx'
685
- )
686
- })
687
- describe('When previous UTXOs were retained by the topic manager', () => {
688
- it('Notifies all lookup services about the output being spent (not deleted, see the comment about this in deleteUTXODeep)', async () => {
689
- // Mock findUTXO to return a UTXO
690
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
691
- const engine = new Engine(
692
- {
693
- Hello: mockTopicManager
694
- },
695
- {
696
- Hello: mockLookupService
697
- },
698
- mockStorageEngine,
699
- mockChainTracker
700
- )
701
-
702
- // Submit the utxo
703
- await engine.submit({
704
- beef: exampleBeef,
705
- topics: ['Hello']
706
- })
707
- expect(engine.lookupServices.Hello.outputSpent).toHaveBeenCalledWith({
708
- mode: 'none',
709
- txid: exampleTXID,
710
- outputIndex: 0,
711
- topic: 'Hello'
712
- })
713
- })
714
- })
715
- describe('When previous UTXOs were not retained by the topic manager', () => {
716
- it('Marks the UTXO as stale, deleting all stale UTXOs by calling deleteUTXODeep', async () => {
717
- // Mock findUTXO to return a UTXO
718
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
719
- const engine = new Engine(
720
- {
721
- Hello: mockTopicManager
722
- },
723
- {
724
- Hello: mockLookupService
725
- },
726
- mockStorageEngine,
727
- mockChainTracker
728
- )
729
-
730
- // Submit the utxo
731
- await engine.submit({
732
- beef: exampleBeef,
733
- topics: ['Hello']
734
- })
735
- // Test that previous UTXOs are deleted
736
- expect(mockStorageEngine.deleteOutput).toHaveBeenCalledWith(exampleTXID, 0, 'hello')
737
- expect(mockLookupService.outputNoLongerRetainedInHistory).toHaveBeenCalledWith(exampleTXID, 0, 'hello')
738
- })
739
- it('Notifies all lookup services about the output being spent (the notification about the actual deletion will come from deleteUTXODeep)', async () => {
740
- // Mock findUTXO to return a UTXO
741
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
742
- const engine = new Engine(
743
- {
744
- Hello: mockTopicManager
745
- },
746
- {
747
- Hello: mockLookupService
748
- },
749
- mockStorageEngine,
750
- mockChainTracker
751
- )
752
-
753
- // Submit the utxo
754
- await engine.submit({
755
- beef: exampleBeef,
756
- topics: ['Hello']
757
- })
758
- // Was the lookup service notified of the output deletion?
759
- expect(mockLookupService.outputNoLongerRetainedInHistory).toHaveBeenCalledWith(exampleTXID, 0, 'hello')
760
- })
761
- })
762
- it('Adds admissible UTXOs to the storage engine', async () => {
763
- // Mock findUTXO to return a UTXO
764
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
765
- const engine = new Engine(
766
- {
767
- hello: mockTopicManager
768
- },
769
- {
770
- Hello: mockLookupService
771
- },
772
- mockStorageEngine,
773
- mockChainTracker
774
- )
775
-
776
- // Submit the utxo
777
- await engine.submit({
778
- beef: exampleBeef,
779
- topics: ['hello']
780
- })
781
- // Test the new UTXO was added
782
- expect(mockStorageEngine.insertOutput).toHaveBeenCalledWith(expect.objectContaining({
783
- ...mockOutput,
784
- score: expect.any(Number)
785
- }))
786
- })
787
- it('Notifies lookup services about incoming admissible UTXOs', async () => {
788
- // Mock findUTXO to return a UTXO
789
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
790
- const engine = new Engine(
791
- {
792
- Hello: mockTopicManager
793
- },
794
- {
795
- Hello: mockLookupService
796
- },
797
- mockStorageEngine,
798
- mockChainTracker
799
- )
800
-
801
- // Submit the utxo
802
- await engine.submit({
803
- beef: exampleBeef,
804
- topics: ['Hello']
805
- })
806
- // Test the lookup service was notified of the new UTXO
807
- expect(mockLookupService.outputAdmittedByTopic).toHaveBeenCalledWith({
808
- mode: 'locking-script',
809
- txid: exampleTXID,
810
- satoshis: 26172,
811
- outputIndex: 0,
812
- lockingScript: exampleTX.outputs[0].lockingScript,
813
- topic: 'Hello'
814
- })
815
- })
816
- describe('For each consumed UTXO', () => {
817
- it('Finds the UTXO', async () => {
818
- // Mock findUTXO to return a UTXO
819
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
820
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
821
- return {
822
- outputsToAdmit: [0],
823
- coinsToRetain: [0]
824
- }
825
- })
826
- const engine = new Engine(
827
- {
828
- Hello: mockTopicManager
829
- },
830
- {
831
- Hello: mockLookupService
832
- },
833
- mockStorageEngine,
834
- mockChainTracker
835
- )
836
-
837
- // Submit the utxo
838
- await engine.submit({
839
- beef: exampleBeef,
840
- topics: ['Hello']
841
- })
842
-
843
- // Test a storage engine lookup happens for the utxo consumed
844
- expect(mockStorageEngine.findOutput).toHaveBeenCalledWith(examplePreviousTXID, 0, 'Hello')
845
- })
846
- it('Updates the UTXO to reflect that it is now additionally consumed by the newly-created UTXOs', async () => {
847
- // Mock findUTXO to return a UTXO
848
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
849
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
850
- return {
851
- outputsToAdmit: [0],
852
- coinsToRetain: [0]
853
- }
854
- })
855
- const engine = new Engine(
856
- {
857
- Hello: mockTopicManager
858
- },
859
- {
860
- Hello: mockLookupService
861
- },
862
- mockStorageEngine,
863
- mockChainTracker
864
- )
865
-
866
- // Submit the utxo
867
- await engine.submit({
868
- beef: exampleBeef,
869
- topics: ['Hello']
870
- })
871
-
872
- // Test the consumedBy data is updated
873
- expect(mockStorageEngine.updateConsumedBy).toHaveBeenCalledWith(examplePreviousTXID, 0, 'Hello', [{
874
- txid: exampleTXID,
875
- outputIndex: 0
876
- }])
877
- })
878
- })
879
- it('Inserts a new applied transaction to avoid de-duplication', async () => {
880
- // Mock findUTXO to return a UTXO
881
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
882
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
883
- return {
884
- outputsToAdmit: [0],
885
- coinsToRetain: [0]
886
- }
887
- })
888
- const engine = new Engine(
889
- {
890
- Hello: mockTopicManager
891
- },
892
- {
893
- Hello: mockLookupService
894
- },
895
- mockStorageEngine,
896
- mockChainTracker
897
- )
898
-
899
- // Submit the utxo
900
- await engine.submit({
901
- beef: exampleBeef,
902
- topics: ['Hello']
903
- })
904
-
905
- // Test the tx is inserted
906
- expect(mockStorageEngine.insertAppliedTransaction).toHaveBeenCalledWith(expect.objectContaining({
907
- txid: exampleTXID,
908
- topic: 'Hello'
909
- }))
910
- })
911
- it('Returns a correct set of admitted topics and outputs', async () => {
912
- // Mock findUTXO to return a UTXO
913
- mockStorageEngine.insertOutput = jest.fn()
914
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
915
- mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
916
- return {
917
- outputsToAdmit: [0],
918
- coinsToRetain: [0]
919
- }
920
- })
921
- const engine = new Engine(
922
- {
923
- Hello: mockTopicManager
924
- },
925
- {
926
- Hello: mockLookupService
927
- },
928
- mockStorageEngine,
929
- mockChainTracker
930
- )
931
-
932
- // Submit the utxo
933
- const results = await engine.submit({
934
- beef: exampleBeef,
935
- topics: ['Hello']
936
- })
937
-
938
- // Test the correct outputs are admitted
939
- expect(results).toEqual({
940
- Hello: {
941
- outputsToAdmit: [0],
942
- coinsToRetain: [0],
943
- coinsRemoved: []
944
- }
945
- })
946
- })
947
- })
948
-
949
- describe('lookup', () => {
950
- it('Throws an error if no lookup service has this provider name', async () => {
951
- const engine = new Engine(
952
- {
953
- Hello: mockTopicManager
954
- },
955
- {
956
- Hello: mockLookupService
957
- },
958
- mockStorageEngine,
959
- mockChainTracker
960
- )
961
-
962
- // Perform a lookup request
963
- await expect(engine.lookup({
964
- service: 'HelloWorld',
965
- query: { name: 'Bob' }
966
- })).rejects.toThrow()
967
- })
968
- it('Calls the lookup function from the lookup service', async () => {
969
- // TODO: Make the default storage engine return something...?
970
- mockLookupService.lookup = jest.fn(async () => [{ txid: exampleTXID, outputIndex: 0 }])
971
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
972
- const engine = new Engine(
973
- {
974
- Hello: mockTopicManager
975
- },
976
- {
977
- Hello: mockLookupService
978
- },
979
- mockStorageEngine,
980
- mockChainTracker
981
- )
982
-
983
- // Perform a lookup request
984
- await engine.lookup({
985
- service: 'Hello',
986
- query: { name: 'Bob' }
987
- })
988
- expect(mockLookupService.lookup).toHaveBeenCalledWith({
989
- service: 'Hello',
990
- query: { name: 'Bob' }
991
- })
992
- })
993
- it('Uses batched output loading when the storage engine supports it', async () => {
994
- mockLookupService.lookup = jest.fn(async () => [{
995
- txid: 'mockTXID',
996
- outputIndex: 0,
997
- history: undefined
998
- }])
999
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1000
- const findOutputsByOutpoints = jest.fn(async () => [mockOutput])
1001
- ;(mockStorageEngine).findOutputsByOutpoints = findOutputsByOutpoints
1002
- const engine = new Engine(
1003
- {
1004
- Hello: mockTopicManager
1005
- },
1006
- {
1007
- Hello: mockLookupService
1008
- },
1009
- mockStorageEngine,
1010
- mockChainTracker
1011
- )
1012
-
1013
- await engine.lookup({
1014
- service: 'Hello',
1015
- query: { name: 'Bob' }
1016
- })
1017
-
1018
- expect(findOutputsByOutpoints).toHaveBeenCalledWith([{ txid: 'mockTXID', outputIndex: 0 }], true)
1019
- expect(mockStorageEngine.findOutput).not.toHaveBeenCalled()
1020
- })
1021
- describe('For each returned result', () => {
1022
- it('Finds the identified UTXO by its txid and vout', async () => {
1023
- // TODO: Make the default storage engine return something...?
1024
- mockLookupService.lookup = jest.fn(async () => [{
1025
- txid: 'mockTXID',
1026
- outputIndex: 0,
1027
- history: undefined
1028
- }])
1029
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1030
- const engine = new Engine(
1031
- {
1032
- Hello: mockTopicManager
1033
- },
1034
- {
1035
- Hello: mockLookupService
1036
- },
1037
- mockStorageEngine,
1038
- mockChainTracker
1039
- )
1040
-
1041
- // Perform a lookup request
1042
- await engine.lookup({
1043
- service: 'Hello',
1044
- query: { name: 'Bob' }
1045
- })
1046
- expect(mockStorageEngine.findOutput).toHaveBeenCalledWith(
1047
- 'mockTXID', 0, undefined, undefined, true
1048
- )
1049
- })
1050
- it('Calls getUTXOHistory with the correct UTXO and history parameters', async () => {
1051
- mockLookupService.lookup = jest.fn(async () => [{
1052
- txid: 'mockTXID',
1053
- outputIndex: 0,
1054
- history: undefined
1055
- }])
1056
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1057
- const engine = new Engine(
1058
- {
1059
- Hello: mockTopicManager
1060
- },
1061
- {
1062
- Hello: mockLookupService
1063
- },
1064
- mockStorageEngine,
1065
- mockChainTracker
1066
- )
1067
- engine.getUTXOHistory = jest.fn(async () => {
1068
- return mockOutput
1069
- })
1070
-
1071
- // Perform a lookup request
1072
- await engine.lookup({
1073
- service: 'Hello',
1074
- query: { name: 'Bob' }
1075
- })
1076
- expect(engine.getUTXOHistory).toHaveBeenCalledWith(
1077
- mockOutput,
1078
- undefined,
1079
- 0,
1080
- expect.anything()
1081
- )
1082
- })
1083
- })
1084
- it('Returns the correct set of hydrated results', async () => {
1085
- mockLookupService.lookup = jest.fn(async () => [{
1086
- txid: 'mockTXID',
1087
- outputIndex: 0,
1088
- history: undefined
1089
- }])
1090
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1091
- const engine = new Engine(
1092
- {
1093
- Hello: mockTopicManager
1094
- },
1095
- {
1096
- Hello: mockLookupService
1097
- },
1098
- mockStorageEngine,
1099
- mockChainTracker
1100
- )
1101
- engine.getUTXOHistory = jest.fn(async () => {
1102
- return mockOutput
1103
- })
1104
-
1105
- // Perform a lookup request
1106
- const results = await engine.lookup({
1107
- service: 'Hello',
1108
- query: { name: 'Bob' }
1109
- })
1110
- expect(results).toEqual({
1111
- outputs: [{
1112
- beef: mockOutput.beef,
1113
- outputIndex: mockOutput.outputIndex
1114
- }],
1115
- type: 'output-list'
1116
- })
1117
- })
1118
- })
1119
- describe('getUTXOHistory', () => {
1120
- it('Returns the given output if there is no history selector', async () => {
1121
- // Already tested above
1122
- return true
1123
- })
1124
- it('Invokes the history selector function with the correct data', async () => {
1125
- const mockedHistorySelector = jest.fn(async (beef, outputIndex, currentDepth) => {
1126
- if (currentDepth !== 2) {
1127
- return true
1128
- }
1129
- return false
1130
- })
1131
- mockLookupService.lookup = jest.fn(async () => [{
1132
- txid: 'mockTXID',
1133
- outputIndex: 0,
1134
- history: mockedHistorySelector
1135
- }])
1136
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1137
- const engine = new Engine(
1138
- {
1139
- Hello: mockTopicManager
1140
- },
1141
- {
1142
- Hello: mockLookupService
1143
- },
1144
- mockStorageEngine,
1145
- mockChainTracker
1146
- )
1147
-
1148
- // Perform a lookup request
1149
- await engine.lookup({
1150
- service: 'Hello',
1151
- query: { name: 'Bob' }
1152
- })
1153
- expect(mockedHistorySelector).toHaveBeenCalledWith(
1154
- mockOutput.beef,
1155
- mockOutput.outputIndex,
1156
- 0
1157
- )
1158
- expect(mockedHistorySelector).toHaveBeenCalled()
1159
- })
1160
- it('Returns undefined if history should not be traversed', async () => {
1161
- const mockedHistorySelector = jest.fn(async (beef, outputIndex, currentDepth) => {
1162
- return false
1163
- })
1164
- mockLookupService.lookup = jest.fn(async () => [{
1165
- txid: 'mockTXID',
1166
- outputIndex: 0,
1167
- history: mockedHistorySelector
1168
- }])
1169
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1170
- const engine = new Engine(
1171
- {
1172
- Hello: mockTopicManager
1173
- },
1174
- {
1175
- Hello: mockLookupService
1176
- },
1177
- mockStorageEngine,
1178
- mockChainTracker
1179
- )
1180
-
1181
- // Perform a lookup request
1182
- const results = await engine.lookup({
1183
- service: 'Hello',
1184
- query: { name: 'Bob' }
1185
- })
1186
- expect(mockedHistorySelector).toHaveBeenCalledWith(
1187
- mockOutput.beef,
1188
- mockOutput.outputIndex,
1189
- 0
1190
- )
1191
- expect(results).toEqual({
1192
- outputs: [],
1193
- type: 'output-list'
1194
- })
1195
- })
1196
- it('Returns undefined if the history selector is a number, and less than the current depth', async () => {
1197
- mockLookupService.lookup = jest.fn(async () => [{
1198
- txid: 'mockTXID',
1199
- outputIndex: 0,
1200
- history: -1
1201
- }])
1202
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1203
- const engine = new Engine(
1204
- {
1205
- Hello: mockTopicManager
1206
- },
1207
- {
1208
- Hello: mockLookupService
1209
- },
1210
- mockStorageEngine,
1211
- mockChainTracker
1212
- )
1213
-
1214
- // Perform a lookup request
1215
- const results = await engine.lookup({
1216
- service: 'Hello',
1217
- query: { name: 'Bob' }
1218
- })
1219
- expect(results).toEqual({
1220
- outputs: [],
1221
- type: 'output-list'
1222
- })
1223
- })
1224
- it('Returns the current output even if history should be traversed, if the current output is part of a transaction that does not consume any previous topical UTXOs', async () => {
1225
- mockLookupService.lookup = jest.fn(async () => [{
1226
- txid: 'mockTXID',
1227
- outputIndex: 0,
1228
- history: 1
1229
- }])
1230
- mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
1231
- const engine = new Engine(
1232
- {
1233
- Hello: mockTopicManager
1234
- },
1235
- {
1236
- Hello: mockLookupService
1237
- },
1238
- mockStorageEngine,
1239
- mockChainTracker
1240
- )
1241
-
1242
- // Perform a lookup request
1243
- const results = await engine.lookup({
1244
- service: 'Hello',
1245
- query: { name: 'Bob' }
1246
- })
1247
-
1248
- expect(results).toEqual({
1249
- outputs: [{
1250
- beef: mockOutput.beef,
1251
- outputIndex: mockOutput.outputIndex
1252
- }],
1253
- type: 'output-list'
1254
- })
1255
- })
1256
- // it('Traversing history, calls findOutput with any output consumed by this UTXO', async () => {
1257
- // mockLookupService.lookup = jest.fn(async () => [{
1258
- // txid: 'mockTXID',
1259
- // outputIndex: 0,
1260
- // history: 1
1261
- // }])
1262
- // mockStorageEngine.findOutput = jest.fn(async () => ({
1263
- // ...mockOutput,
1264
- // outputsConsumed: [{
1265
- // txid: examplePreviousTXID,
1266
- // outputIndex: 0
1267
- // }]
1268
- // }))
1269
- // const engine = new Engine(
1270
- // {
1271
- // Hello: mockTopicManager
1272
- // },
1273
- // {
1274
- // Hello: mockLookupService
1275
- // },
1276
- // mockStorageEngine,
1277
- // mockChainTracker
1278
- // )
1279
-
1280
- // // Perform a lookup request
1281
- // await engine.lookup({
1282
- // service: 'Hello',
1283
- // query: { name: 'Bob' }
1284
- // })
1285
-
1286
- // expect(mockStorageEngine.findOutput).toHaveBeenCalledWith()
1287
- // })
1288
- // it('Returns the correct envelope based on the history traversal process', async () => {
1289
- // // TODO: Come up with some test data for a simple case, but different than the above code.
1290
- // })
1291
- // it('Returns the correct envelope based on the history traversal process for a more complex multi-layer multi-output graph', async () => {
1292
- // // TODO: Come up with some test data to test the history traversal process better
1293
- // })
1294
- })
1295
- // describe('deleteUTXODeep', () => {
1296
- // it('Finds UTXO by ID if no output was provided', async () => {
1297
-
1298
- // })
1299
- // it('Finds UTXO by TXID and VOUT if no ID was provided', async () => {
1300
-
1301
- // })
1302
- // it('Throws an error if no output can be found', async () => {
1303
-
1304
- // })
1305
- // describe('When no more UTXOs are consumed by this UTXO', () => {
1306
- // it('Deletes the UTXO by ID from the storage engine', async () => {
1307
-
1308
- // })
1309
- // it('Notifies all lookup services about the deletion of the UTXO', async () => {
1310
-
1311
- // })
1312
- // })
1313
- // it('For each UTXO that this UTXO consumes, finds the consumed UTXO by its ID and removes reference to this one', async () => {
1314
-
1315
- // })
1316
- })
1317
- })
1318
-
1319
- // beef27c8f_1 is 1 level deep, i.e. proofs on inputs
1320
- const beef27c8f1 = '0100beef01feafcf0c000802020044ef36fc6c79af360b13f2164c6e772e6e5e2083a54d72894f34710fe6a68cf80302792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d010000059e83aae2cfc6a35477566439dd9d58969cc41a134864c671c9b844f2a409a60101004991c129bba819832d6dd315c3a2065bd31b1a60aff8192be335f83731641e690101002f87724a7387df82417902266d2a4fb2fee857451e963a352ce8acbb279aa9eb010100a9e9eb55d20cef840bacbdb26ad8d6e7680ef7653364830b1e770fa140ece6bb010100f0ecd03fff2df21b0034d331cade50152781be9052dcec54eb95c963ef1cdcbf0101006fdb2c01a266e761a32f53b3ad0fabbca31ae234d31d85d9af9bbd624b321c9b0101008108cad77aabf7b0ee371f42afd773cada534244535f7108c7590df2b9d5f8a2020100000006e9847b33c5d666ccee1dccd52235489d8d58bf432be1b8c0cc7c13af80f10703030000006b483045022100fe8a18e043d693246a56c9b32ff553bea89a0bbe389bc55e8244044dcd865de3022065245c30fbbcc6820e4761048ed28c587bf41ece1933dfb85b8fa8c3433d93504121022f2ac22e73a55aa4ff21e905f66959200642ae74716544f834a48af5aa53fb27ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45050000006b483045022100e6332a6459350ff2486a39b6d79cdc0b65b6d52cf898fc457e45880c0db01b2a02206e34126bc81e6f03c6de90278788d6b2be3a3f5d7bd85e6442bbea90b8a5e2394121029780a1a98be5ff17a3228feb830e3bc32395ebecc5cf6de08f7a5685faf66c07ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45040000006b483045022100dfba71c6c33d2366b2202ea136271fd2f3a26c0a7295f6dd6018fbe12fc48708022052387ca532247bbfffef4b076fd2fbbdebe85b3cf53c73004897cd07be5d1ad6412103f735eb69c9f0b3c90dd2798b709e12a5b3d176a4690bd2a31ec616af9ecd47b5ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45020000006b483045022100f941b74c5da1e7f0ce4d1c08745e4d6b8ef56656199505caeff4b74f54890cec02201eaa0d01112497c89ebe4818687c5f3305629a83abd3b0bf5135feee68f57ca0412103fb0ec51f9e6e45b213cb1deff921e3322624cfb6d4a4ba5dc699867cf163b736ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45030000006a47304402205a3a2848350fdc19cae73243bea16dfc1e8fbbd5cecde4b5c88a57790505cebb022043db51d04998d52ec394d0e945a76e9922dd63caffb882242bec2ced2a35c7f1412102bae3e2f47dceaed7db2a212e059a00674a0087e3395b92d9f12cdbefdbc84efaffffffffad49b6f41125ffadab03cb2dcc24caf2c897c2cd4aed6e0a3b5c003f2dde757a030000006b483045022100aa7aaec7fc6c5a2f4d5318b7fcbbca0f223c5c198f8fc0e1c8d59aee7155af1202207a4960fb58aa86eba12852f84fa3cdb4c427c7374fe737b3d5bcd9e2a6d76e3b412102b55cfd872e85b3dd424dcceafe01ed5a87dfb599d354ab7fc50d9ec48af77f6effffffff1a400d0300000000001976a914f182138abcb69c7c2339040605aa0a442cfec44288acc8000000000000001976a914e9d2276f468223a6d650672ab9d4c1271fb157e288acacb00000000000001976a914c0ea0208a097df8146933ae15f3ce545d5451e7388ac7a140000000000001976a9142d9ae3291f9393639fe2c38c9a7a948d136a10b488acea070000000000001976a91435d1c396b7ccb467118a41c2d77ebf79fb5fb7eb88ac17050000000000001976a9140dac1e2ddc975ee6d9f393e2ccd4876edb386cdd88ac51060000000000001976a91422878cb2f57a1ebe513815562003128f6e99eda188ac67040000000000001976a91439a752923ba1ef8b126ba7886646d58f54521c6588ac1e040000000000001976a914a939565af52d3fa974a040f42c35365d76b98f6a88acec030000000000001976a914c90823a53e29cafd35a225cc14e3a44eda143a5788ac05040000000000001976a914af6f8d6aaec3bf123efc17c8977bfad1ec1c859388acec030000000000001976a914d4c5e66a062e634be1c6f0eb1f2af0acebecb47988acf1030000000000001976a914a94a3e84bd740095ab39bed110cd3b3922c8595088ace8030000000000001976a914aa2e3c3bf6f3ee79f5af3b94eab2be60a394ce4c88aceb030000000000001976a91459394fa083ff3726571c79624dba3b38f14d924c88ace8030000000000001976a914726eb3ae6211711aa1624f3d767035b0b44abdc488ace8030000000000001976a9145f4680a099a6dc6891af3094aaab96aa66a6259888ace8030000000000001976a914f1a4fbd99e3923b0934abdb8839c337eb2fd50b888ace8030000000000001976a9147ed0ad307f5fe9993f984e7525c0012e3569594588ace8030000000000001976a91407f386bcdc4892cc2d0be3226b721ceaf04750b288ace8030000000000001976a914b51edaaa3c92d0af94109d952c7ddf13aa345f9988ace8030000000000001976a914018f4e02bd74f17a75f5b3965db4633ab514c57588ace8030000000000001976a914cc8d1f7ba5406b81efbc23da0fcccb4d3fe5b40a88ace8030000000000001976a914db1d4b7bd3e62f535dc3f2c8d0a989b000fa736d88ace8030000000000001976a914b8ea69358da7bea9506b15db6fc6597b3849b0a188ace8030000000000001976a914ecd769fdd67e04786e871c50c1a10504861bd79488ac0000000001000100000001792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d070000006b48304502210090368b5925795abb0415a3cd6265d7abae7d3541fb7f2fdb1fdffdd916b3fce3022068136da44df8ee367807c7c59c950ea8d428adfe142268cd6371cf4c1b4c3208412102858ea1804708d1e16e77b49ba1559b3742797f5f43efd3922fa3d21557521d7cffffffff03f401000000000000fde10121026afd108ddbc05e093207ebdf06557a3d66e15b96accbf1766c2e7d6efa3d1981ac22313852713463583334356b6a7461734467764b41455a435135564c5a72516f76786d2081d846bcae65ce281829b6d81eff3862a68b8eaf4e99ad97c0c4ad2e149631c020f936ace2d2b4034710dec819e343f69ddbb1a16d721b88413b697c2475f75f504c4e8470062e6966cb7ec80864e5e1217ecf2b3f113bb5307e1ec1d338678e54b426a11a267e990f7dcd85072c7b3870fabb54812d7f8a7aaa672ce01e4f8a2c6a1d532849877329bd23be0b3383fcb437ffd1ff05aad824343bfe5bae52d5775ed99ba9d5a72038dd50f81153e674a8b1c5d659aac361e660cc5657b291eda149de2505efb0bbac0a353331393633353238334c797f3040f01934bb25733d4700a0309194a7defd8ceea7a8a59ad279c6237e2917264a115773777c5746546288b15ec1cf6788b801dd3952fec804cadbba2ff4e05464735e2db83c3509b59119efc843716c34becd2884f011f2e7f61f382237891875115b6ea5bac1c3e9ca6c575b44547d589c38c1b5a8cadb463044022016e1fdb226b465f3151aaa1e033b122dd58f3b495d2846d532f784ec8f6fc9ae022031dc12155c02e7f588c9f6d975a79c9c26cf341c1c88578cd6a87a3c01f6a73d6d6d6d6dc8000000000000001976a914010b7bfa8ff585b6d95f8a18a998cbd87508bd4188aca9010000000000001976a914254c6f1ce67d27ae4dafb03d2ba08318df2883c588ac0000000000'
1321
- // beef27c8f_0 is 0 levels deep, i.e. tx has merklePath
1322
- const beef27c8f0 = '0100beef01fef4f10c000902fd020100ab2d9b3bbfc2ecf5c834f7719bf10dcabef31cfa3b78fa714bd3a2b3958fc6b5fd0301029515629d935d81e704ca97b8dc02a698c39d78f06bbb3d8d46bcaa5178f3c827018000fc94b1da08b5d0850afcc4948a9e129a2c2c0bb629090ca84fef5d326ceadbc2014100fa78356192a22189dd3c2a03bfb2e043667a55a85386a8d0f0853bbaa54ba748012100e37888006acc37cbfc6a459117c3e957b00e8ff3a90d93bc9aaa3fc9d972453701110052564e444cbe4b1f24015d91f5cfd7eff63e5f403613a7b48e77efa32761f950010900acef65445ef232f1fcb527bab67fccc90ae9d0c610a4f752d84c5e10a3415d74010500e1fadd176551150808ae96ff2b4dff487c432788d23db25fd3fb5e8b3e16b96f0103003e530f146a992ed9378807fa43bc1e6033de0711d728ee3a0ea1507b5ffff2940100006c8eca8a9d680ab2ae4dc1f0e247f282de3f32b99175e6f9ddb4f8e76ae0bf1b010100000001792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d070000006b48304502210090368b5925795abb0415a3cd6265d7abae7d3541fb7f2fdb1fdffdd916b3fce3022068136da44df8ee367807c7c59c950ea8d428adfe142268cd6371cf4c1b4c3208412102858ea1804708d1e16e77b49ba1559b3742797f5f43efd3922fa3d21557521d7cffffffff03f401000000000000fde10121026afd108ddbc05e093207ebdf06557a3d66e15b96accbf1766c2e7d6efa3d1981ac22313852713463583334356b6a7461734467764b41455a435135564c5a72516f76786d2081d846bcae65ce281829b6d81eff3862a68b8eaf4e99ad97c0c4ad2e149631c020f936ace2d2b4034710dec819e343f69ddbb1a16d721b88413b697c2475f75f504c4e8470062e6966cb7ec80864e5e1217ecf2b3f113bb5307e1ec1d338678e54b426a11a267e990f7dcd85072c7b3870fabb54812d7f8a7aaa672ce01e4f8a2c6a1d532849877329bd23be0b3383fcb437ffd1ff05aad824343bfe5bae52d5775ed99ba9d5a72038dd50f81153e674a8b1c5d659aac361e660cc5657b291eda149de2505efb0bbac0a353331393633353238334c797f3040f01934bb25733d4700a0309194a7defd8ceea7a8a59ad279c6237e2917264a115773777c5746546288b15ec1cf6788b801dd3952fec804cadbba2ff4e05464735e2db83c3509b59119efc843716c34becd2884f011f2e7f61f382237891875115b6ea5bac1c3e9ca6c575b44547d589c38c1b5a8cadb463044022016e1fdb226b465f3151aaa1e033b122dd58f3b495d2846d532f784ec8f6fc9ae022031dc12155c02e7f588c9f6d975a79c9c26cf341c1c88578cd6a87a3c01f6a73d6d6d6d6dc8000000000000001976a914010b7bfa8ff585b6d95f8a18a998cbd87508bd4188aca9010000000000001976a914254c6f1ce67d27ae4dafb03d2ba08318df2883c588ac000000000100'
1323
- // full txid
1324
- const txid27c8f = '27c8f37851aabc468d3dbb6bf0789dc398a602dcb897ca04e7815d939d621595'
1325
-
1326
- /*
1327
- txid 17d1829ba8424b97369ee8b528ee8b65d9d4b9c08d037d224a7be7c025f78f78 output 0 1196 sats
1328
- txid 9426201003b01e5bb66e9240a1cc337174238e816a25a06ba532fa67af4457d7 output 0 1197 sats
1329
- txid 509f5ef79c18504fdfe21e67608f56bacb8d8a200634e46f60a9dc8430dcd6e9 output 0 1198 sats
1330
- txid 877734db8eb917d0e2174a4bdddc085b34f67cedd6b94628c4feb1b979a2b2c5 output 0 1199 sats
1331
- txid 37abad7168d47d4e107d0f9f96813a4feef6ea346482fce554c3fade85d45409 output 0 1200 sats
1332
- txid d786db393ec7f1315bee2cbd3aa47634394f57c861feed8c075563308f14c237 output 0 1568 sats
1333
- txid a7d4cac391f5b3d8dedaacb3cd8b5afac54df2d8e043ae0452abcfe190686494 output 17 1132 sats
1334
- */
1335
- const txid17d182 = '17d1829ba8424b97369ee8b528ee8b65d9d4b9c08d037d224a7be7c025f78f78'
1336
- const beef17d1820 = '0100beef01feaff60c000c02fd660302788ff725c0e77b4a227d038dc0b9d4d9658bee28b5e89e36974b42a89b82d117fd670300fe2b9c31ae10a15803f91f63df0cf6a5b6771db9dd831e5bc37a36460a1ffa0701fdb201009bf0e3a5854c4b4c8d62c0ade2754ae1bbc0f16a3e49251ed964486b8dc387f101d80003ae2ec810744139e9989e4f36d8f5ebb160981481e68647890d11e3dda971fa016d0081efcddc14858ead1f427f235d2b21c8de43e33bfeb155ed79f666dd4dd490c2013700a6a847f6e264cfa64c0b6aad6b74912c63c6fc89728be813d104abd2724b98a5011a0062c7d393b9518b05d2d0891ae8c2cd840c93e051d2944bb8e651c976301c4e50010c008d8ee79c1a042cf43f4dcd880aeb4ff7f54552cd3a4c9a40c72cc16ed6847cee01070050194d11d12eddf5e56926b28778f0f64ad54e79eb4df4ce7834ddfc57bf882d010200bd66a0cdbb0af933166931b1050dd108e02d76cff19afd614e681fcbdf1be0740100008b36364159630b47b4c27f6e1e056e820b0aa350ab5dde2520fc1753129b628c010100cab620ac48bb55de40c8c5ddb62bbc44c907fbf5d882326d427f4cd4b34334800101001163b76c2c6824c07a53af2ea86da1b1d946340d9377d3b9b84560e074aea3a6010100000001d75744af67fa32a56ba0256a818e23747133cca140926eb65b1eb00310202694000000006a473044022027137acdeb7583e966a9e8825c9f5edb909b2fd46d40e6ee83846d164529630e0220644e82a46d813fac2836650ce832cdba7eb46b6c134982b36e84f5f08a921dbb412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ac040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
1337
- const beef17d1824 = '0100beef01fedcee0c000d02f8020954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37f900fa225908f222f8f7f291731a10e93cdf410189ca01adc688edf5577552aa8ff2017d006cfea8ad6ace7ecce9bbbe729eb9dca25f9be064b150d290588c3364867ee112013f00bfbacc8bfdda7f734c160fcfee972dd9182aede73290367aab62b5ce242e5655011e007a0eb94f2bcf98489247a01551a843d9cf1e091522c32f50ce347ba5057b439d010e00beb857ba543d2bd199e68c63a480c73b83660eca27d2ce105355741f27017876010600d5fb1ea8440e41b4ffde093dd53d6ed741c4fe4fe66ff43f6a4eb9af3c6ddebb010200169db3be130a9e8aff1dbff39abee477d3a224cb3e118c39794bb7ba427b707501000083cd0ce2d2734835c45331cd06f38556cf4e07634d599f59692ad9980175a7f201010022f4a629e99ca5838f56f87b050bdefa9b9f621c08745cb27b941d9c6750d99a01010032b90a4148ff984b0efee309a578cb87d278142c014f1a7fd5351b3e3cd4c7da01010082dc3ea570250357fca88652ee1628928136c29eb4910b7e5627e0ffef1d958a010100f5194f41c28960f0dba669f4715229f59f59afb5d55f50a2a7556111b8da67130101009cd7f9a65014cc6fd5c0b71391d0b70eb983ea3b03a2fbd7a13150775855953405010000000237c2148f306355078cedfe61c8574f393476a43abd2cee5b31f1c73e39db86d7000000006a47304402207ee089c64fa810ad88e03f7bad4ae57c9b7de215d222a5bf52f9259d171b717a0220643d3cb4423b5b7ffcb7b2b59eb81dede0a25319aaa0228d27e59d5e238c35a2412102641b5a033ce332a9fc0f81dee7948c099b194758d8352bd5572988ac760ae2faffffffff94646890e1cfab5204ae43e0d8f24dc5fa5a8bcdb3acdaded8b3f591c3cad4a7110000006a47304402205b92eaba4a6c029385e9cf291631b98d0c7aa6851755fb44279e2b1ebd12759702206e96f3bac252f2c5cf5edf7f8828d6358baaa9fe34aa163faeb9b31aac830e47412102577e2a3877a253d9e7587c3d77a4038b4af5e51113393c2713c6d50c9da5de6effffffff03b0040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88acc8000000000000001976a914292e61672fc24a9a70cfa6f73a9af5655652629d88ac13050000000000001976a914ad2ab55d09559074e41e2a2c1c89742e13bee03088ac00000000010001000000010954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37000000006b4830450221009c67deb38aa6b5ed8bfa140e9464e1b1ee2908a296bb70e3d164725254491c6a02207b595dcced1136c276edfc1e5e5901279cf6f0a6432e4c1a908468262e7d9543412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001af040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac00000000000100000001c5b2a279b9b1fec42846b9d6ed7cf6345b08dcdd4b4a17e2d017b98edb347787000000006b483045022100c0992a684be99a07bc87e03bf88701acefc033c1aa57cf4a3b15253e64c8962a022031d5b3af1684a1a1a201eb16fc1fbca8522ae16b9c0d340e8bec48eeea1fe458412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ae040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac00000000000100000001e9d6dc3084dca9606fe43406208a8dcbba568f60671ee2df4f50189cf75e9f50000000006b483045022100c9c8baa777fe09c1310788614d4eba88b6027d39eada18a5a79c6a33e55919c302205222961b1f63dddd425cd418571797cb3a68aa59ac37331c0fc61fc9de66c34d412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ad040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac00000000000100000001d75744af67fa32a56ba0256a818e23747133cca140926eb65b1eb00310202694000000006a473044022027137acdeb7583e966a9e8825c9f5edb909b2fd46d40e6ee83846d164529630e0220644e82a46d813fac2836650ce832cdba7eb46b6c134982b36e84f5f08a921dbb412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ac040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac0000000000'
1338
- const txid942620 = '9426201003b01e5bb66e9240a1cc337174238e816a25a06ba532fa67af4457d7'
1339
- const beef9426200 = '0100beef01fe07ef0c0008022802d75744af67fa32a56ba0256a818e23747133cca140926eb65b1eb00310202694290089598873694014f9706b6074a1b349fcf59df875e58a0cf3a8900c6e5830f33601150036fffb4b882171c594a27d37c5d7827cc706811c3e0acd152e0f3ee1c7ac98f9010b002b3b5c611cbea3bbdf9e99646cb23e80b62fc2dc2e619656157296878d46d8b30104002877b6b97c95fd55c0584d0ccf683c9ad6c60be146dd73605b93ef042da648a0010300d1d01ac14f19d974f8131b9cdb38ae2abcf5403d5c3f0d2772a1c995c136356b01000017ece09932fa7c6d464c633df200003c2328beb26290c7b9b1333eb84c90c544010100cd4147f92f226bc39354a78209db1f521ffd8720e3fcc9c942ee1dd535513d0f0101005daeb3f84528a58700c58c8479f29cd627b07c198602b6c08033340262c0c607010100000001e9d6dc3084dca9606fe43406208a8dcbba568f60671ee2df4f50189cf75e9f50000000006b483045022100c9c8baa777fe09c1310788614d4eba88b6027d39eada18a5a79c6a33e55919c302205222961b1f63dddd425cd418571797cb3a68aa59ac37331c0fc61fc9de66c34d412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ad040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
1340
- const txid509f5e = '509f5ef79c18504fdfe21e67608f56bacb8d8a200634e46f60a9dc8430dcd6e9'
1341
- const beef509f5e0 = '0100beef01fe05ef0c0009029800fb09f12f5632f7af6409432fc914d1e68fc499270fa29f3b3c43fb00ebeb8ed09902e9d6dc3084dca9606fe43406208a8dcbba568f60671ee2df4f50189cf75e9f50014d00ccb4f6ea2fabdfaf86b0e3837cc702ddde19cb5112e680f00832330b03be1d290127000480365544cd4dca6262cc25567f241d650339773f3f9ef1b9a25b9a034945c30112004ab6bdfecf74af1da1e0574671d06aef63dcde80133d1a6c46beda2ad08abc15010800ce75abf88ee3de3e2f89be0462e1cb0b9470b67947b9880e941bb6565a44bc86010500940d69edc69fc8694485452b961b8df8a2e0c1ff7bdbfeee1e6855542a7e7cd10103002217abcbd7e5431fffc0f52624ee3cf659e39734ae5335d5d196193f7f6e39ab01000082d3a59ca910a1c6fc1f06ab2a5ea80ffee7a39d9a987c85701cfaaf604219f80101008bed85ed818e9d69147000e8687b2efed77e5ee0073e5a2640033715747bd0c2010100000001c5b2a279b9b1fec42846b9d6ed7cf6345b08dcdd4b4a17e2d017b98edb347787000000006b483045022100c0992a684be99a07bc87e03bf88701acefc033c1aa57cf4a3b15253e64c8962a022031d5b3af1684a1a1a201eb16fc1fbca8522ae16b9c0d340e8bec48eeea1fe458412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ae040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
1342
- const txid877734 = '877734db8eb917d0e2174a4bdddc085b34f67cedd6b94628c4feb1b979a2b2c5'
1343
- const beef8777340 = '0100beef01fedfee0c000e02fd202900eeebd665d8893249c90dabbd50266fb7e0f125dd8ee087a3c8089debf09499d4fd212902c5b2a279b9b1fec42846b9d6ed7cf6345b08dcdd4b4a17e2d017b98edb34778701fd911400b32e903076f6869d4e0a9fa2d00f5c8bad598d3ede5b4bafef88debdb1d3de6901fd490a000a79573c8eb909d30c4e9653f90ea2869e44220d551da81cc96913b2fcd734aa01fd2505003d6db6b39d889be1535a478da0c3428e8107872ac55f0fcfdd2225b6a402d1dc01fd9302008ae31e7e4d4e766814ea11b337b5b51f5d807737f802c44c8d0d75a583a61bde01fd480100e962a171fcc0e79bf55b1433d79564f8d3b2622ecbcb84c9ede792d353e0afb901a5007d0a8d8a3a2f0eb209e82f5e80ef6c4835b7d07c85d6ac8c613cc8754a47bd79015300b070576d92310f1e08a12581ecea4d1d4de48beb15ff7e42eddc1b957fa2909d012800544a58cac6b4dd78cebd84178dc1572693552fec5777a055dad0068bd67aec6101150025674a887fff026640fe9851cbffb37bd8e02780c4f6c0476ed3cee4e4884c2f010b0023aa2b01e8a59c3b79976918d95ae6a044ace2264e3690f9846f452499c0cbad01040078033c2619a3b115bfc71d09addf13e288660be1841cf6769ffb9924aca246970103004d2d6ae3e2c02703b773c49af789b7372b5b71bf92b49e2dc4b921880df984540100003b86537a85e5047596a482ac2dba30d1798b26d200c08a2fb19d8d75b2c2a1b50101000000010954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37000000006b4830450221009c67deb38aa6b5ed8bfa140e9464e1b1ee2908a296bb70e3d164725254491c6a02207b595dcced1136c276edfc1e5e5901279cf6f0a6432e4c1a908468262e7d9543412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001af040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
1344
- const txid37abad = '37abad7168d47d4e107d0f9f96813a4feef6ea346482fce554c3fade85d45409'
1345
- const beef37abad0 = '0100beef01fedcee0c000d02f8020954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37f900fa225908f222f8f7f291731a10e93cdf410189ca01adc688edf5577552aa8ff2017d006cfea8ad6ace7ecce9bbbe729eb9dca25f9be064b150d290588c3364867ee112013f00bfbacc8bfdda7f734c160fcfee972dd9182aede73290367aab62b5ce242e5655011e007a0eb94f2bcf98489247a01551a843d9cf1e091522c32f50ce347ba5057b439d010e00beb857ba543d2bd199e68c63a480c73b83660eca27d2ce105355741f27017876010600d5fb1ea8440e41b4ffde093dd53d6ed741c4fe4fe66ff43f6a4eb9af3c6ddebb010200169db3be130a9e8aff1dbff39abee477d3a224cb3e118c39794bb7ba427b707501000083cd0ce2d2734835c45331cd06f38556cf4e07634d599f59692ad9980175a7f201010022f4a629e99ca5838f56f87b050bdefa9b9f621c08745cb27b941d9c6750d99a01010032b90a4148ff984b0efee309a578cb87d278142c014f1a7fd5351b3e3cd4c7da01010082dc3ea570250357fca88652ee1628928136c29eb4910b7e5627e0ffef1d958a010100f5194f41c28960f0dba669f4715229f59f59afb5d55f50a2a7556111b8da67130101009cd7f9a65014cc6fd5c0b71391d0b70eb983ea3b03a2fbd7a13150775855953401010000000237c2148f306355078cedfe61c8574f393476a43abd2cee5b31f1c73e39db86d7000000006a47304402207ee089c64fa810ad88e03f7bad4ae57c9b7de215d222a5bf52f9259d171b717a0220643d3cb4423b5b7ffcb7b2b59eb81dede0a25319aaa0228d27e59d5e238c35a2412102641b5a033ce332a9fc0f81dee7948c099b194758d8352bd5572988ac760ae2faffffffff94646890e1cfab5204ae43e0d8f24dc5fa5a8bcdb3acdaded8b3f591c3cad4a7110000006a47304402205b92eaba4a6c029385e9cf291631b98d0c7aa6851755fb44279e2b1ebd12759702206e96f3bac252f2c5cf5edf7f8828d6358baaa9fe34aa163faeb9b31aac830e47412102577e2a3877a253d9e7587c3d77a4038b4af5e51113393c2713c6d50c9da5de6effffffff03b0040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88acc8000000000000001976a914292e61672fc24a9a70cfa6f73a9af5655652629d88ac13050000000000001976a914ad2ab55d09559074e41e2a2c1c89742e13bee03088ac000000000100'