@bsv/overlay 2.2.1 → 2.3.0
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/LICENSE.txt +57 -27
- package/README.md +102 -269
- package/dist/cjs/mod.d.ts +15 -0
- package/dist/cjs/mod.d.ts.map +1 -0
- package/dist/cjs/mod.js +4 -1
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +41 -13
- package/dist/cjs/src/Advertisement.d.ts +9 -0
- package/dist/cjs/src/Advertisement.d.ts.map +1 -0
- package/dist/cjs/src/Advertiser.d.ts +36 -0
- package/dist/cjs/src/Advertiser.d.ts.map +1 -0
- package/dist/cjs/src/BASM.d.ts +110 -0
- package/dist/cjs/src/BASM.d.ts.map +1 -0
- package/dist/cjs/src/BASM.js.map +1 -1
- package/dist/cjs/src/BASMRemote.d.ts +14 -0
- package/dist/cjs/src/BASMRemote.d.ts.map +1 -0
- package/dist/cjs/src/BASMRemote.js.map +1 -1
- package/dist/cjs/src/Engine.d.ts +379 -0
- package/dist/cjs/src/Engine.d.ts.map +1 -0
- package/dist/cjs/src/Engine.js +425 -418
- package/dist/cjs/src/Engine.js.map +1 -1
- package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts +24 -0
- package/dist/cjs/src/GASP/OverlayGASPRemote.d.ts.map +1 -0
- package/dist/cjs/src/GASP/OverlayGASPRemote.js +7 -10
- package/dist/cjs/src/GASP/OverlayGASPRemote.js.map +1 -1
- package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts +103 -0
- package/dist/cjs/src/GASP/OverlayGASPStorage.d.ts.map +1 -0
- package/dist/cjs/src/GASP/OverlayGASPStorage.js +32 -30
- package/dist/cjs/src/GASP/OverlayGASPStorage.js.map +1 -1
- package/dist/cjs/src/LookupFormula.d.ts +29 -0
- package/dist/cjs/src/LookupFormula.d.ts.map +1 -0
- package/dist/cjs/src/LookupService.d.ts +85 -0
- package/dist/cjs/src/LookupService.d.ts.map +1 -0
- package/dist/cjs/src/Output.d.ts +32 -0
- package/dist/cjs/src/Output.d.ts.map +1 -0
- package/dist/cjs/src/SafeLog.d.ts +14 -0
- package/dist/cjs/src/SafeLog.d.ts.map +1 -0
- package/dist/cjs/src/SafeLog.js +44 -0
- package/dist/cjs/src/SafeLog.js.map +1 -0
- package/{src/SyncConfiguration.ts → dist/cjs/src/SyncConfiguration.d.ts} +2 -1
- package/dist/cjs/src/SyncConfiguration.d.ts.map +1 -0
- package/dist/cjs/src/TopicManager.d.ts +35 -0
- package/dist/cjs/src/TopicManager.d.ts.map +1 -0
- package/dist/cjs/src/storage/Storage.d.ts +237 -0
- package/dist/cjs/src/storage/Storage.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/KnexStorage.d.ts +69 -0
- package/dist/cjs/src/storage/knex/KnexStorage.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/KnexStorage.js +7 -10
- package/dist/cjs/src/storage/knex/KnexStorage.js.map +1 -1
- package/dist/cjs/src/storage/knex/all-migrations.d.ts +11 -0
- package/dist/cjs/src/storage/knex/all-migrations.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/all-migrations.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2024-05-18-001-initial.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2024-07-10-001-block-height.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2024-07-17-001-transactions.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2024-07-18-001-indexes.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2025-05-28-001-enlarge.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.d.ts +9 -0
- package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.js.map +1 -1
- package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts +4 -0
- package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.d.ts.map +1 -0
- package/dist/cjs/src/storage/knex/migrations/2026-05-29-001-brc136-basm.js.map +1 -1
- package/dist/esm/mod.js +2 -1
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/BASMRemote.js.map +1 -1
- package/dist/esm/src/Engine.js +404 -396
- package/dist/esm/src/Engine.js.map +1 -1
- package/dist/esm/src/GASP/OverlayGASPRemote.js +2 -2
- package/dist/esm/src/GASP/OverlayGASPRemote.js.map +1 -1
- package/dist/esm/src/GASP/OverlayGASPStorage.js +21 -18
- package/dist/esm/src/GASP/OverlayGASPStorage.js.map +1 -1
- package/dist/esm/src/SafeLog.js +40 -0
- package/dist/esm/src/SafeLog.js.map +1 -0
- package/dist/esm/src/storage/knex/KnexStorage.js.map +1 -1
- package/dist/types/mod.d.ts +5 -4
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/BASMRemote.d.ts.map +1 -1
- package/dist/types/src/Engine.d.ts +18 -1
- package/dist/types/src/Engine.d.ts.map +1 -1
- package/dist/types/src/GASP/OverlayGASPRemote.d.ts +2 -2
- package/dist/types/src/GASP/OverlayGASPRemote.d.ts.map +1 -1
- package/dist/types/src/GASP/OverlayGASPStorage.d.ts +3 -1
- package/dist/types/src/GASP/OverlayGASPStorage.d.ts.map +1 -1
- package/dist/types/src/SafeLog.d.ts +14 -0
- package/dist/types/src/SafeLog.d.ts.map +1 -0
- package/dist/types/src/storage/knex/KnexStorage.d.ts.map +1 -1
- package/dist/types/src/storage/knex/all-migrations.d.ts.map +1 -1
- package/package.json +85 -28
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +0 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +0 -1
- package/dist/types/tsconfig.types.tsbuildinfo +0 -1
- package/docs/API.md +0 -944
- package/docs/BRC-136-BASM.md +0 -327
- package/docs/README.md +0 -16
- package/docs/Synchronization.md +0 -212
- package/docs/concepts/00-overview.md +0 -85
- package/docs/concepts/01-best-practices.md +0 -202
- package/docs/concepts/02-query-performance.md +0 -345
- package/docs/concepts/03-database-monitoring.md +0 -211
- package/docs/concepts/04-pagination-example.md +0 -186
- package/docs/concepts/05-recommendations-summary.md +0 -158
- package/docs/concepts/README.md +0 -20
- package/docs/examples/README.md +0 -15
- package/docs/examples/gs-wip.md +0 -111
- package/docs/internal/README.md +0 -11
- package/mod.ts +0 -40
- package/src/Advertisement.ts +0 -8
- package/src/Advertiser.ts +0 -41
- package/src/BASM.ts +0 -208
- package/src/BASMRemote.ts +0 -54
- package/src/Engine.ts +0 -2194
- package/src/GASP/OverlayGASPRemote.ts +0 -108
- package/src/GASP/OverlayGASPStorage.ts +0 -384
- package/src/LookupFormula.ts +0 -31
- package/src/LookupService.ts +0 -136
- package/src/Output.ts +0 -31
- package/src/TopicManager.ts +0 -40
- package/src/__tests/BASM.test.ts +0 -44
- package/src/__tests/BASMChain.test.ts +0 -295
- package/src/__tests/Engine.test.ts +0 -1381
- package/src/__tests/OverlayGASPRemote.test.ts +0 -135
- package/src/__tests/OverlayGASPStorage.test.ts +0 -205
- package/src/storage/Storage.ts +0 -260
- package/src/storage/knex/KnexStorage.ts +0 -651
- package/src/storage/knex/all-migrations.ts +0 -32
- package/src/storage/knex/migrations/2024-05-18-001-initial.ts +0 -30
- package/src/storage/knex/migrations/2024-07-10-001-block-height.ts +0 -15
- package/src/storage/knex/migrations/2024-07-17-001-transactions.ts +0 -64
- package/src/storage/knex/migrations/2024-07-18-001-indexes.ts +0 -30
- package/src/storage/knex/migrations/2025-05-28-001-enlarge.ts +0 -42
- package/src/storage/knex/migrations/2025-06-25-001-gasp-pagination-support.ts +0 -46
- package/src/storage/knex/migrations/2025-07-22-001-fix-score-column-type.ts +0 -24
- package/src/storage/knex/migrations/2025-11-11-001-utxo-lookup-index.ts +0 -18
- package/src/storage/knex/migrations/2026-05-29-001-brc136-basm.ts +0 -123
|
@@ -1,1381 +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('persists a replacement proof when the stored BEEF is already mined', async () => {
|
|
313
|
-
const tx = Transaction.fromHexBEEF(beef37abad0)
|
|
314
|
-
const txid = tx.id('hex')
|
|
315
|
-
const output: Output = {
|
|
316
|
-
txid,
|
|
317
|
-
outputIndex: 0,
|
|
318
|
-
outputScript: tx.outputs[0].lockingScript.toBinary(),
|
|
319
|
-
topic: 'hello',
|
|
320
|
-
satoshis: tx.outputs[0].satoshis,
|
|
321
|
-
beef: tx.toBEEF(),
|
|
322
|
-
spent: false,
|
|
323
|
-
outputsConsumed: [],
|
|
324
|
-
consumedBy: [],
|
|
325
|
-
score: Date.now()
|
|
326
|
-
}
|
|
327
|
-
const updateTransactionBEEF = jest.fn(async () => undefined)
|
|
328
|
-
mockStorageEngine.findOutputsForTransaction = jest.fn(async () => [output])
|
|
329
|
-
mockStorageEngine.updateTransactionBEEF = updateTransactionBEEF
|
|
330
|
-
const engine = new Engine(
|
|
331
|
-
{ Hello: mockTopicManager },
|
|
332
|
-
{ Hello: mockLookupService },
|
|
333
|
-
mockStorageEngine,
|
|
334
|
-
mockChainTracker
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
if (tx.merklePath === undefined) throw new Error('improper test setup')
|
|
338
|
-
await engine.handleNewMerkleProof(txid, tx.merklePath)
|
|
339
|
-
|
|
340
|
-
expect(updateTransactionBEEF).toHaveBeenCalledWith(txid, tx.toBEEF())
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
it('0 simple proof', async () => {
|
|
344
|
-
const beef = beef27c8f1
|
|
345
|
-
const txid = txid27c8f
|
|
346
|
-
const tx = Transaction.fromHexBEEF(beef)
|
|
347
|
-
|
|
348
|
-
const output27c8fZero: Output = {
|
|
349
|
-
txid,
|
|
350
|
-
outputIndex: 0,
|
|
351
|
-
outputScript: tx.outputs[0].lockingScript.toBinary(),
|
|
352
|
-
topic: 'hello',
|
|
353
|
-
satoshis: tx.outputs[0].satoshis,
|
|
354
|
-
beef: tx.toBEEF(),
|
|
355
|
-
spent: false,
|
|
356
|
-
outputsConsumed: [],
|
|
357
|
-
consumedBy: [],
|
|
358
|
-
score: Date.now()
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
362
|
-
txid,
|
|
363
|
-
outputIndex: 0,
|
|
364
|
-
history: 1
|
|
365
|
-
}])
|
|
366
|
-
let newBEEF: number[] = []
|
|
367
|
-
mockStorageEngine.findOutput = jest.fn(async () => output27c8fZero)
|
|
368
|
-
mockStorageEngine.findOutputsForTransaction = jest.fn(async () => [output27c8fZero])
|
|
369
|
-
mockStorageEngine.updateTransactionBEEF = jest.fn(async (txid: string, beef: number[]) => {
|
|
370
|
-
newBEEF = beef
|
|
371
|
-
})
|
|
372
|
-
const engine = new Engine(
|
|
373
|
-
{
|
|
374
|
-
Hello: mockTopicManager
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
Hello: mockLookupService
|
|
378
|
-
},
|
|
379
|
-
mockStorageEngine,
|
|
380
|
-
mockChainTracker
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
const merklePath = Transaction.fromHexBEEF(beef27c8f0).merklePath
|
|
384
|
-
if (merklePath == null) throw new Error('improper test setup')
|
|
385
|
-
await engine.handleNewMerkleProof(txid, merklePath)
|
|
386
|
-
expect(newBEEF.length > 0 && newBEEF.length < beef.length / 2).toBe(true)
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
it('1 recurse proof', async () => {
|
|
390
|
-
const outputs: Output[] = []
|
|
391
|
-
const findOutput = (txid: string, outputIndex: number, includeBEEF?: boolean): Output => {
|
|
392
|
-
const i = outputs.findIndex(o => o.txid === txid && o.outputIndex === outputIndex)
|
|
393
|
-
if (i < 0) throw new Error(`missing output ${txid} ${outputIndex}`)
|
|
394
|
-
return outputs[i]
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const addConsumingOutput = (tx: Transaction, outputIndex: number, consumes?: Output): Output => {
|
|
398
|
-
const o: Output = {
|
|
399
|
-
txid: tx.id('hex'),
|
|
400
|
-
outputIndex,
|
|
401
|
-
outputScript: tx.outputs[outputIndex].lockingScript.toBinary(),
|
|
402
|
-
topic: 'hello',
|
|
403
|
-
satoshis: tx.outputs[outputIndex].satoshis,
|
|
404
|
-
beef: tx.toBEEF(),
|
|
405
|
-
spent: false,
|
|
406
|
-
outputsConsumed: [],
|
|
407
|
-
consumedBy: [],
|
|
408
|
-
score: Date.now()
|
|
409
|
-
}
|
|
410
|
-
if (consumes != null) {
|
|
411
|
-
const c = findOutput(consumes.txid, consumes.outputIndex)
|
|
412
|
-
c.consumedBy.push(o)
|
|
413
|
-
o.outputsConsumed.push(c)
|
|
414
|
-
}
|
|
415
|
-
outputs.push(o)
|
|
416
|
-
return o
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
mockLookupService.lookup = jest.fn(async () => [{ txid: txid17d182, outputIndex: 0, history: 1 }])
|
|
420
|
-
const newBEEF: Record<string, string> = {}
|
|
421
|
-
mockStorageEngine.findOutput = jest.fn(async (txid: string, outputIndex: number, topic?: string, spent?: boolean, includeBEEF?: boolean) => {
|
|
422
|
-
return findOutput(txid, outputIndex, true)
|
|
423
|
-
})
|
|
424
|
-
mockStorageEngine.findOutputsForTransaction = jest.fn(async (txid: string, includeBEEF?: boolean) => {
|
|
425
|
-
const os = outputs.filter(o => o.txid === txid)
|
|
426
|
-
return os
|
|
427
|
-
})
|
|
428
|
-
mockStorageEngine.updateTransactionBEEF = jest.fn(async (txid: string, beef: number[]) => {
|
|
429
|
-
newBEEF[txid] = Utils.toHex(beef)
|
|
430
|
-
})
|
|
431
|
-
const engine = new Engine({ Hello: mockTopicManager }, { Hello: mockLookupService }, mockStorageEngine, mockChainTracker)
|
|
432
|
-
|
|
433
|
-
/*
|
|
434
|
-
txid 17d1829ba8424b97369ee8b528ee8b65d9d4b9c08d037d224a7be7c025f78f78 output 0 1196 sats
|
|
435
|
-
txid 9426201003b01e5bb66e9240a1cc337174238e816a25a06ba532fa67af4457d7 output 0 1197 sats
|
|
436
|
-
txid 509f5ef79c18504fdfe21e67608f56bacb8d8a200634e46f60a9dc8430dcd6e9 output 0 1198 sats
|
|
437
|
-
txid 877734db8eb917d0e2174a4bdddc085b34f67cedd6b94628c4feb1b979a2b2c5 output 0 1199 sats
|
|
438
|
-
txid 37abad7168d47d4e107d0f9f96813a4feef6ea346482fce554c3fade85d45409 output 0 1200 sats
|
|
439
|
-
txid d786db393ec7f1315bee2cbd3aa47634394f57c861feed8c075563308f14c237 output 0 1568 sats
|
|
440
|
-
txid a7d4cac391f5b3d8dedaacb3cd8b5afac54df2d8e043ae0452abcfe190686494 output 17 1132 sats
|
|
441
|
-
*/
|
|
442
|
-
|
|
443
|
-
const tx17d182 = Transaction.fromHexBEEF(beef17d1824)
|
|
444
|
-
const tx942620 = tx17d182.inputs[0].sourceTransaction
|
|
445
|
-
const tx509f5e = tx942620.inputs[0].sourceTransaction
|
|
446
|
-
const tx877734 = tx509f5e.inputs[0].sourceTransaction
|
|
447
|
-
const tx37abad = tx877734.inputs[0].sourceTransaction
|
|
448
|
-
|
|
449
|
-
const output37abadZero = addConsumingOutput(tx37abad, 0)
|
|
450
|
-
const output877734Zero = addConsumingOutput(tx877734, 0, output37abadZero)
|
|
451
|
-
const output509f5eZero = addConsumingOutput(tx509f5e, 0, output877734Zero)
|
|
452
|
-
const output942620Zero = addConsumingOutput(tx942620, 0, output509f5eZero)
|
|
453
|
-
addConsumingOutput(tx17d182, 0, output942620Zero)
|
|
454
|
-
|
|
455
|
-
const mp37abad = Transaction.fromHexBEEF(beef37abad0).merklePath
|
|
456
|
-
await engine.handleNewMerkleProof(txid37abad, mp37abad)
|
|
457
|
-
expect(newBEEF[`${txid37abad}`]).toBe(beef37abad0)
|
|
458
|
-
expect(newBEEF[`${txid877734}`].length).toBeGreaterThan(beef8777340.length)
|
|
459
|
-
expect(newBEEF[`${txid509f5e}`].length).toBeGreaterThan(beef509f5e0.length)
|
|
460
|
-
expect(newBEEF[`${txid942620}`].length).toBeGreaterThan(beef9426200.length)
|
|
461
|
-
expect(newBEEF[`${txid17d182}`].length).toBeGreaterThan(beef17d1820.length)
|
|
462
|
-
expect(Object.keys(newBEEF)).toHaveLength(5)
|
|
463
|
-
|
|
464
|
-
const mp877734 = Transaction.fromHexBEEF(beef8777340).merklePath
|
|
465
|
-
await engine.handleNewMerkleProof(txid877734, mp877734)
|
|
466
|
-
expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
|
|
467
|
-
expect(newBEEF[`${txid509f5e}`].length).toBeGreaterThan(beef509f5e0.length)
|
|
468
|
-
expect(newBEEF[`${txid942620}`].length).toBeGreaterThan(beef9426200.length)
|
|
469
|
-
expect(newBEEF[`${txid17d182}`].length).toBeGreaterThan(beef17d1820.length)
|
|
470
|
-
expect(Object.keys(newBEEF)).toHaveLength(5)
|
|
471
|
-
|
|
472
|
-
const mp509f5e = Transaction.fromHexBEEF(beef509f5e0).merklePath
|
|
473
|
-
await engine.handleNewMerkleProof(txid509f5e, mp509f5e)
|
|
474
|
-
expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
|
|
475
|
-
expect(newBEEF[`${txid509f5e}`]).toBe(beef509f5e0)
|
|
476
|
-
expect(newBEEF[`${txid942620}`].length).toBeGreaterThan(beef9426200.length)
|
|
477
|
-
expect(newBEEF[`${txid17d182}`].length).toBeGreaterThan(beef17d1820.length)
|
|
478
|
-
expect(Object.keys(newBEEF)).toHaveLength(5)
|
|
479
|
-
|
|
480
|
-
const mp942620 = Transaction.fromHexBEEF(beef9426200).merklePath
|
|
481
|
-
await engine.handleNewMerkleProof(txid942620, mp942620)
|
|
482
|
-
expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
|
|
483
|
-
expect(newBEEF[`${txid509f5e}`]).toBe(beef509f5e0)
|
|
484
|
-
expect(newBEEF[`${txid942620}`]).toBe(beef9426200)
|
|
485
|
-
expect(newBEEF[`${txid17d182}`].length).toBeGreaterThan(beef17d1820.length)
|
|
486
|
-
expect(Object.keys(newBEEF)).toHaveLength(5)
|
|
487
|
-
|
|
488
|
-
const mp17d182 = Transaction.fromHexBEEF(beef17d1820).merklePath
|
|
489
|
-
await engine.handleNewMerkleProof(txid17d182, mp17d182)
|
|
490
|
-
expect(newBEEF[`${txid877734}`]).toBe(beef8777340)
|
|
491
|
-
expect(newBEEF[`${txid509f5e}`]).toBe(beef509f5e0)
|
|
492
|
-
expect(newBEEF[`${txid942620}`]).toBe(beef9426200)
|
|
493
|
-
expect(newBEEF[`${txid17d182}`]).toBe(beef17d1820)
|
|
494
|
-
expect(Object.keys(newBEEF)).toHaveLength(5)
|
|
495
|
-
})
|
|
496
|
-
})
|
|
497
|
-
|
|
498
|
-
describe('submit', () => {
|
|
499
|
-
it('Throws an error if the user submits to a topic that is not supported', async () => {
|
|
500
|
-
const engine = new Engine(
|
|
501
|
-
{
|
|
502
|
-
Hello: mockTopicManager
|
|
503
|
-
},
|
|
504
|
-
{},
|
|
505
|
-
mockStorageEngine,
|
|
506
|
-
mockChainTracker
|
|
507
|
-
)
|
|
508
|
-
await expect(engine.submit({
|
|
509
|
-
beef: exampleBeef,
|
|
510
|
-
topics: ['hello']
|
|
511
|
-
})).rejects.toHaveProperty('message', 'This server does not support this topic: hello')
|
|
512
|
-
})
|
|
513
|
-
it('Verifies the BEEF for the provided transaction', async () => {
|
|
514
|
-
const engine = new Engine(
|
|
515
|
-
{
|
|
516
|
-
Hello: mockTopicManager
|
|
517
|
-
},
|
|
518
|
-
{},
|
|
519
|
-
mockStorageEngine,
|
|
520
|
-
mockChainTracker
|
|
521
|
-
)
|
|
522
|
-
await engine.submit({
|
|
523
|
-
beef: exampleBeef,
|
|
524
|
-
topics: ['Hello']
|
|
525
|
-
})
|
|
526
|
-
expect(mockChainTracker.isValidRootForHeight).toHaveBeenCalledWith('bb6f640cc4ee56bf38eb5a1969ac0c16caa2d3d202b22bf3735d10eec0ca6e00', 814435)
|
|
527
|
-
})
|
|
528
|
-
it('Throws an error if an invalid envelope is provided', async () => {
|
|
529
|
-
const engine = new Engine(
|
|
530
|
-
{
|
|
531
|
-
Hello: mockTopicManager
|
|
532
|
-
},
|
|
533
|
-
{},
|
|
534
|
-
mockStorageEngine,
|
|
535
|
-
mockChainTracker
|
|
536
|
-
)
|
|
537
|
-
mockChainTracker.isValidRootForHeight.mockReturnValueOnce(Promise.resolve(false))
|
|
538
|
-
await expect(engine.submit({
|
|
539
|
-
beef: exampleBeef,
|
|
540
|
-
topics: ['Hello']
|
|
541
|
-
})).rejects.toHaveProperty('message', 'Invalid merkle path for transaction 3ecead27a44d013ad1aae40038acbb1883ac9242406808bb4667c15b4f164eac')
|
|
542
|
-
})
|
|
543
|
-
describe('Broadcast gating (PHASE 2)', () => {
|
|
544
|
-
const makeBroadcaster = (): { broadcast: jest.Mock } => ({
|
|
545
|
-
broadcast: jest.fn(async () => ({ status: 'success', txid: exampleTXID, message: 'ok' }))
|
|
546
|
-
})
|
|
547
|
-
const makeEngine = (broadcaster: any): Engine => new Engine(
|
|
548
|
-
{ Hello: mockTopicManager },
|
|
549
|
-
{},
|
|
550
|
-
mockStorageEngine,
|
|
551
|
-
mockChainTracker,
|
|
552
|
-
undefined,
|
|
553
|
-
undefined,
|
|
554
|
-
undefined,
|
|
555
|
-
broadcaster
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
it('broadcasts when at least one topic admits outputs', async () => {
|
|
559
|
-
const broadcaster = makeBroadcaster()
|
|
560
|
-
const engine = makeEngine(broadcaster)
|
|
561
|
-
await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
|
|
562
|
-
expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
it('never broadcasts a transaction every topic manager rejected', async () => {
|
|
566
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => ({
|
|
567
|
-
outputsToAdmit: [],
|
|
568
|
-
coinsToRetain: []
|
|
569
|
-
}))
|
|
570
|
-
const broadcaster = makeBroadcaster()
|
|
571
|
-
const engine = makeEngine(broadcaster)
|
|
572
|
-
await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
|
|
573
|
-
expect(broadcaster.broadcast).not.toHaveBeenCalled()
|
|
574
|
-
})
|
|
575
|
-
|
|
576
|
-
it('never broadcasts when topic validation throws, even with tracked coins consumed', async () => {
|
|
577
|
-
// The rejected tx spends previously-admitted coins — a throw must
|
|
578
|
-
// still gate the broadcast (this is the rejected-transfer shape).
|
|
579
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
580
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
|
|
581
|
-
throw new Error('rule violation')
|
|
582
|
-
})
|
|
583
|
-
const broadcaster = makeBroadcaster()
|
|
584
|
-
const engine = makeEngine(broadcaster)
|
|
585
|
-
await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
|
|
586
|
-
expect(broadcaster.broadcast).not.toHaveBeenCalled()
|
|
587
|
-
})
|
|
588
|
-
|
|
589
|
-
it('broadcasts a consume-only transaction that retains nothing (history purge)', async () => {
|
|
590
|
-
// e.g. a KVStore remove that also purges history: previously-admitted
|
|
591
|
-
// coins are consumed, nothing admitted, nothing retained. That is an
|
|
592
|
-
// acceptance, not a rejection (rejection = throw).
|
|
593
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
594
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => ({
|
|
595
|
-
outputsToAdmit: [],
|
|
596
|
-
coinsToRetain: []
|
|
597
|
-
}))
|
|
598
|
-
const broadcaster = makeBroadcaster()
|
|
599
|
-
const engine = makeEngine(broadcaster)
|
|
600
|
-
await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
|
|
601
|
-
expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
|
|
602
|
-
})
|
|
603
|
-
|
|
604
|
-
it('still broadcasts a duplicate (previously accepted) transaction', async () => {
|
|
605
|
-
mockStorageEngine.doesAppliedTransactionExist = jest.fn(async () => true)
|
|
606
|
-
const broadcaster = makeBroadcaster()
|
|
607
|
-
const engine = makeEngine(broadcaster)
|
|
608
|
-
await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
|
|
609
|
-
expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
|
|
610
|
-
})
|
|
611
|
-
|
|
612
|
-
it('broadcasts when outputs are consumed but none admitted (coinsToRetain only)', async () => {
|
|
613
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
614
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => ({
|
|
615
|
-
outputsToAdmit: [],
|
|
616
|
-
coinsToRetain: [0]
|
|
617
|
-
}))
|
|
618
|
-
const broadcaster = makeBroadcaster()
|
|
619
|
-
const engine = makeEngine(broadcaster)
|
|
620
|
-
await engine.submit({ beef: exampleBeef, topics: ['Hello'] })
|
|
621
|
-
expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
|
|
622
|
-
})
|
|
623
|
-
})
|
|
624
|
-
|
|
625
|
-
describe('For each topic being processed', () => {
|
|
626
|
-
it('Checks for duplicate transactions', async () => {
|
|
627
|
-
const engine = new Engine(
|
|
628
|
-
{
|
|
629
|
-
Hello: mockTopicManager
|
|
630
|
-
},
|
|
631
|
-
{},
|
|
632
|
-
mockStorageEngine,
|
|
633
|
-
mockChainTracker
|
|
634
|
-
)
|
|
635
|
-
await engine.submit({
|
|
636
|
-
beef: exampleBeef,
|
|
637
|
-
topics: ['Hello']
|
|
638
|
-
})
|
|
639
|
-
expect(mockStorageEngine.doesAppliedTransactionExist).toHaveBeenCalledWith({ txid: exampleTXID, topic: 'Hello' })
|
|
640
|
-
})
|
|
641
|
-
it('Does not process the output if the transaction was already applied to the topic', async () => {
|
|
642
|
-
mockStorageEngine.doesAppliedTransactionExist = jest.fn(async () => true)
|
|
643
|
-
const engine = new Engine(
|
|
644
|
-
{
|
|
645
|
-
Hello: mockTopicManager
|
|
646
|
-
},
|
|
647
|
-
{},
|
|
648
|
-
mockStorageEngine,
|
|
649
|
-
mockChainTracker
|
|
650
|
-
)
|
|
651
|
-
await engine.submit({
|
|
652
|
-
beef: exampleBeef,
|
|
653
|
-
topics: ['Hello']
|
|
654
|
-
})
|
|
655
|
-
expect(mockStorageEngine.findOutput).not.toHaveBeenCalled()
|
|
656
|
-
expect(mockStorageEngine.markUTXOAsSpent).not.toHaveBeenCalled()
|
|
657
|
-
expect(mockStorageEngine.insertOutput).not.toHaveBeenCalled()
|
|
658
|
-
})
|
|
659
|
-
describe('For each input of the transaction', () => {
|
|
660
|
-
it('Acquires the appropriate previous topical UTXOs from the storage engine', async () => {
|
|
661
|
-
// Mock findUTXO to return a UTXO
|
|
662
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
663
|
-
const engine = new Engine(
|
|
664
|
-
{
|
|
665
|
-
Hello: mockTopicManager
|
|
666
|
-
},
|
|
667
|
-
{},
|
|
668
|
-
mockStorageEngine,
|
|
669
|
-
mockChainTracker
|
|
670
|
-
)
|
|
671
|
-
|
|
672
|
-
// Submit the utxo
|
|
673
|
-
await engine.submit({
|
|
674
|
-
beef: exampleBeef,
|
|
675
|
-
topics: ['Hello']
|
|
676
|
-
})
|
|
677
|
-
expect(mockStorageEngine.findOutput).toHaveBeenCalled()
|
|
678
|
-
})
|
|
679
|
-
it('Includes the appropriate previous topical UTXOs when they are returned from the storage engine', async () => {
|
|
680
|
-
// Mock findUTXO to return a UTXO
|
|
681
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
682
|
-
const engine = new Engine(
|
|
683
|
-
{
|
|
684
|
-
Hello: mockTopicManager
|
|
685
|
-
},
|
|
686
|
-
{},
|
|
687
|
-
mockStorageEngine,
|
|
688
|
-
mockChainTracker
|
|
689
|
-
)
|
|
690
|
-
|
|
691
|
-
// Submit the utxo
|
|
692
|
-
await engine.submit({
|
|
693
|
-
beef: exampleBeef,
|
|
694
|
-
topics: ['Hello']
|
|
695
|
-
})
|
|
696
|
-
expect(mockStorageEngine.findOutput).toHaveBeenCalled()
|
|
697
|
-
expect(mockStorageEngine.markUTXOAsSpent).toHaveBeenCalledWith(exampleTXID, 0, 'Hello')
|
|
698
|
-
})
|
|
699
|
-
})
|
|
700
|
-
it('Identifies admissible outputs with the appropriate topic manager', async () => {
|
|
701
|
-
// Mock findUTXO to return a UTXO
|
|
702
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
703
|
-
const engine = new Engine(
|
|
704
|
-
{
|
|
705
|
-
Hello: mockTopicManager
|
|
706
|
-
},
|
|
707
|
-
{},
|
|
708
|
-
mockStorageEngine,
|
|
709
|
-
mockChainTracker
|
|
710
|
-
)
|
|
711
|
-
|
|
712
|
-
await engine.submit({
|
|
713
|
-
beef: exampleBeef,
|
|
714
|
-
topics: ['Hello']
|
|
715
|
-
})
|
|
716
|
-
expect(engine.managers.Hello.identifyAdmissibleOutputs).toHaveBeenCalledWith(
|
|
717
|
-
exampleBeef,
|
|
718
|
-
[0],
|
|
719
|
-
undefined,
|
|
720
|
-
'current-tx'
|
|
721
|
-
)
|
|
722
|
-
})
|
|
723
|
-
describe('When previous UTXOs were retained by the topic manager', () => {
|
|
724
|
-
it('Notifies all lookup services about the output being spent (not deleted, see the comment about this in deleteUTXODeep)', async () => {
|
|
725
|
-
// Mock findUTXO to return a UTXO
|
|
726
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
727
|
-
const engine = new Engine(
|
|
728
|
-
{
|
|
729
|
-
Hello: mockTopicManager
|
|
730
|
-
},
|
|
731
|
-
{
|
|
732
|
-
Hello: mockLookupService
|
|
733
|
-
},
|
|
734
|
-
mockStorageEngine,
|
|
735
|
-
mockChainTracker
|
|
736
|
-
)
|
|
737
|
-
|
|
738
|
-
// Submit the utxo
|
|
739
|
-
await engine.submit({
|
|
740
|
-
beef: exampleBeef,
|
|
741
|
-
topics: ['Hello']
|
|
742
|
-
})
|
|
743
|
-
expect(engine.lookupServices.Hello.outputSpent).toHaveBeenCalledWith({
|
|
744
|
-
mode: 'none',
|
|
745
|
-
txid: exampleTXID,
|
|
746
|
-
outputIndex: 0,
|
|
747
|
-
topic: 'Hello'
|
|
748
|
-
})
|
|
749
|
-
})
|
|
750
|
-
})
|
|
751
|
-
describe('When previous UTXOs were not retained by the topic manager', () => {
|
|
752
|
-
it('Marks the UTXO as stale, deleting all stale UTXOs by calling deleteUTXODeep', async () => {
|
|
753
|
-
// Mock findUTXO to return a UTXO
|
|
754
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
755
|
-
const engine = new Engine(
|
|
756
|
-
{
|
|
757
|
-
Hello: mockTopicManager
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
Hello: mockLookupService
|
|
761
|
-
},
|
|
762
|
-
mockStorageEngine,
|
|
763
|
-
mockChainTracker
|
|
764
|
-
)
|
|
765
|
-
|
|
766
|
-
// Submit the utxo
|
|
767
|
-
await engine.submit({
|
|
768
|
-
beef: exampleBeef,
|
|
769
|
-
topics: ['Hello']
|
|
770
|
-
})
|
|
771
|
-
// Test that previous UTXOs are deleted
|
|
772
|
-
expect(mockStorageEngine.deleteOutput).toHaveBeenCalledWith(exampleTXID, 0, 'hello')
|
|
773
|
-
expect(mockLookupService.outputNoLongerRetainedInHistory).toHaveBeenCalledWith(exampleTXID, 0, 'hello')
|
|
774
|
-
})
|
|
775
|
-
it('Notifies all lookup services about the output being spent (the notification about the actual deletion will come from deleteUTXODeep)', async () => {
|
|
776
|
-
// Mock findUTXO to return a UTXO
|
|
777
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
778
|
-
const engine = new Engine(
|
|
779
|
-
{
|
|
780
|
-
Hello: mockTopicManager
|
|
781
|
-
},
|
|
782
|
-
{
|
|
783
|
-
Hello: mockLookupService
|
|
784
|
-
},
|
|
785
|
-
mockStorageEngine,
|
|
786
|
-
mockChainTracker
|
|
787
|
-
)
|
|
788
|
-
|
|
789
|
-
// Submit the utxo
|
|
790
|
-
await engine.submit({
|
|
791
|
-
beef: exampleBeef,
|
|
792
|
-
topics: ['Hello']
|
|
793
|
-
})
|
|
794
|
-
// Was the lookup service notified of the output deletion?
|
|
795
|
-
expect(mockLookupService.outputNoLongerRetainedInHistory).toHaveBeenCalledWith(exampleTXID, 0, 'hello')
|
|
796
|
-
})
|
|
797
|
-
})
|
|
798
|
-
it('Adds admissible UTXOs to the storage engine', async () => {
|
|
799
|
-
// Mock findUTXO to return a UTXO
|
|
800
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
801
|
-
const engine = new Engine(
|
|
802
|
-
{
|
|
803
|
-
hello: mockTopicManager
|
|
804
|
-
},
|
|
805
|
-
{
|
|
806
|
-
Hello: mockLookupService
|
|
807
|
-
},
|
|
808
|
-
mockStorageEngine,
|
|
809
|
-
mockChainTracker
|
|
810
|
-
)
|
|
811
|
-
|
|
812
|
-
// Submit the utxo
|
|
813
|
-
await engine.submit({
|
|
814
|
-
beef: exampleBeef,
|
|
815
|
-
topics: ['hello']
|
|
816
|
-
})
|
|
817
|
-
// Test the new UTXO was added
|
|
818
|
-
expect(mockStorageEngine.insertOutput).toHaveBeenCalledWith(expect.objectContaining({
|
|
819
|
-
...mockOutput,
|
|
820
|
-
score: expect.any(Number)
|
|
821
|
-
}))
|
|
822
|
-
})
|
|
823
|
-
it('Notifies lookup services about incoming admissible UTXOs', async () => {
|
|
824
|
-
// Mock findUTXO to return a UTXO
|
|
825
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
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
|
-
// Test the lookup service was notified of the new UTXO
|
|
843
|
-
expect(mockLookupService.outputAdmittedByTopic).toHaveBeenCalledWith({
|
|
844
|
-
mode: 'locking-script',
|
|
845
|
-
txid: exampleTXID,
|
|
846
|
-
satoshis: 26172,
|
|
847
|
-
outputIndex: 0,
|
|
848
|
-
lockingScript: exampleTX.outputs[0].lockingScript,
|
|
849
|
-
topic: 'Hello'
|
|
850
|
-
})
|
|
851
|
-
})
|
|
852
|
-
describe('For each consumed UTXO', () => {
|
|
853
|
-
it('Finds the UTXO', async () => {
|
|
854
|
-
// Mock findUTXO to return a UTXO
|
|
855
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
856
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
|
|
857
|
-
return {
|
|
858
|
-
outputsToAdmit: [0],
|
|
859
|
-
coinsToRetain: [0]
|
|
860
|
-
}
|
|
861
|
-
})
|
|
862
|
-
const engine = new Engine(
|
|
863
|
-
{
|
|
864
|
-
Hello: mockTopicManager
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
Hello: mockLookupService
|
|
868
|
-
},
|
|
869
|
-
mockStorageEngine,
|
|
870
|
-
mockChainTracker
|
|
871
|
-
)
|
|
872
|
-
|
|
873
|
-
// Submit the utxo
|
|
874
|
-
await engine.submit({
|
|
875
|
-
beef: exampleBeef,
|
|
876
|
-
topics: ['Hello']
|
|
877
|
-
})
|
|
878
|
-
|
|
879
|
-
// Test a storage engine lookup happens for the utxo consumed
|
|
880
|
-
expect(mockStorageEngine.findOutput).toHaveBeenCalledWith(examplePreviousTXID, 0, 'Hello')
|
|
881
|
-
})
|
|
882
|
-
it('Updates the UTXO to reflect that it is now additionally consumed by the newly-created UTXOs', async () => {
|
|
883
|
-
// Mock findUTXO to return a UTXO
|
|
884
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
885
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
|
|
886
|
-
return {
|
|
887
|
-
outputsToAdmit: [0],
|
|
888
|
-
coinsToRetain: [0]
|
|
889
|
-
}
|
|
890
|
-
})
|
|
891
|
-
const engine = new Engine(
|
|
892
|
-
{
|
|
893
|
-
Hello: mockTopicManager
|
|
894
|
-
},
|
|
895
|
-
{
|
|
896
|
-
Hello: mockLookupService
|
|
897
|
-
},
|
|
898
|
-
mockStorageEngine,
|
|
899
|
-
mockChainTracker
|
|
900
|
-
)
|
|
901
|
-
|
|
902
|
-
// Submit the utxo
|
|
903
|
-
await engine.submit({
|
|
904
|
-
beef: exampleBeef,
|
|
905
|
-
topics: ['Hello']
|
|
906
|
-
})
|
|
907
|
-
|
|
908
|
-
// Test the consumedBy data is updated
|
|
909
|
-
expect(mockStorageEngine.updateConsumedBy).toHaveBeenCalledWith(examplePreviousTXID, 0, 'Hello', [{
|
|
910
|
-
txid: exampleTXID,
|
|
911
|
-
outputIndex: 0
|
|
912
|
-
}])
|
|
913
|
-
})
|
|
914
|
-
})
|
|
915
|
-
it('Inserts a new applied transaction to avoid de-duplication', async () => {
|
|
916
|
-
// Mock findUTXO to return a UTXO
|
|
917
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
918
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
|
|
919
|
-
return {
|
|
920
|
-
outputsToAdmit: [0],
|
|
921
|
-
coinsToRetain: [0]
|
|
922
|
-
}
|
|
923
|
-
})
|
|
924
|
-
const engine = new Engine(
|
|
925
|
-
{
|
|
926
|
-
Hello: mockTopicManager
|
|
927
|
-
},
|
|
928
|
-
{
|
|
929
|
-
Hello: mockLookupService
|
|
930
|
-
},
|
|
931
|
-
mockStorageEngine,
|
|
932
|
-
mockChainTracker
|
|
933
|
-
)
|
|
934
|
-
|
|
935
|
-
// Submit the utxo
|
|
936
|
-
await engine.submit({
|
|
937
|
-
beef: exampleBeef,
|
|
938
|
-
topics: ['Hello']
|
|
939
|
-
})
|
|
940
|
-
|
|
941
|
-
// Test the tx is inserted
|
|
942
|
-
expect(mockStorageEngine.insertAppliedTransaction).toHaveBeenCalledWith(expect.objectContaining({
|
|
943
|
-
txid: exampleTXID,
|
|
944
|
-
topic: 'Hello'
|
|
945
|
-
}))
|
|
946
|
-
})
|
|
947
|
-
it('Returns a correct set of admitted topics and outputs', async () => {
|
|
948
|
-
// Mock findUTXO to return a UTXO
|
|
949
|
-
mockStorageEngine.insertOutput = jest.fn()
|
|
950
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
951
|
-
mockTopicManager.identifyAdmissibleOutputs = jest.fn(async () => {
|
|
952
|
-
return {
|
|
953
|
-
outputsToAdmit: [0],
|
|
954
|
-
coinsToRetain: [0]
|
|
955
|
-
}
|
|
956
|
-
})
|
|
957
|
-
const engine = new Engine(
|
|
958
|
-
{
|
|
959
|
-
Hello: mockTopicManager
|
|
960
|
-
},
|
|
961
|
-
{
|
|
962
|
-
Hello: mockLookupService
|
|
963
|
-
},
|
|
964
|
-
mockStorageEngine,
|
|
965
|
-
mockChainTracker
|
|
966
|
-
)
|
|
967
|
-
|
|
968
|
-
// Submit the utxo
|
|
969
|
-
const results = await engine.submit({
|
|
970
|
-
beef: exampleBeef,
|
|
971
|
-
topics: ['Hello']
|
|
972
|
-
})
|
|
973
|
-
|
|
974
|
-
// Test the correct outputs are admitted
|
|
975
|
-
expect(results).toEqual({
|
|
976
|
-
Hello: {
|
|
977
|
-
outputsToAdmit: [0],
|
|
978
|
-
coinsToRetain: [0],
|
|
979
|
-
coinsRemoved: []
|
|
980
|
-
}
|
|
981
|
-
})
|
|
982
|
-
})
|
|
983
|
-
})
|
|
984
|
-
|
|
985
|
-
describe('lookup', () => {
|
|
986
|
-
it('Throws an error if no lookup service has this provider name', async () => {
|
|
987
|
-
const engine = new Engine(
|
|
988
|
-
{
|
|
989
|
-
Hello: mockTopicManager
|
|
990
|
-
},
|
|
991
|
-
{
|
|
992
|
-
Hello: mockLookupService
|
|
993
|
-
},
|
|
994
|
-
mockStorageEngine,
|
|
995
|
-
mockChainTracker
|
|
996
|
-
)
|
|
997
|
-
|
|
998
|
-
// Perform a lookup request
|
|
999
|
-
await expect(engine.lookup({
|
|
1000
|
-
service: 'HelloWorld',
|
|
1001
|
-
query: { name: 'Bob' }
|
|
1002
|
-
})).rejects.toThrow('Lookup service not found for provider: HelloWorld')
|
|
1003
|
-
})
|
|
1004
|
-
it('Calls the lookup function from the lookup service', async () => {
|
|
1005
|
-
// TODO: Make the default storage engine return something...?
|
|
1006
|
-
mockLookupService.lookup = jest.fn(async () => [{ txid: exampleTXID, outputIndex: 0 }])
|
|
1007
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1008
|
-
const engine = new Engine(
|
|
1009
|
-
{
|
|
1010
|
-
Hello: mockTopicManager
|
|
1011
|
-
},
|
|
1012
|
-
{
|
|
1013
|
-
Hello: mockLookupService
|
|
1014
|
-
},
|
|
1015
|
-
mockStorageEngine,
|
|
1016
|
-
mockChainTracker
|
|
1017
|
-
)
|
|
1018
|
-
|
|
1019
|
-
// Perform a lookup request
|
|
1020
|
-
await engine.lookup({
|
|
1021
|
-
service: 'Hello',
|
|
1022
|
-
query: { name: 'Bob' }
|
|
1023
|
-
})
|
|
1024
|
-
expect(mockLookupService.lookup).toHaveBeenCalledWith({
|
|
1025
|
-
service: 'Hello',
|
|
1026
|
-
query: { name: 'Bob' }
|
|
1027
|
-
})
|
|
1028
|
-
})
|
|
1029
|
-
it('Uses batched output loading when the storage engine supports it', async () => {
|
|
1030
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1031
|
-
txid: 'mockTXID',
|
|
1032
|
-
outputIndex: 0,
|
|
1033
|
-
history: undefined
|
|
1034
|
-
}])
|
|
1035
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1036
|
-
const findOutputsByOutpoints = jest.fn(async () => [mockOutput])
|
|
1037
|
-
;(mockStorageEngine).findOutputsByOutpoints = findOutputsByOutpoints
|
|
1038
|
-
const engine = new Engine(
|
|
1039
|
-
{
|
|
1040
|
-
Hello: mockTopicManager
|
|
1041
|
-
},
|
|
1042
|
-
{
|
|
1043
|
-
Hello: mockLookupService
|
|
1044
|
-
},
|
|
1045
|
-
mockStorageEngine,
|
|
1046
|
-
mockChainTracker
|
|
1047
|
-
)
|
|
1048
|
-
|
|
1049
|
-
await engine.lookup({
|
|
1050
|
-
service: 'Hello',
|
|
1051
|
-
query: { name: 'Bob' }
|
|
1052
|
-
})
|
|
1053
|
-
|
|
1054
|
-
expect(findOutputsByOutpoints).toHaveBeenCalledWith([{ txid: 'mockTXID', outputIndex: 0 }], true)
|
|
1055
|
-
expect(mockStorageEngine.findOutput).not.toHaveBeenCalled()
|
|
1056
|
-
})
|
|
1057
|
-
describe('For each returned result', () => {
|
|
1058
|
-
it('Finds the identified UTXO by its txid and vout', async () => {
|
|
1059
|
-
// TODO: Make the default storage engine return something...?
|
|
1060
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1061
|
-
txid: 'mockTXID',
|
|
1062
|
-
outputIndex: 0,
|
|
1063
|
-
history: undefined
|
|
1064
|
-
}])
|
|
1065
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1066
|
-
const engine = new Engine(
|
|
1067
|
-
{
|
|
1068
|
-
Hello: mockTopicManager
|
|
1069
|
-
},
|
|
1070
|
-
{
|
|
1071
|
-
Hello: mockLookupService
|
|
1072
|
-
},
|
|
1073
|
-
mockStorageEngine,
|
|
1074
|
-
mockChainTracker
|
|
1075
|
-
)
|
|
1076
|
-
|
|
1077
|
-
// Perform a lookup request
|
|
1078
|
-
await engine.lookup({
|
|
1079
|
-
service: 'Hello',
|
|
1080
|
-
query: { name: 'Bob' }
|
|
1081
|
-
})
|
|
1082
|
-
expect(mockStorageEngine.findOutput).toHaveBeenCalledWith(
|
|
1083
|
-
'mockTXID', 0, undefined, undefined, true
|
|
1084
|
-
)
|
|
1085
|
-
})
|
|
1086
|
-
it('Calls getUTXOHistory with the correct UTXO and history parameters', async () => {
|
|
1087
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1088
|
-
txid: 'mockTXID',
|
|
1089
|
-
outputIndex: 0,
|
|
1090
|
-
history: undefined
|
|
1091
|
-
}])
|
|
1092
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1093
|
-
const engine = new Engine(
|
|
1094
|
-
{
|
|
1095
|
-
Hello: mockTopicManager
|
|
1096
|
-
},
|
|
1097
|
-
{
|
|
1098
|
-
Hello: mockLookupService
|
|
1099
|
-
},
|
|
1100
|
-
mockStorageEngine,
|
|
1101
|
-
mockChainTracker
|
|
1102
|
-
)
|
|
1103
|
-
engine.getUTXOHistory = jest.fn(async () => {
|
|
1104
|
-
return mockOutput
|
|
1105
|
-
})
|
|
1106
|
-
|
|
1107
|
-
// Perform a lookup request
|
|
1108
|
-
await engine.lookup({
|
|
1109
|
-
service: 'Hello',
|
|
1110
|
-
query: { name: 'Bob' }
|
|
1111
|
-
})
|
|
1112
|
-
expect(engine.getUTXOHistory).toHaveBeenCalledWith(
|
|
1113
|
-
mockOutput,
|
|
1114
|
-
undefined,
|
|
1115
|
-
0,
|
|
1116
|
-
expect.anything()
|
|
1117
|
-
)
|
|
1118
|
-
})
|
|
1119
|
-
})
|
|
1120
|
-
it('Returns the correct set of hydrated results', async () => {
|
|
1121
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1122
|
-
txid: 'mockTXID',
|
|
1123
|
-
outputIndex: 0,
|
|
1124
|
-
history: undefined
|
|
1125
|
-
}])
|
|
1126
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1127
|
-
const engine = new Engine(
|
|
1128
|
-
{
|
|
1129
|
-
Hello: mockTopicManager
|
|
1130
|
-
},
|
|
1131
|
-
{
|
|
1132
|
-
Hello: mockLookupService
|
|
1133
|
-
},
|
|
1134
|
-
mockStorageEngine,
|
|
1135
|
-
mockChainTracker
|
|
1136
|
-
)
|
|
1137
|
-
engine.getUTXOHistory = jest.fn(async () => {
|
|
1138
|
-
return mockOutput
|
|
1139
|
-
})
|
|
1140
|
-
|
|
1141
|
-
// Perform a lookup request
|
|
1142
|
-
const results = await engine.lookup({
|
|
1143
|
-
service: 'Hello',
|
|
1144
|
-
query: { name: 'Bob' }
|
|
1145
|
-
})
|
|
1146
|
-
expect(results).toEqual({
|
|
1147
|
-
outputs: [{
|
|
1148
|
-
beef: mockOutput.beef,
|
|
1149
|
-
outputIndex: mockOutput.outputIndex
|
|
1150
|
-
}],
|
|
1151
|
-
type: 'output-list'
|
|
1152
|
-
})
|
|
1153
|
-
})
|
|
1154
|
-
})
|
|
1155
|
-
describe('getUTXOHistory', () => {
|
|
1156
|
-
it('Returns the given output if there is no history selector', async () => {
|
|
1157
|
-
// Already tested above
|
|
1158
|
-
return true
|
|
1159
|
-
})
|
|
1160
|
-
it('Invokes the history selector function with the correct data', async () => {
|
|
1161
|
-
const mockedHistorySelector = jest.fn(async (beef, outputIndex, currentDepth) => {
|
|
1162
|
-
if (currentDepth !== 2) {
|
|
1163
|
-
return true
|
|
1164
|
-
}
|
|
1165
|
-
return false
|
|
1166
|
-
})
|
|
1167
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1168
|
-
txid: 'mockTXID',
|
|
1169
|
-
outputIndex: 0,
|
|
1170
|
-
history: mockedHistorySelector
|
|
1171
|
-
}])
|
|
1172
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1173
|
-
const engine = new Engine(
|
|
1174
|
-
{
|
|
1175
|
-
Hello: mockTopicManager
|
|
1176
|
-
},
|
|
1177
|
-
{
|
|
1178
|
-
Hello: mockLookupService
|
|
1179
|
-
},
|
|
1180
|
-
mockStorageEngine,
|
|
1181
|
-
mockChainTracker
|
|
1182
|
-
)
|
|
1183
|
-
|
|
1184
|
-
// Perform a lookup request
|
|
1185
|
-
await engine.lookup({
|
|
1186
|
-
service: 'Hello',
|
|
1187
|
-
query: { name: 'Bob' }
|
|
1188
|
-
})
|
|
1189
|
-
expect(mockedHistorySelector).toHaveBeenCalledWith(
|
|
1190
|
-
mockOutput.beef,
|
|
1191
|
-
mockOutput.outputIndex,
|
|
1192
|
-
0
|
|
1193
|
-
)
|
|
1194
|
-
expect(mockedHistorySelector).toHaveBeenCalled()
|
|
1195
|
-
})
|
|
1196
|
-
it('Returns undefined if history should not be traversed', async () => {
|
|
1197
|
-
const mockedHistorySelector = jest.fn(async (beef, outputIndex, currentDepth) => {
|
|
1198
|
-
return false
|
|
1199
|
-
})
|
|
1200
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1201
|
-
txid: 'mockTXID',
|
|
1202
|
-
outputIndex: 0,
|
|
1203
|
-
history: mockedHistorySelector
|
|
1204
|
-
}])
|
|
1205
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1206
|
-
const engine = new Engine(
|
|
1207
|
-
{
|
|
1208
|
-
Hello: mockTopicManager
|
|
1209
|
-
},
|
|
1210
|
-
{
|
|
1211
|
-
Hello: mockLookupService
|
|
1212
|
-
},
|
|
1213
|
-
mockStorageEngine,
|
|
1214
|
-
mockChainTracker
|
|
1215
|
-
)
|
|
1216
|
-
|
|
1217
|
-
// Perform a lookup request
|
|
1218
|
-
const results = await engine.lookup({
|
|
1219
|
-
service: 'Hello',
|
|
1220
|
-
query: { name: 'Bob' }
|
|
1221
|
-
})
|
|
1222
|
-
expect(mockedHistorySelector).toHaveBeenCalledWith(
|
|
1223
|
-
mockOutput.beef,
|
|
1224
|
-
mockOutput.outputIndex,
|
|
1225
|
-
0
|
|
1226
|
-
)
|
|
1227
|
-
expect(results).toEqual({
|
|
1228
|
-
outputs: [],
|
|
1229
|
-
type: 'output-list'
|
|
1230
|
-
})
|
|
1231
|
-
})
|
|
1232
|
-
it('Returns undefined if the history selector is a number, and less than the current depth', async () => {
|
|
1233
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1234
|
-
txid: 'mockTXID',
|
|
1235
|
-
outputIndex: 0,
|
|
1236
|
-
history: -1
|
|
1237
|
-
}])
|
|
1238
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1239
|
-
const engine = new Engine(
|
|
1240
|
-
{
|
|
1241
|
-
Hello: mockTopicManager
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
Hello: mockLookupService
|
|
1245
|
-
},
|
|
1246
|
-
mockStorageEngine,
|
|
1247
|
-
mockChainTracker
|
|
1248
|
-
)
|
|
1249
|
-
|
|
1250
|
-
// Perform a lookup request
|
|
1251
|
-
const results = await engine.lookup({
|
|
1252
|
-
service: 'Hello',
|
|
1253
|
-
query: { name: 'Bob' }
|
|
1254
|
-
})
|
|
1255
|
-
expect(results).toEqual({
|
|
1256
|
-
outputs: [],
|
|
1257
|
-
type: 'output-list'
|
|
1258
|
-
})
|
|
1259
|
-
})
|
|
1260
|
-
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 () => {
|
|
1261
|
-
mockLookupService.lookup = jest.fn(async () => [{
|
|
1262
|
-
txid: 'mockTXID',
|
|
1263
|
-
outputIndex: 0,
|
|
1264
|
-
history: 1
|
|
1265
|
-
}])
|
|
1266
|
-
mockStorageEngine.findOutput = jest.fn(async () => mockOutput)
|
|
1267
|
-
const engine = new Engine(
|
|
1268
|
-
{
|
|
1269
|
-
Hello: mockTopicManager
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
Hello: mockLookupService
|
|
1273
|
-
},
|
|
1274
|
-
mockStorageEngine,
|
|
1275
|
-
mockChainTracker
|
|
1276
|
-
)
|
|
1277
|
-
|
|
1278
|
-
// Perform a lookup request
|
|
1279
|
-
const results = await engine.lookup({
|
|
1280
|
-
service: 'Hello',
|
|
1281
|
-
query: { name: 'Bob' }
|
|
1282
|
-
})
|
|
1283
|
-
|
|
1284
|
-
expect(results).toEqual({
|
|
1285
|
-
outputs: [{
|
|
1286
|
-
beef: mockOutput.beef,
|
|
1287
|
-
outputIndex: mockOutput.outputIndex
|
|
1288
|
-
}],
|
|
1289
|
-
type: 'output-list'
|
|
1290
|
-
})
|
|
1291
|
-
})
|
|
1292
|
-
// it('Traversing history, calls findOutput with any output consumed by this UTXO', async () => {
|
|
1293
|
-
// mockLookupService.lookup = jest.fn(async () => [{
|
|
1294
|
-
// txid: 'mockTXID',
|
|
1295
|
-
// outputIndex: 0,
|
|
1296
|
-
// history: 1
|
|
1297
|
-
// }])
|
|
1298
|
-
// mockStorageEngine.findOutput = jest.fn(async () => ({
|
|
1299
|
-
// ...mockOutput,
|
|
1300
|
-
// outputsConsumed: [{
|
|
1301
|
-
// txid: examplePreviousTXID,
|
|
1302
|
-
// outputIndex: 0
|
|
1303
|
-
// }]
|
|
1304
|
-
// }))
|
|
1305
|
-
// const engine = new Engine(
|
|
1306
|
-
// {
|
|
1307
|
-
// Hello: mockTopicManager
|
|
1308
|
-
// },
|
|
1309
|
-
// {
|
|
1310
|
-
// Hello: mockLookupService
|
|
1311
|
-
// },
|
|
1312
|
-
// mockStorageEngine,
|
|
1313
|
-
// mockChainTracker
|
|
1314
|
-
// )
|
|
1315
|
-
|
|
1316
|
-
// // Perform a lookup request
|
|
1317
|
-
// await engine.lookup({
|
|
1318
|
-
// service: 'Hello',
|
|
1319
|
-
// query: { name: 'Bob' }
|
|
1320
|
-
// })
|
|
1321
|
-
|
|
1322
|
-
// expect(mockStorageEngine.findOutput).toHaveBeenCalledWith()
|
|
1323
|
-
// })
|
|
1324
|
-
// it('Returns the correct envelope based on the history traversal process', async () => {
|
|
1325
|
-
// // TODO: Come up with some test data for a simple case, but different than the above code.
|
|
1326
|
-
// })
|
|
1327
|
-
// it('Returns the correct envelope based on the history traversal process for a more complex multi-layer multi-output graph', async () => {
|
|
1328
|
-
// // TODO: Come up with some test data to test the history traversal process better
|
|
1329
|
-
// })
|
|
1330
|
-
})
|
|
1331
|
-
// describe('deleteUTXODeep', () => {
|
|
1332
|
-
// it('Finds UTXO by ID if no output was provided', async () => {
|
|
1333
|
-
|
|
1334
|
-
// })
|
|
1335
|
-
// it('Finds UTXO by TXID and VOUT if no ID was provided', async () => {
|
|
1336
|
-
|
|
1337
|
-
// })
|
|
1338
|
-
// it('Throws an error if no output can be found', async () => {
|
|
1339
|
-
|
|
1340
|
-
// })
|
|
1341
|
-
// describe('When no more UTXOs are consumed by this UTXO', () => {
|
|
1342
|
-
// it('Deletes the UTXO by ID from the storage engine', async () => {
|
|
1343
|
-
|
|
1344
|
-
// })
|
|
1345
|
-
// it('Notifies all lookup services about the deletion of the UTXO', async () => {
|
|
1346
|
-
|
|
1347
|
-
// })
|
|
1348
|
-
// })
|
|
1349
|
-
// it('For each UTXO that this UTXO consumes, finds the consumed UTXO by its ID and removes reference to this one', async () => {
|
|
1350
|
-
|
|
1351
|
-
// })
|
|
1352
|
-
})
|
|
1353
|
-
})
|
|
1354
|
-
|
|
1355
|
-
// beef27c8f_1 is 1 level deep, i.e. proofs on inputs
|
|
1356
|
-
const beef27c8f1 = '0100beef01feafcf0c000802020044ef36fc6c79af360b13f2164c6e772e6e5e2083a54d72894f34710fe6a68cf80302792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d010000059e83aae2cfc6a35477566439dd9d58969cc41a134864c671c9b844f2a409a60101004991c129bba819832d6dd315c3a2065bd31b1a60aff8192be335f83731641e690101002f87724a7387df82417902266d2a4fb2fee857451e963a352ce8acbb279aa9eb010100a9e9eb55d20cef840bacbdb26ad8d6e7680ef7653364830b1e770fa140ece6bb010100f0ecd03fff2df21b0034d331cade50152781be9052dcec54eb95c963ef1cdcbf0101006fdb2c01a266e761a32f53b3ad0fabbca31ae234d31d85d9af9bbd624b321c9b0101008108cad77aabf7b0ee371f42afd773cada534244535f7108c7590df2b9d5f8a2020100000006e9847b33c5d666ccee1dccd52235489d8d58bf432be1b8c0cc7c13af80f10703030000006b483045022100fe8a18e043d693246a56c9b32ff553bea89a0bbe389bc55e8244044dcd865de3022065245c30fbbcc6820e4761048ed28c587bf41ece1933dfb85b8fa8c3433d93504121022f2ac22e73a55aa4ff21e905f66959200642ae74716544f834a48af5aa53fb27ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45050000006b483045022100e6332a6459350ff2486a39b6d79cdc0b65b6d52cf898fc457e45880c0db01b2a02206e34126bc81e6f03c6de90278788d6b2be3a3f5d7bd85e6442bbea90b8a5e2394121029780a1a98be5ff17a3228feb830e3bc32395ebecc5cf6de08f7a5685faf66c07ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45040000006b483045022100dfba71c6c33d2366b2202ea136271fd2f3a26c0a7295f6dd6018fbe12fc48708022052387ca532247bbfffef4b076fd2fbbdebe85b3cf53c73004897cd07be5d1ad6412103f735eb69c9f0b3c90dd2798b709e12a5b3d176a4690bd2a31ec616af9ecd47b5ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45020000006b483045022100f941b74c5da1e7f0ce4d1c08745e4d6b8ef56656199505caeff4b74f54890cec02201eaa0d01112497c89ebe4818687c5f3305629a83abd3b0bf5135feee68f57ca0412103fb0ec51f9e6e45b213cb1deff921e3322624cfb6d4a4ba5dc699867cf163b736ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45030000006a47304402205a3a2848350fdc19cae73243bea16dfc1e8fbbd5cecde4b5c88a57790505cebb022043db51d04998d52ec394d0e945a76e9922dd63caffb882242bec2ced2a35c7f1412102bae3e2f47dceaed7db2a212e059a00674a0087e3395b92d9f12cdbefdbc84efaffffffffad49b6f41125ffadab03cb2dcc24caf2c897c2cd4aed6e0a3b5c003f2dde757a030000006b483045022100aa7aaec7fc6c5a2f4d5318b7fcbbca0f223c5c198f8fc0e1c8d59aee7155af1202207a4960fb58aa86eba12852f84fa3cdb4c427c7374fe737b3d5bcd9e2a6d76e3b412102b55cfd872e85b3dd424dcceafe01ed5a87dfb599d354ab7fc50d9ec48af77f6effffffff1a400d0300000000001976a914f182138abcb69c7c2339040605aa0a442cfec44288acc8000000000000001976a914e9d2276f468223a6d650672ab9d4c1271fb157e288acacb00000000000001976a914c0ea0208a097df8146933ae15f3ce545d5451e7388ac7a140000000000001976a9142d9ae3291f9393639fe2c38c9a7a948d136a10b488acea070000000000001976a91435d1c396b7ccb467118a41c2d77ebf79fb5fb7eb88ac17050000000000001976a9140dac1e2ddc975ee6d9f393e2ccd4876edb386cdd88ac51060000000000001976a91422878cb2f57a1ebe513815562003128f6e99eda188ac67040000000000001976a91439a752923ba1ef8b126ba7886646d58f54521c6588ac1e040000000000001976a914a939565af52d3fa974a040f42c35365d76b98f6a88acec030000000000001976a914c90823a53e29cafd35a225cc14e3a44eda143a5788ac05040000000000001976a914af6f8d6aaec3bf123efc17c8977bfad1ec1c859388acec030000000000001976a914d4c5e66a062e634be1c6f0eb1f2af0acebecb47988acf1030000000000001976a914a94a3e84bd740095ab39bed110cd3b3922c8595088ace8030000000000001976a914aa2e3c3bf6f3ee79f5af3b94eab2be60a394ce4c88aceb030000000000001976a91459394fa083ff3726571c79624dba3b38f14d924c88ace8030000000000001976a914726eb3ae6211711aa1624f3d767035b0b44abdc488ace8030000000000001976a9145f4680a099a6dc6891af3094aaab96aa66a6259888ace8030000000000001976a914f1a4fbd99e3923b0934abdb8839c337eb2fd50b888ace8030000000000001976a9147ed0ad307f5fe9993f984e7525c0012e3569594588ace8030000000000001976a91407f386bcdc4892cc2d0be3226b721ceaf04750b288ace8030000000000001976a914b51edaaa3c92d0af94109d952c7ddf13aa345f9988ace8030000000000001976a914018f4e02bd74f17a75f5b3965db4633ab514c57588ace8030000000000001976a914cc8d1f7ba5406b81efbc23da0fcccb4d3fe5b40a88ace8030000000000001976a914db1d4b7bd3e62f535dc3f2c8d0a989b000fa736d88ace8030000000000001976a914b8ea69358da7bea9506b15db6fc6597b3849b0a188ace8030000000000001976a914ecd769fdd67e04786e871c50c1a10504861bd79488ac0000000001000100000001792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d070000006b48304502210090368b5925795abb0415a3cd6265d7abae7d3541fb7f2fdb1fdffdd916b3fce3022068136da44df8ee367807c7c59c950ea8d428adfe142268cd6371cf4c1b4c3208412102858ea1804708d1e16e77b49ba1559b3742797f5f43efd3922fa3d21557521d7cffffffff03f401000000000000fde10121026afd108ddbc05e093207ebdf06557a3d66e15b96accbf1766c2e7d6efa3d1981ac22313852713463583334356b6a7461734467764b41455a435135564c5a72516f76786d2081d846bcae65ce281829b6d81eff3862a68b8eaf4e99ad97c0c4ad2e149631c020f936ace2d2b4034710dec819e343f69ddbb1a16d721b88413b697c2475f75f504c4e8470062e6966cb7ec80864e5e1217ecf2b3f113bb5307e1ec1d338678e54b426a11a267e990f7dcd85072c7b3870fabb54812d7f8a7aaa672ce01e4f8a2c6a1d532849877329bd23be0b3383fcb437ffd1ff05aad824343bfe5bae52d5775ed99ba9d5a72038dd50f81153e674a8b1c5d659aac361e660cc5657b291eda149de2505efb0bbac0a353331393633353238334c797f3040f01934bb25733d4700a0309194a7defd8ceea7a8a59ad279c6237e2917264a115773777c5746546288b15ec1cf6788b801dd3952fec804cadbba2ff4e05464735e2db83c3509b59119efc843716c34becd2884f011f2e7f61f382237891875115b6ea5bac1c3e9ca6c575b44547d589c38c1b5a8cadb463044022016e1fdb226b465f3151aaa1e033b122dd58f3b495d2846d532f784ec8f6fc9ae022031dc12155c02e7f588c9f6d975a79c9c26cf341c1c88578cd6a87a3c01f6a73d6d6d6d6dc8000000000000001976a914010b7bfa8ff585b6d95f8a18a998cbd87508bd4188aca9010000000000001976a914254c6f1ce67d27ae4dafb03d2ba08318df2883c588ac0000000000'
|
|
1357
|
-
// beef27c8f_0 is 0 levels deep, i.e. tx has merklePath
|
|
1358
|
-
const beef27c8f0 = '0100beef01fef4f10c000902fd020100ab2d9b3bbfc2ecf5c834f7719bf10dcabef31cfa3b78fa714bd3a2b3958fc6b5fd0301029515629d935d81e704ca97b8dc02a698c39d78f06bbb3d8d46bcaa5178f3c827018000fc94b1da08b5d0850afcc4948a9e129a2c2c0bb629090ca84fef5d326ceadbc2014100fa78356192a22189dd3c2a03bfb2e043667a55a85386a8d0f0853bbaa54ba748012100e37888006acc37cbfc6a459117c3e957b00e8ff3a90d93bc9aaa3fc9d972453701110052564e444cbe4b1f24015d91f5cfd7eff63e5f403613a7b48e77efa32761f950010900acef65445ef232f1fcb527bab67fccc90ae9d0c610a4f752d84c5e10a3415d74010500e1fadd176551150808ae96ff2b4dff487c432788d23db25fd3fb5e8b3e16b96f0103003e530f146a992ed9378807fa43bc1e6033de0711d728ee3a0ea1507b5ffff2940100006c8eca8a9d680ab2ae4dc1f0e247f282de3f32b99175e6f9ddb4f8e76ae0bf1b010100000001792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d070000006b48304502210090368b5925795abb0415a3cd6265d7abae7d3541fb7f2fdb1fdffdd916b3fce3022068136da44df8ee367807c7c59c950ea8d428adfe142268cd6371cf4c1b4c3208412102858ea1804708d1e16e77b49ba1559b3742797f5f43efd3922fa3d21557521d7cffffffff03f401000000000000fde10121026afd108ddbc05e093207ebdf06557a3d66e15b96accbf1766c2e7d6efa3d1981ac22313852713463583334356b6a7461734467764b41455a435135564c5a72516f76786d2081d846bcae65ce281829b6d81eff3862a68b8eaf4e99ad97c0c4ad2e149631c020f936ace2d2b4034710dec819e343f69ddbb1a16d721b88413b697c2475f75f504c4e8470062e6966cb7ec80864e5e1217ecf2b3f113bb5307e1ec1d338678e54b426a11a267e990f7dcd85072c7b3870fabb54812d7f8a7aaa672ce01e4f8a2c6a1d532849877329bd23be0b3383fcb437ffd1ff05aad824343bfe5bae52d5775ed99ba9d5a72038dd50f81153e674a8b1c5d659aac361e660cc5657b291eda149de2505efb0bbac0a353331393633353238334c797f3040f01934bb25733d4700a0309194a7defd8ceea7a8a59ad279c6237e2917264a115773777c5746546288b15ec1cf6788b801dd3952fec804cadbba2ff4e05464735e2db83c3509b59119efc843716c34becd2884f011f2e7f61f382237891875115b6ea5bac1c3e9ca6c575b44547d589c38c1b5a8cadb463044022016e1fdb226b465f3151aaa1e033b122dd58f3b495d2846d532f784ec8f6fc9ae022031dc12155c02e7f588c9f6d975a79c9c26cf341c1c88578cd6a87a3c01f6a73d6d6d6d6dc8000000000000001976a914010b7bfa8ff585b6d95f8a18a998cbd87508bd4188aca9010000000000001976a914254c6f1ce67d27ae4dafb03d2ba08318df2883c588ac000000000100'
|
|
1359
|
-
// full txid
|
|
1360
|
-
const txid27c8f = '27c8f37851aabc468d3dbb6bf0789dc398a602dcb897ca04e7815d939d621595'
|
|
1361
|
-
|
|
1362
|
-
/*
|
|
1363
|
-
txid 17d1829ba8424b97369ee8b528ee8b65d9d4b9c08d037d224a7be7c025f78f78 output 0 1196 sats
|
|
1364
|
-
txid 9426201003b01e5bb66e9240a1cc337174238e816a25a06ba532fa67af4457d7 output 0 1197 sats
|
|
1365
|
-
txid 509f5ef79c18504fdfe21e67608f56bacb8d8a200634e46f60a9dc8430dcd6e9 output 0 1198 sats
|
|
1366
|
-
txid 877734db8eb917d0e2174a4bdddc085b34f67cedd6b94628c4feb1b979a2b2c5 output 0 1199 sats
|
|
1367
|
-
txid 37abad7168d47d4e107d0f9f96813a4feef6ea346482fce554c3fade85d45409 output 0 1200 sats
|
|
1368
|
-
txid d786db393ec7f1315bee2cbd3aa47634394f57c861feed8c075563308f14c237 output 0 1568 sats
|
|
1369
|
-
txid a7d4cac391f5b3d8dedaacb3cd8b5afac54df2d8e043ae0452abcfe190686494 output 17 1132 sats
|
|
1370
|
-
*/
|
|
1371
|
-
const txid17d182 = '17d1829ba8424b97369ee8b528ee8b65d9d4b9c08d037d224a7be7c025f78f78'
|
|
1372
|
-
const beef17d1820 = '0100beef01feaff60c000c02fd660302788ff725c0e77b4a227d038dc0b9d4d9658bee28b5e89e36974b42a89b82d117fd670300fe2b9c31ae10a15803f91f63df0cf6a5b6771db9dd831e5bc37a36460a1ffa0701fdb201009bf0e3a5854c4b4c8d62c0ade2754ae1bbc0f16a3e49251ed964486b8dc387f101d80003ae2ec810744139e9989e4f36d8f5ebb160981481e68647890d11e3dda971fa016d0081efcddc14858ead1f427f235d2b21c8de43e33bfeb155ed79f666dd4dd490c2013700a6a847f6e264cfa64c0b6aad6b74912c63c6fc89728be813d104abd2724b98a5011a0062c7d393b9518b05d2d0891ae8c2cd840c93e051d2944bb8e651c976301c4e50010c008d8ee79c1a042cf43f4dcd880aeb4ff7f54552cd3a4c9a40c72cc16ed6847cee01070050194d11d12eddf5e56926b28778f0f64ad54e79eb4df4ce7834ddfc57bf882d010200bd66a0cdbb0af933166931b1050dd108e02d76cff19afd614e681fcbdf1be0740100008b36364159630b47b4c27f6e1e056e820b0aa350ab5dde2520fc1753129b628c010100cab620ac48bb55de40c8c5ddb62bbc44c907fbf5d882326d427f4cd4b34334800101001163b76c2c6824c07a53af2ea86da1b1d946340d9377d3b9b84560e074aea3a6010100000001d75744af67fa32a56ba0256a818e23747133cca140926eb65b1eb00310202694000000006a473044022027137acdeb7583e966a9e8825c9f5edb909b2fd46d40e6ee83846d164529630e0220644e82a46d813fac2836650ce832cdba7eb46b6c134982b36e84f5f08a921dbb412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ac040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
|
|
1373
|
-
const beef17d1824 = '0100beef01fedcee0c000d02f8020954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37f900fa225908f222f8f7f291731a10e93cdf410189ca01adc688edf5577552aa8ff2017d006cfea8ad6ace7ecce9bbbe729eb9dca25f9be064b150d290588c3364867ee112013f00bfbacc8bfdda7f734c160fcfee972dd9182aede73290367aab62b5ce242e5655011e007a0eb94f2bcf98489247a01551a843d9cf1e091522c32f50ce347ba5057b439d010e00beb857ba543d2bd199e68c63a480c73b83660eca27d2ce105355741f27017876010600d5fb1ea8440e41b4ffde093dd53d6ed741c4fe4fe66ff43f6a4eb9af3c6ddebb010200169db3be130a9e8aff1dbff39abee477d3a224cb3e118c39794bb7ba427b707501000083cd0ce2d2734835c45331cd06f38556cf4e07634d599f59692ad9980175a7f201010022f4a629e99ca5838f56f87b050bdefa9b9f621c08745cb27b941d9c6750d99a01010032b90a4148ff984b0efee309a578cb87d278142c014f1a7fd5351b3e3cd4c7da01010082dc3ea570250357fca88652ee1628928136c29eb4910b7e5627e0ffef1d958a010100f5194f41c28960f0dba669f4715229f59f59afb5d55f50a2a7556111b8da67130101009cd7f9a65014cc6fd5c0b71391d0b70eb983ea3b03a2fbd7a13150775855953405010000000237c2148f306355078cedfe61c8574f393476a43abd2cee5b31f1c73e39db86d7000000006a47304402207ee089c64fa810ad88e03f7bad4ae57c9b7de215d222a5bf52f9259d171b717a0220643d3cb4423b5b7ffcb7b2b59eb81dede0a25319aaa0228d27e59d5e238c35a2412102641b5a033ce332a9fc0f81dee7948c099b194758d8352bd5572988ac760ae2faffffffff94646890e1cfab5204ae43e0d8f24dc5fa5a8bcdb3acdaded8b3f591c3cad4a7110000006a47304402205b92eaba4a6c029385e9cf291631b98d0c7aa6851755fb44279e2b1ebd12759702206e96f3bac252f2c5cf5edf7f8828d6358baaa9fe34aa163faeb9b31aac830e47412102577e2a3877a253d9e7587c3d77a4038b4af5e51113393c2713c6d50c9da5de6effffffff03b0040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88acc8000000000000001976a914292e61672fc24a9a70cfa6f73a9af5655652629d88ac13050000000000001976a914ad2ab55d09559074e41e2a2c1c89742e13bee03088ac00000000010001000000010954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37000000006b4830450221009c67deb38aa6b5ed8bfa140e9464e1b1ee2908a296bb70e3d164725254491c6a02207b595dcced1136c276edfc1e5e5901279cf6f0a6432e4c1a908468262e7d9543412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001af040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac00000000000100000001c5b2a279b9b1fec42846b9d6ed7cf6345b08dcdd4b4a17e2d017b98edb347787000000006b483045022100c0992a684be99a07bc87e03bf88701acefc033c1aa57cf4a3b15253e64c8962a022031d5b3af1684a1a1a201eb16fc1fbca8522ae16b9c0d340e8bec48eeea1fe458412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ae040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac00000000000100000001e9d6dc3084dca9606fe43406208a8dcbba568f60671ee2df4f50189cf75e9f50000000006b483045022100c9c8baa777fe09c1310788614d4eba88b6027d39eada18a5a79c6a33e55919c302205222961b1f63dddd425cd418571797cb3a68aa59ac37331c0fc61fc9de66c34d412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ad040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac00000000000100000001d75744af67fa32a56ba0256a818e23747133cca140926eb65b1eb00310202694000000006a473044022027137acdeb7583e966a9e8825c9f5edb909b2fd46d40e6ee83846d164529630e0220644e82a46d813fac2836650ce832cdba7eb46b6c134982b36e84f5f08a921dbb412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ac040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac0000000000'
|
|
1374
|
-
const txid942620 = '9426201003b01e5bb66e9240a1cc337174238e816a25a06ba532fa67af4457d7'
|
|
1375
|
-
const beef9426200 = '0100beef01fe07ef0c0008022802d75744af67fa32a56ba0256a818e23747133cca140926eb65b1eb00310202694290089598873694014f9706b6074a1b349fcf59df875e58a0cf3a8900c6e5830f33601150036fffb4b882171c594a27d37c5d7827cc706811c3e0acd152e0f3ee1c7ac98f9010b002b3b5c611cbea3bbdf9e99646cb23e80b62fc2dc2e619656157296878d46d8b30104002877b6b97c95fd55c0584d0ccf683c9ad6c60be146dd73605b93ef042da648a0010300d1d01ac14f19d974f8131b9cdb38ae2abcf5403d5c3f0d2772a1c995c136356b01000017ece09932fa7c6d464c633df200003c2328beb26290c7b9b1333eb84c90c544010100cd4147f92f226bc39354a78209db1f521ffd8720e3fcc9c942ee1dd535513d0f0101005daeb3f84528a58700c58c8479f29cd627b07c198602b6c08033340262c0c607010100000001e9d6dc3084dca9606fe43406208a8dcbba568f60671ee2df4f50189cf75e9f50000000006b483045022100c9c8baa777fe09c1310788614d4eba88b6027d39eada18a5a79c6a33e55919c302205222961b1f63dddd425cd418571797cb3a68aa59ac37331c0fc61fc9de66c34d412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ad040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
|
|
1376
|
-
const txid509f5e = '509f5ef79c18504fdfe21e67608f56bacb8d8a200634e46f60a9dc8430dcd6e9'
|
|
1377
|
-
const beef509f5e0 = '0100beef01fe05ef0c0009029800fb09f12f5632f7af6409432fc914d1e68fc499270fa29f3b3c43fb00ebeb8ed09902e9d6dc3084dca9606fe43406208a8dcbba568f60671ee2df4f50189cf75e9f50014d00ccb4f6ea2fabdfaf86b0e3837cc702ddde19cb5112e680f00832330b03be1d290127000480365544cd4dca6262cc25567f241d650339773f3f9ef1b9a25b9a034945c30112004ab6bdfecf74af1da1e0574671d06aef63dcde80133d1a6c46beda2ad08abc15010800ce75abf88ee3de3e2f89be0462e1cb0b9470b67947b9880e941bb6565a44bc86010500940d69edc69fc8694485452b961b8df8a2e0c1ff7bdbfeee1e6855542a7e7cd10103002217abcbd7e5431fffc0f52624ee3cf659e39734ae5335d5d196193f7f6e39ab01000082d3a59ca910a1c6fc1f06ab2a5ea80ffee7a39d9a987c85701cfaaf604219f80101008bed85ed818e9d69147000e8687b2efed77e5ee0073e5a2640033715747bd0c2010100000001c5b2a279b9b1fec42846b9d6ed7cf6345b08dcdd4b4a17e2d017b98edb347787000000006b483045022100c0992a684be99a07bc87e03bf88701acefc033c1aa57cf4a3b15253e64c8962a022031d5b3af1684a1a1a201eb16fc1fbca8522ae16b9c0d340e8bec48eeea1fe458412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001ae040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
|
|
1378
|
-
const txid877734 = '877734db8eb917d0e2174a4bdddc085b34f67cedd6b94628c4feb1b979a2b2c5'
|
|
1379
|
-
const beef8777340 = '0100beef01fedfee0c000e02fd202900eeebd665d8893249c90dabbd50266fb7e0f125dd8ee087a3c8089debf09499d4fd212902c5b2a279b9b1fec42846b9d6ed7cf6345b08dcdd4b4a17e2d017b98edb34778701fd911400b32e903076f6869d4e0a9fa2d00f5c8bad598d3ede5b4bafef88debdb1d3de6901fd490a000a79573c8eb909d30c4e9653f90ea2869e44220d551da81cc96913b2fcd734aa01fd2505003d6db6b39d889be1535a478da0c3428e8107872ac55f0fcfdd2225b6a402d1dc01fd9302008ae31e7e4d4e766814ea11b337b5b51f5d807737f802c44c8d0d75a583a61bde01fd480100e962a171fcc0e79bf55b1433d79564f8d3b2622ecbcb84c9ede792d353e0afb901a5007d0a8d8a3a2f0eb209e82f5e80ef6c4835b7d07c85d6ac8c613cc8754a47bd79015300b070576d92310f1e08a12581ecea4d1d4de48beb15ff7e42eddc1b957fa2909d012800544a58cac6b4dd78cebd84178dc1572693552fec5777a055dad0068bd67aec6101150025674a887fff026640fe9851cbffb37bd8e02780c4f6c0476ed3cee4e4884c2f010b0023aa2b01e8a59c3b79976918d95ae6a044ace2264e3690f9846f452499c0cbad01040078033c2619a3b115bfc71d09addf13e288660be1841cf6769ffb9924aca246970103004d2d6ae3e2c02703b773c49af789b7372b5b71bf92b49e2dc4b921880df984540100003b86537a85e5047596a482ac2dba30d1798b26d200c08a2fb19d8d75b2c2a1b50101000000010954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37000000006b4830450221009c67deb38aa6b5ed8bfa140e9464e1b1ee2908a296bb70e3d164725254491c6a02207b595dcced1136c276edfc1e5e5901279cf6f0a6432e4c1a908468262e7d9543412102b963cd9b5a1419c041a4c4ac46f5205a2b8cb691cd5633e5d498ddcf3d37460b0000000001af040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88ac000000000100'
|
|
1380
|
-
const txid37abad = '37abad7168d47d4e107d0f9f96813a4feef6ea346482fce554c3fade85d45409'
|
|
1381
|
-
const beef37abad0 = '0100beef01fedcee0c000d02f8020954d485defac354e5fc826434eaf6ee4f3a81969f0f7d104e7dd46871adab37f900fa225908f222f8f7f291731a10e93cdf410189ca01adc688edf5577552aa8ff2017d006cfea8ad6ace7ecce9bbbe729eb9dca25f9be064b150d290588c3364867ee112013f00bfbacc8bfdda7f734c160fcfee972dd9182aede73290367aab62b5ce242e5655011e007a0eb94f2bcf98489247a01551a843d9cf1e091522c32f50ce347ba5057b439d010e00beb857ba543d2bd199e68c63a480c73b83660eca27d2ce105355741f27017876010600d5fb1ea8440e41b4ffde093dd53d6ed741c4fe4fe66ff43f6a4eb9af3c6ddebb010200169db3be130a9e8aff1dbff39abee477d3a224cb3e118c39794bb7ba427b707501000083cd0ce2d2734835c45331cd06f38556cf4e07634d599f59692ad9980175a7f201010022f4a629e99ca5838f56f87b050bdefa9b9f621c08745cb27b941d9c6750d99a01010032b90a4148ff984b0efee309a578cb87d278142c014f1a7fd5351b3e3cd4c7da01010082dc3ea570250357fca88652ee1628928136c29eb4910b7e5627e0ffef1d958a010100f5194f41c28960f0dba669f4715229f59f59afb5d55f50a2a7556111b8da67130101009cd7f9a65014cc6fd5c0b71391d0b70eb983ea3b03a2fbd7a13150775855953401010000000237c2148f306355078cedfe61c8574f393476a43abd2cee5b31f1c73e39db86d7000000006a47304402207ee089c64fa810ad88e03f7bad4ae57c9b7de215d222a5bf52f9259d171b717a0220643d3cb4423b5b7ffcb7b2b59eb81dede0a25319aaa0228d27e59d5e238c35a2412102641b5a033ce332a9fc0f81dee7948c099b194758d8352bd5572988ac760ae2faffffffff94646890e1cfab5204ae43e0d8f24dc5fa5a8bcdb3acdaded8b3f591c3cad4a7110000006a47304402205b92eaba4a6c029385e9cf291631b98d0c7aa6851755fb44279e2b1ebd12759702206e96f3bac252f2c5cf5edf7f8828d6358baaa9fe34aa163faeb9b31aac830e47412102577e2a3877a253d9e7587c3d77a4038b4af5e51113393c2713c6d50c9da5de6effffffff03b0040000000000001976a914ae0545a670c6f739f9a8963aee37be95df0d9b2a88acc8000000000000001976a914292e61672fc24a9a70cfa6f73a9af5655652629d88ac13050000000000001976a914ad2ab55d09559074e41e2a2c1c89742e13bee03088ac000000000100'
|