@bsv/overlay 0.1.0-alpha.3 → 0.1.0-alpha.5
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/dist/cjs/package.json +2 -2
- package/dist/cjs/src/Engine.js +144 -2
- package/dist/cjs/src/Engine.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/Engine.js +144 -2
- package/dist/esm/src/Engine.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/Advertiser.d.ts +3 -3
- package/dist/types/src/Engine.d.ts +26 -1
- package/dist/types/src/Engine.d.ts.map +1 -1
- package/dist/types/src/SHIPAdvertisement.d.ts +4 -4
- package/dist/types/src/SHIPAdvertisement.d.ts.map +1 -1
- package/dist/types/src/SLAPAdvertisement.d.ts +4 -4
- package/dist/types/src/SLAPAdvertisement.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/Advertiser.ts +52 -0
- package/src/Engine.ts +163 -2
- package/src/SHIPAdvertisement.ts +8 -0
- package/src/SLAPAdvertisement.ts +8 -0
- package/src/__tests/Engine.test.ts +65 -0
package/src/Engine.ts
CHANGED
|
@@ -9,6 +9,9 @@ import { LookupQuestion } from './LookupQuestion.js'
|
|
|
9
9
|
import { LookupAnswer } from './LookupAnswer.js'
|
|
10
10
|
import { LookupFormula } from './LookupFormula.js'
|
|
11
11
|
import { Transaction, ChainTracker, MerklePath, Broadcaster } from '@bsv/sdk'
|
|
12
|
+
import { Advertiser } from './Advertiser.js'
|
|
13
|
+
import { SHIPAdvertisement } from './SHIPAdvertisement.js'
|
|
14
|
+
import { SLAPAdvertisement } from './SLAPAdvertisement.js'
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
17
|
* Am engine for running BSV Overlay Services (topic managers and lookup services).
|
|
@@ -20,14 +23,18 @@ export class Engine {
|
|
|
20
23
|
* @param {[key: string]: LookupService} lookupServices - manages UTXO lookups
|
|
21
24
|
* @param {Storage} storage - for interacting with internally-managed persistent data
|
|
22
25
|
* @param {ChainTracker} chainTracker - Verifies SPV data associated with transactions
|
|
26
|
+
* @param {string} hostingURL
|
|
23
27
|
* @param {Broadcaster} [Broadcaster] - broadcaster used for broadcasting the incoming transaction
|
|
28
|
+
* @param {Advertiser} [Advertiser] - handles SHIP and SLAP advertisements for peer-discovery
|
|
24
29
|
*/
|
|
25
30
|
constructor(
|
|
26
31
|
public managers: { [key: string]: TopicManager },
|
|
27
32
|
public lookupServices: { [key: string]: LookupService },
|
|
28
33
|
public storage: Storage,
|
|
29
34
|
public chainTracker: ChainTracker,
|
|
30
|
-
public
|
|
35
|
+
public hostingURL: string,
|
|
36
|
+
public broadcaster?: Broadcaster,
|
|
37
|
+
public advertiser?: Advertiser
|
|
31
38
|
) { }
|
|
32
39
|
|
|
33
40
|
/**
|
|
@@ -207,8 +214,87 @@ export class Engine {
|
|
|
207
214
|
if (Object.keys(steak).length > 0 && this.broadcaster !== undefined) {
|
|
208
215
|
await this.broadcaster.broadcast(tx)
|
|
209
216
|
}
|
|
217
|
+
|
|
218
|
+
// If we don't have an advertiser, just return the steak
|
|
219
|
+
if (this.advertiser === undefined) {
|
|
220
|
+
return steak
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Propagate transaction to other nodes according to synchronization agreements
|
|
224
|
+
// 1. Find nodes that host the topics associated with admissable outputs
|
|
225
|
+
// We want to figure out which topics we actually care about (because their associated outputs were admitted)
|
|
226
|
+
// AND if the topic was not admitted we want to remove it from the list of topics we care about.
|
|
227
|
+
const relevantTopics = taggedBEEF.topics.filter(topic =>
|
|
228
|
+
steak[topic] !== undefined && steak[topic].outputsToAdmit.length !== 0
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if (relevantTopics.length > 0) {
|
|
232
|
+
// Find all SHIP advertisements for the topics we care about
|
|
233
|
+
const domainToTopicsMap = new Map<string, Set<string>>()
|
|
234
|
+
for (const topic of relevantTopics) {
|
|
235
|
+
try {
|
|
236
|
+
// Handle custom lookup service answers
|
|
237
|
+
const lookupAnswer = await this.lookup({
|
|
238
|
+
service: 'ls_ship',
|
|
239
|
+
query: {
|
|
240
|
+
topic
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// Lookup will currently always return type output-list
|
|
245
|
+
if (lookupAnswer.type === 'output-list') {
|
|
246
|
+
const shipAdvertisements: SHIPAdvertisement[] = []
|
|
247
|
+
lookupAnswer.outputs.forEach(output => {
|
|
248
|
+
try {
|
|
249
|
+
// Parse out the advertisements using the provided parser
|
|
250
|
+
const tx = Transaction.fromBEEF(output.beef)
|
|
251
|
+
const advertisement = this.advertiser?.parseAdvertisement(tx.outputs[output.outputIndex].lockingScript)
|
|
252
|
+
if (advertisement !== undefined && advertisement !== null && advertisement.protocol === 'SHIP') {
|
|
253
|
+
shipAdvertisements.push(advertisement)
|
|
254
|
+
}
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('Failed to parse advertisement output:', error)
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
if (shipAdvertisements.length > 0) {
|
|
260
|
+
shipAdvertisements.forEach((advertisement: SHIPAdvertisement) => {
|
|
261
|
+
if (!domainToTopicsMap.has(advertisement.domain)) {
|
|
262
|
+
domainToTopicsMap.set(advertisement.domain, new Set<string>())
|
|
263
|
+
}
|
|
264
|
+
domainToTopicsMap.get(advertisement.domain)?.add(topic)
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(`Error looking up topic ${String(topic)}:`, error)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const broadcastPromises: Array<Promise<Response>> = []
|
|
274
|
+
|
|
275
|
+
// Note: We are depending on window.fetch, this may not be ideal for the long term.
|
|
276
|
+
for (const [domain, topics] of domainToTopicsMap.entries()) {
|
|
277
|
+
if (domain !== this.hostingURL) {
|
|
278
|
+
const promise = fetch(`${String(domain)}/submit`, {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
headers: {
|
|
281
|
+
'Content-Type': 'application/octet-stream',
|
|
282
|
+
'X-Topics': JSON.stringify(Array.from(topics))
|
|
283
|
+
},
|
|
284
|
+
body: new Uint8Array(taggedBEEF.beef)
|
|
285
|
+
})
|
|
286
|
+
broadcastPromises.push(promise)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
await Promise.all(broadcastPromises)
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Error during broadcasting:', error)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
210
297
|
return steak
|
|
211
|
-
// TODO propagate transaction to other nodes according to synchronization agreements
|
|
212
298
|
}
|
|
213
299
|
|
|
214
300
|
/**
|
|
@@ -255,6 +341,81 @@ export class Engine {
|
|
|
255
341
|
}
|
|
256
342
|
}
|
|
257
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Ensures alignment between the current SHIP/SLAP advertisements and the
|
|
346
|
+
* configured Topic Managers and Lookup Services in the engine.
|
|
347
|
+
*
|
|
348
|
+
* This method performs the following actions:
|
|
349
|
+
* 1. Retrieves the current configuration of topics and services.
|
|
350
|
+
* 2. Fetches the existing SHIP advertisements for each configured topic.
|
|
351
|
+
* 3. Fetches the existing SLAP advertisements for each configured service.
|
|
352
|
+
* 4. Compares the current configuration with the fetched advertisements to determine which advertisements
|
|
353
|
+
* need to be created or revoked.
|
|
354
|
+
* 5. Creates new SHIP/SLAP advertisements if they do not exist for the configured topics/services.
|
|
355
|
+
* 6. Revokes existing SHIP/SLAP advertisements if they are no longer required based on the current configuration.
|
|
356
|
+
*
|
|
357
|
+
* The function uses the `Advertiser` methods to create or revoke advertisements and ensures the updates are
|
|
358
|
+
* submitted to the SHIP/SLAP overlay networks using the engine's `submit()` method.
|
|
359
|
+
*
|
|
360
|
+
* @throws Will throw an error if there are issues during the advertisement synchronization process.
|
|
361
|
+
* @returns {Promise<void>} A promise that resolves when the synchronization process is complete.
|
|
362
|
+
*/
|
|
363
|
+
async syncAdvertisements(): Promise<void> {
|
|
364
|
+
if (this.advertiser === undefined) {
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
// Step 1: Retrieve Current Configuration
|
|
368
|
+
const configuredTopics = Object.keys(this.managers)
|
|
369
|
+
const configuredServices = Object.keys(this.lookupServices)
|
|
370
|
+
|
|
371
|
+
// Step 2: Fetch Existing Advertisements for each topic and service
|
|
372
|
+
const currentSHIPAdvertisements: SHIPAdvertisement[] = []
|
|
373
|
+
for (const topic of configuredTopics) {
|
|
374
|
+
const ads = await this.advertiser.findAllSHIPAdvertisements(topic)
|
|
375
|
+
currentSHIPAdvertisements.push(...ads)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const currentSLAPAdvertisements: SLAPAdvertisement[] = []
|
|
379
|
+
for (const service of configuredServices) {
|
|
380
|
+
const ads = await this.advertiser.findAllSLAPAdvertisements(service)
|
|
381
|
+
currentSLAPAdvertisements.push(...ads)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Step 3: Compare and Determine Actions
|
|
385
|
+
const requiredSHIPAdvertisements = new Set(configuredTopics)
|
|
386
|
+
const requiredSLAPAdvertisements = new Set(configuredServices)
|
|
387
|
+
|
|
388
|
+
const existingSHIPTopics = new Set(currentSHIPAdvertisements.map(ad => ad.topic))
|
|
389
|
+
const existingSLAPServices = new Set(currentSLAPAdvertisements.map(ad => ad.service))
|
|
390
|
+
|
|
391
|
+
const shipToCreate = Array.from(requiredSHIPAdvertisements).filter(topic => !existingSHIPTopics.has(topic))
|
|
392
|
+
const shipToRevoke = currentSHIPAdvertisements.filter(ad => !requiredSHIPAdvertisements.has(ad.topic))
|
|
393
|
+
|
|
394
|
+
const slapToCreate = Array.from(requiredSLAPAdvertisements).filter(service => !existingSLAPServices.has(service))
|
|
395
|
+
const slapToRevoke = currentSLAPAdvertisements.filter(ad => !requiredSLAPAdvertisements.has(ad.service))
|
|
396
|
+
|
|
397
|
+
// Step 4: Update Advertisements
|
|
398
|
+
for (const topic of shipToCreate) {
|
|
399
|
+
const taggedBEEF = await this.advertiser.createSHIPAdvertisement(topic)
|
|
400
|
+
await this.submit(taggedBEEF)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
for (const service of slapToCreate) {
|
|
404
|
+
const taggedBEEF = await this.advertiser.createSLAPAdvertisement(service)
|
|
405
|
+
await this.submit(taggedBEEF)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
for (const ad of shipToRevoke) {
|
|
409
|
+
const taggedBEEF = await this.advertiser.revokeAdvertisement(ad)
|
|
410
|
+
await this.submit(taggedBEEF)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
for (const ad of slapToRevoke) {
|
|
414
|
+
const taggedBEEF = await this.advertiser.revokeAdvertisement(ad)
|
|
415
|
+
await this.submit(taggedBEEF)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
258
419
|
/**
|
|
259
420
|
* Traverse and return the history of a UTXO.
|
|
260
421
|
*
|
|
@@ -63,6 +63,64 @@ describe('BSV Overlay Services Engine', () => {
|
|
|
63
63
|
deleteOutput: jest.fn()
|
|
64
64
|
}
|
|
65
65
|
})
|
|
66
|
+
describe('handleNewMerkleProof tests', () => {
|
|
67
|
+
const mockOutput: Output = {
|
|
68
|
+
txid: exampleTXID,
|
|
69
|
+
outputIndex: 0,
|
|
70
|
+
outputScript: exampleTX.outputs[0].lockingScript.toBinary(),
|
|
71
|
+
topic: 'hello',
|
|
72
|
+
satoshis: exampleTX.outputs[0].satoshis as number,
|
|
73
|
+
beef: exampleBeef,
|
|
74
|
+
spent: false,
|
|
75
|
+
outputsConsumed: [],
|
|
76
|
+
consumedBy: []
|
|
77
|
+
}
|
|
78
|
+
it('0 simple proof', async () => {
|
|
79
|
+
const beef = beef27c8f_1
|
|
80
|
+
const txid = txid27c8f
|
|
81
|
+
const tx = Transaction.fromHexBEEF(beef)
|
|
82
|
+
|
|
83
|
+
const output27c8f_0: Output = {
|
|
84
|
+
txid: txid,
|
|
85
|
+
outputIndex: 0,
|
|
86
|
+
outputScript: tx.outputs[0].lockingScript.toBinary(),
|
|
87
|
+
topic: 'hello',
|
|
88
|
+
satoshis: tx.outputs[0].satoshis as number,
|
|
89
|
+
beef: tx.toBEEF(),
|
|
90
|
+
spent: false,
|
|
91
|
+
outputsConsumed: [],
|
|
92
|
+
consumedBy: []
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
mockLookupService.lookup = jest.fn(async () => [{
|
|
96
|
+
txid: txid,
|
|
97
|
+
outputIndex: 0,
|
|
98
|
+
history: 1
|
|
99
|
+
}])
|
|
100
|
+
let newBEEF: number[] = []
|
|
101
|
+
mockStorageEngine.findOutput = jest.fn(async () => output27c8f_0)
|
|
102
|
+
mockStorageEngine.findOutputsForTransaction = jest.fn(async () => [output27c8f_0])
|
|
103
|
+
mockStorageEngine.updateOutputBeef = jest.fn(async (txid: string, outputIndex: number, topic: string, beef: number[]) => {
|
|
104
|
+
newBEEF = beef
|
|
105
|
+
})
|
|
106
|
+
const engine = new Engine(
|
|
107
|
+
{
|
|
108
|
+
Hello: mockTopicManager
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
Hello: mockLookupService
|
|
112
|
+
},
|
|
113
|
+
mockStorageEngine,
|
|
114
|
+
mockChainTracker
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const merklePath = Transaction.fromHexBEEF(beef27c8f_0).merklePath
|
|
118
|
+
if (!merklePath) throw('improper test setup')
|
|
119
|
+
await engine.handleNewMerkleProof(txid, merklePath)
|
|
120
|
+
expect(newBEEF.length > 0 && newBEEF.length < beef.length / 2).toBe(true)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
66
124
|
describe('submit', () => {
|
|
67
125
|
it('Throws an error if the user submits to a topic that is not supported', async () => {
|
|
68
126
|
const engine = new Engine(
|
|
@@ -792,3 +850,10 @@ describe('BSV Overlay Services Engine', () => {
|
|
|
792
850
|
// })
|
|
793
851
|
})
|
|
794
852
|
})
|
|
853
|
+
|
|
854
|
+
// beef27c8f_1 is 1 level deep, i.e. proofs on inputs
|
|
855
|
+
const beef27c8f_1 = '0100beef01feafcf0c000802020044ef36fc6c79af360b13f2164c6e772e6e5e2083a54d72894f34710fe6a68cf80302792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d010000059e83aae2cfc6a35477566439dd9d58969cc41a134864c671c9b844f2a409a60101004991c129bba819832d6dd315c3a2065bd31b1a60aff8192be335f83731641e690101002f87724a7387df82417902266d2a4fb2fee857451e963a352ce8acbb279aa9eb010100a9e9eb55d20cef840bacbdb26ad8d6e7680ef7653364830b1e770fa140ece6bb010100f0ecd03fff2df21b0034d331cade50152781be9052dcec54eb95c963ef1cdcbf0101006fdb2c01a266e761a32f53b3ad0fabbca31ae234d31d85d9af9bbd624b321c9b0101008108cad77aabf7b0ee371f42afd773cada534244535f7108c7590df2b9d5f8a2020100000006e9847b33c5d666ccee1dccd52235489d8d58bf432be1b8c0cc7c13af80f10703030000006b483045022100fe8a18e043d693246a56c9b32ff553bea89a0bbe389bc55e8244044dcd865de3022065245c30fbbcc6820e4761048ed28c587bf41ece1933dfb85b8fa8c3433d93504121022f2ac22e73a55aa4ff21e905f66959200642ae74716544f834a48af5aa53fb27ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45050000006b483045022100e6332a6459350ff2486a39b6d79cdc0b65b6d52cf898fc457e45880c0db01b2a02206e34126bc81e6f03c6de90278788d6b2be3a3f5d7bd85e6442bbea90b8a5e2394121029780a1a98be5ff17a3228feb830e3bc32395ebecc5cf6de08f7a5685faf66c07ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45040000006b483045022100dfba71c6c33d2366b2202ea136271fd2f3a26c0a7295f6dd6018fbe12fc48708022052387ca532247bbfffef4b076fd2fbbdebe85b3cf53c73004897cd07be5d1ad6412103f735eb69c9f0b3c90dd2798b709e12a5b3d176a4690bd2a31ec616af9ecd47b5ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45020000006b483045022100f941b74c5da1e7f0ce4d1c08745e4d6b8ef56656199505caeff4b74f54890cec02201eaa0d01112497c89ebe4818687c5f3305629a83abd3b0bf5135feee68f57ca0412103fb0ec51f9e6e45b213cb1deff921e3322624cfb6d4a4ba5dc699867cf163b736ffffffff7e8823d7e3e5565fdb9e53c015ad5b63bc6dd6ee1a9be7f7a8e105c903783d45030000006a47304402205a3a2848350fdc19cae73243bea16dfc1e8fbbd5cecde4b5c88a57790505cebb022043db51d04998d52ec394d0e945a76e9922dd63caffb882242bec2ced2a35c7f1412102bae3e2f47dceaed7db2a212e059a00674a0087e3395b92d9f12cdbefdbc84efaffffffffad49b6f41125ffadab03cb2dcc24caf2c897c2cd4aed6e0a3b5c003f2dde757a030000006b483045022100aa7aaec7fc6c5a2f4d5318b7fcbbca0f223c5c198f8fc0e1c8d59aee7155af1202207a4960fb58aa86eba12852f84fa3cdb4c427c7374fe737b3d5bcd9e2a6d76e3b412102b55cfd872e85b3dd424dcceafe01ed5a87dfb599d354ab7fc50d9ec48af77f6effffffff1a400d0300000000001976a914f182138abcb69c7c2339040605aa0a442cfec44288acc8000000000000001976a914e9d2276f468223a6d650672ab9d4c1271fb157e288acacb00000000000001976a914c0ea0208a097df8146933ae15f3ce545d5451e7388ac7a140000000000001976a9142d9ae3291f9393639fe2c38c9a7a948d136a10b488acea070000000000001976a91435d1c396b7ccb467118a41c2d77ebf79fb5fb7eb88ac17050000000000001976a9140dac1e2ddc975ee6d9f393e2ccd4876edb386cdd88ac51060000000000001976a91422878cb2f57a1ebe513815562003128f6e99eda188ac67040000000000001976a91439a752923ba1ef8b126ba7886646d58f54521c6588ac1e040000000000001976a914a939565af52d3fa974a040f42c35365d76b98f6a88acec030000000000001976a914c90823a53e29cafd35a225cc14e3a44eda143a5788ac05040000000000001976a914af6f8d6aaec3bf123efc17c8977bfad1ec1c859388acec030000000000001976a914d4c5e66a062e634be1c6f0eb1f2af0acebecb47988acf1030000000000001976a914a94a3e84bd740095ab39bed110cd3b3922c8595088ace8030000000000001976a914aa2e3c3bf6f3ee79f5af3b94eab2be60a394ce4c88aceb030000000000001976a91459394fa083ff3726571c79624dba3b38f14d924c88ace8030000000000001976a914726eb3ae6211711aa1624f3d767035b0b44abdc488ace8030000000000001976a9145f4680a099a6dc6891af3094aaab96aa66a6259888ace8030000000000001976a914f1a4fbd99e3923b0934abdb8839c337eb2fd50b888ace8030000000000001976a9147ed0ad307f5fe9993f984e7525c0012e3569594588ace8030000000000001976a91407f386bcdc4892cc2d0be3226b721ceaf04750b288ace8030000000000001976a914b51edaaa3c92d0af94109d952c7ddf13aa345f9988ace8030000000000001976a914018f4e02bd74f17a75f5b3965db4633ab514c57588ace8030000000000001976a914cc8d1f7ba5406b81efbc23da0fcccb4d3fe5b40a88ace8030000000000001976a914db1d4b7bd3e62f535dc3f2c8d0a989b000fa736d88ace8030000000000001976a914b8ea69358da7bea9506b15db6fc6597b3849b0a188ace8030000000000001976a914ecd769fdd67e04786e871c50c1a10504861bd79488ac0000000001000100000001792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d070000006b48304502210090368b5925795abb0415a3cd6265d7abae7d3541fb7f2fdb1fdffdd916b3fce3022068136da44df8ee367807c7c59c950ea8d428adfe142268cd6371cf4c1b4c3208412102858ea1804708d1e16e77b49ba1559b3742797f5f43efd3922fa3d21557521d7cffffffff03f401000000000000fde10121026afd108ddbc05e093207ebdf06557a3d66e15b96accbf1766c2e7d6efa3d1981ac22313852713463583334356b6a7461734467764b41455a435135564c5a72516f76786d2081d846bcae65ce281829b6d81eff3862a68b8eaf4e99ad97c0c4ad2e149631c020f936ace2d2b4034710dec819e343f69ddbb1a16d721b88413b697c2475f75f504c4e8470062e6966cb7ec80864e5e1217ecf2b3f113bb5307e1ec1d338678e54b426a11a267e990f7dcd85072c7b3870fabb54812d7f8a7aaa672ce01e4f8a2c6a1d532849877329bd23be0b3383fcb437ffd1ff05aad824343bfe5bae52d5775ed99ba9d5a72038dd50f81153e674a8b1c5d659aac361e660cc5657b291eda149de2505efb0bbac0a353331393633353238334c797f3040f01934bb25733d4700a0309194a7defd8ceea7a8a59ad279c6237e2917264a115773777c5746546288b15ec1cf6788b801dd3952fec804cadbba2ff4e05464735e2db83c3509b59119efc843716c34becd2884f011f2e7f61f382237891875115b6ea5bac1c3e9ca6c575b44547d589c38c1b5a8cadb463044022016e1fdb226b465f3151aaa1e033b122dd58f3b495d2846d532f784ec8f6fc9ae022031dc12155c02e7f588c9f6d975a79c9c26cf341c1c88578cd6a87a3c01f6a73d6d6d6d6dc8000000000000001976a914010b7bfa8ff585b6d95f8a18a998cbd87508bd4188aca9010000000000001976a914254c6f1ce67d27ae4dafb03d2ba08318df2883c588ac0000000000'
|
|
856
|
+
// beef27c8f_0 is 0 levels deep, i.e. tx has merklePath
|
|
857
|
+
const beef27c8f_0 = '0100beef01fef4f10c000902fd020100ab2d9b3bbfc2ecf5c834f7719bf10dcabef31cfa3b78fa714bd3a2b3958fc6b5fd0301029515629d935d81e704ca97b8dc02a698c39d78f06bbb3d8d46bcaa5178f3c827018000fc94b1da08b5d0850afcc4948a9e129a2c2c0bb629090ca84fef5d326ceadbc2014100fa78356192a22189dd3c2a03bfb2e043667a55a85386a8d0f0853bbaa54ba748012100e37888006acc37cbfc6a459117c3e957b00e8ff3a90d93bc9aaa3fc9d972453701110052564e444cbe4b1f24015d91f5cfd7eff63e5f403613a7b48e77efa32761f950010900acef65445ef232f1fcb527bab67fccc90ae9d0c610a4f752d84c5e10a3415d74010500e1fadd176551150808ae96ff2b4dff487c432788d23db25fd3fb5e8b3e16b96f0103003e530f146a992ed9378807fa43bc1e6033de0711d728ee3a0ea1507b5ffff2940100006c8eca8a9d680ab2ae4dc1f0e247f282de3f32b99175e6f9ddb4f8e76ae0bf1b010100000001792c8c563b17721744d8f101d79e873aae62816d0fd47dd9d90ada219c1b252d070000006b48304502210090368b5925795abb0415a3cd6265d7abae7d3541fb7f2fdb1fdffdd916b3fce3022068136da44df8ee367807c7c59c950ea8d428adfe142268cd6371cf4c1b4c3208412102858ea1804708d1e16e77b49ba1559b3742797f5f43efd3922fa3d21557521d7cffffffff03f401000000000000fde10121026afd108ddbc05e093207ebdf06557a3d66e15b96accbf1766c2e7d6efa3d1981ac22313852713463583334356b6a7461734467764b41455a435135564c5a72516f76786d2081d846bcae65ce281829b6d81eff3862a68b8eaf4e99ad97c0c4ad2e149631c020f936ace2d2b4034710dec819e343f69ddbb1a16d721b88413b697c2475f75f504c4e8470062e6966cb7ec80864e5e1217ecf2b3f113bb5307e1ec1d338678e54b426a11a267e990f7dcd85072c7b3870fabb54812d7f8a7aaa672ce01e4f8a2c6a1d532849877329bd23be0b3383fcb437ffd1ff05aad824343bfe5bae52d5775ed99ba9d5a72038dd50f81153e674a8b1c5d659aac361e660cc5657b291eda149de2505efb0bbac0a353331393633353238334c797f3040f01934bb25733d4700a0309194a7defd8ceea7a8a59ad279c6237e2917264a115773777c5746546288b15ec1cf6788b801dd3952fec804cadbba2ff4e05464735e2db83c3509b59119efc843716c34becd2884f011f2e7f61f382237891875115b6ea5bac1c3e9ca6c575b44547d589c38c1b5a8cadb463044022016e1fdb226b465f3151aaa1e033b122dd58f3b495d2846d532f784ec8f6fc9ae022031dc12155c02e7f588c9f6d975a79c9c26cf341c1c88578cd6a87a3c01f6a73d6d6d6d6dc8000000000000001976a914010b7bfa8ff585b6d95f8a18a998cbd87508bd4188aca9010000000000001976a914254c6f1ce67d27ae4dafb03d2ba08318df2883c588ac000000000100'
|
|
858
|
+
// full txid
|
|
859
|
+
const txid27c8f = '27c8f37851aabc468d3dbb6bf0789dc398a602dcb897ca04e7815d939d621595'
|