@bsv/overlay 2.2.0 → 2.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +57 -27
- package/README.md +102 -266
- 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 +46 -18
- 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 +377 -0
- package/dist/cjs/src/Engine.d.ts.map +1 -0
- package/dist/cjs/src/Engine.js +430 -436
- 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 +34 -31
- 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 +36 -12
- 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 +408 -414
- 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 +23 -19
- 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/esm/src/storage/knex/migrations/2024-07-17-001-transactions.js +36 -12
- package/dist/esm/src/storage/knex/migrations/2024-07-17-001-transactions.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 +15 -0
- 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/dist/types/src/storage/knex/migrations/2024-07-17-001-transactions.d.ts.map +1 -1
- package/package.json +87 -30
- 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 -2199
- package/src/GASP/OverlayGASPRemote.ts +0 -108
- package/src/GASP/OverlayGASPStorage.ts +0 -383
- 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 -290
- package/src/__tests/Engine.test.ts +0 -1345
- package/src/__tests/OverlayGASPRemote.test.ts +0 -135
- package/src/__tests/OverlayGASPStorage.test.ts +0 -171
- 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 -40
- 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
package/src/TopicManager.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { AdmittanceInstructions } from '@bsv/sdk'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Defines a Topic Manager interface that can be implemented for specific use-cases
|
|
5
|
-
*/
|
|
6
|
-
export interface TopicManager {
|
|
7
|
-
/**
|
|
8
|
-
* Returns instructions that denote which outputs from the provided transaction to admit into the topic, and which previous coins should be retained.
|
|
9
|
-
* Accepts the transaction in BEEF format and an array of those input indices which spend previously-admitted outputs from the same topic.
|
|
10
|
-
* The transaction's BEEF structure will always contain the transactions associated with previous coins for reference (if any), regardless of whether the current transaction was directly proven.
|
|
11
|
-
*/
|
|
12
|
-
identifyAdmissibleOutputs: (
|
|
13
|
-
beef: number[],
|
|
14
|
-
previousCoins: number[],
|
|
15
|
-
offChainValues?: number[],
|
|
16
|
-
mode?: 'historical-tx' | 'current-tx' | 'historical-tx-no-spv'
|
|
17
|
-
) => Promise<AdmittanceInstructions>
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Identifies and returns the inputs needed to anchor any topical outputs from this transaction to their associated previous history.
|
|
21
|
-
* @throws - if there are no potentially valid topical outputs in this transaction
|
|
22
|
-
*/
|
|
23
|
-
identifyNeededInputs?: (beef: number[], offChainValues?: number[]) => Promise<Array<{ txid: string, outputIndex: number }>>
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Returns a Markdown-formatted documentation string for the topic manager.
|
|
27
|
-
*/
|
|
28
|
-
getDocumentation: () => Promise<string>
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Returns a metadata object that can be used to identify the topic manager.
|
|
32
|
-
*/
|
|
33
|
-
getMetaData: () => Promise<{
|
|
34
|
-
name: string
|
|
35
|
-
shortDescription: string
|
|
36
|
-
iconURL?: string
|
|
37
|
-
version?: string
|
|
38
|
-
informationURL?: string
|
|
39
|
-
}>
|
|
40
|
-
}
|
package/src/__tests/BASM.test.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { computeBasmRoot, computeTac } from '../BASM'
|
|
2
|
-
|
|
3
|
-
const ZERO = '0000000000000000000000000000000000000000000000000000000000000000'
|
|
4
|
-
const TXID_1 = '0101010101010101010101010101010101010101010101010101010101010101'
|
|
5
|
-
const TXID_2 = '0202020202020202020202020202020202020202020202020202020202020202'
|
|
6
|
-
const TXID_3 = '0303030303030303030303030303030303030303030303030303030303030303'
|
|
7
|
-
const BLOCK_HASH = '0404040404040404040404040404040404040404040404040404040404040404'
|
|
8
|
-
|
|
9
|
-
describe('BRC-136 BASM helpers', () => {
|
|
10
|
-
it('computes the empty BASM root as 32 zero bytes', () => {
|
|
11
|
-
expect(computeBasmRoot([])).toBe(ZERO)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('uses the single admitted txid as the BASM root', () => {
|
|
15
|
-
expect(computeBasmRoot([TXID_1])).toBe(TXID_1)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('hashes two txids in internal byte order and returns display hex', () => {
|
|
19
|
-
expect(computeBasmRoot([TXID_1, TXID_2])).toBe(
|
|
20
|
-
'b4100303b9e99ada4b479b0bb93d9b549cb057a1c4be08896bc982debe20ce39'
|
|
21
|
-
)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('duplicates the odd leaf in multi-layer BASM roots', () => {
|
|
25
|
-
expect(computeBasmRoot([TXID_1, TXID_2, TXID_3])).toBe(
|
|
26
|
-
'acbd47d5022a6c5e954ad677df8ec221c893f871889826df53f0f1ad3f023e22'
|
|
27
|
-
)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('orders admitted refs by blockIndex instead of txid', () => {
|
|
31
|
-
const root = computeBasmRoot([
|
|
32
|
-
{ txid: TXID_2, blockIndex: 20 },
|
|
33
|
-
{ txid: TXID_1, blockIndex: 10 }
|
|
34
|
-
])
|
|
35
|
-
expect(root).toBe('b4100303b9e99ada4b479b0bb93d9b549cb057a1c4be08896bc982debe20ce39')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('chains TAC in internal byte order', () => {
|
|
39
|
-
const root = computeBasmRoot([TXID_1, TXID_2, TXID_3])
|
|
40
|
-
expect(computeTac(ZERO, BLOCK_HASH, root)).toBe(
|
|
41
|
-
'd0d3e770802c7c28ca1da59cc92263d59425f00e48d421ffebb02497b4702307'
|
|
42
|
-
)
|
|
43
|
-
})
|
|
44
|
-
})
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
// Note: references Engine from dist due to compiled code references in Engine.ts
|
|
2
|
-
// (matching Engine.test.ts).
|
|
3
|
-
import { Engine } from '../../dist/cjs/src/Engine.js'
|
|
4
|
-
import { computeBasmRoot, computeTac } from '../BASM'
|
|
5
|
-
import type { AdmittedTxRef, TopicBlockAnchor } from '../BASM'
|
|
6
|
-
|
|
7
|
-
const ZERO = '0000000000000000000000000000000000000000000000000000000000000000'
|
|
8
|
-
const TXID_1 = '0101010101010101010101010101010101010101010101010101010101010101'
|
|
9
|
-
const TXID_2 = '0202020202020202020202020202020202020202020202020202020202020202'
|
|
10
|
-
|
|
11
|
-
/** Deterministic non-zero 32-byte hex block hash for a height. */
|
|
12
|
-
const blockHashFor = (height: number): string => (height + 1).toString(16).padStart(64, '0')
|
|
13
|
-
|
|
14
|
-
interface AppliedRow {
|
|
15
|
-
txid: string
|
|
16
|
-
topic: string
|
|
17
|
-
blockHeight?: number
|
|
18
|
-
blockHash?: string
|
|
19
|
-
blockIndex?: number
|
|
20
|
-
merkleRoot?: string
|
|
21
|
-
firstSeenHeight?: number
|
|
22
|
-
proven: boolean
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface FakeStore {
|
|
26
|
-
anchors: Map<string, TopicBlockAnchor>
|
|
27
|
-
admitted?: Map<number, AdmittedTxRef[]>
|
|
28
|
-
applied?: AppliedRow[]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function makeStorage (store: FakeStore): any {
|
|
32
|
-
const key = (topic: string, height: number): string => `${topic}:${height}`
|
|
33
|
-
return {
|
|
34
|
-
async findTopicAnchorTip (topic: string) {
|
|
35
|
-
let tip: TopicBlockAnchor | undefined
|
|
36
|
-
for (const anchor of store.anchors.values()) {
|
|
37
|
-
if (anchor.topic === topic && (tip === undefined || anchor.blockHeight > tip.blockHeight)) {
|
|
38
|
-
tip = anchor
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return tip ?? { topic, blockHeight: -1, tac: ZERO }
|
|
42
|
-
},
|
|
43
|
-
async upsertTopicBlockAnchor (anchor: TopicBlockAnchor) {
|
|
44
|
-
store.anchors.set(key(anchor.topic, anchor.blockHeight), { ...anchor })
|
|
45
|
-
},
|
|
46
|
-
async findTopicBlockAnchor (topic: string, height: number) {
|
|
47
|
-
return store.anchors.get(key(topic, height))
|
|
48
|
-
},
|
|
49
|
-
async findTopicBlockAnchors (topic: string, fromHeight: number, toHeight: number) {
|
|
50
|
-
const result: TopicBlockAnchor[] = []
|
|
51
|
-
for (const anchor of store.anchors.values()) {
|
|
52
|
-
if (anchor.topic === topic && anchor.blockHeight >= fromHeight && anchor.blockHeight <= toHeight) {
|
|
53
|
-
result.push(anchor)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return result.sort((a, b) => a.blockHeight - b.blockHeight)
|
|
57
|
-
},
|
|
58
|
-
async findAdmittedTransactionsForBlock (topic: string, height: number) {
|
|
59
|
-
// Prefer deriving from proven applied rows so demotion is reflected.
|
|
60
|
-
if (store.applied !== undefined) {
|
|
61
|
-
return store.applied
|
|
62
|
-
.filter(r => r.proven && r.topic === topic && r.blockHeight === height && r.blockIndex !== undefined)
|
|
63
|
-
.sort((a, b) => (a.blockIndex ?? 0) - (b.blockIndex ?? 0))
|
|
64
|
-
.map(r => ({ txid: r.txid, blockIndex: r.blockIndex as number }))
|
|
65
|
-
}
|
|
66
|
-
return store.admitted?.get(height) ?? []
|
|
67
|
-
},
|
|
68
|
-
async findProvenAppliedTransactionsByBlockHash (blockHash: string) {
|
|
69
|
-
return (store.applied ?? [])
|
|
70
|
-
.filter(r => r.proven && r.blockHash?.toLowerCase() === blockHash.toLowerCase())
|
|
71
|
-
.map(r => ({ txid: r.txid, topic: r.topic, blockHeight: r.blockHeight as number }))
|
|
72
|
-
},
|
|
73
|
-
async findProvenAppliedTransactionsInRange (fromHeight: number, toHeight: number, topic?: string) {
|
|
74
|
-
return (store.applied ?? [])
|
|
75
|
-
.filter(r => r.proven && r.blockHeight !== undefined && r.blockHeight >= fromHeight && r.blockHeight <= toHeight && (topic === undefined || r.topic === topic))
|
|
76
|
-
.map(r => ({ txid: r.txid, topic: r.topic, blockHeight: r.blockHeight as number, blockHash: r.blockHash, merkleRoot: r.merkleRoot }))
|
|
77
|
-
},
|
|
78
|
-
async demoteAppliedTransactionToUnproven (txid: string, topic: string) {
|
|
79
|
-
const row = (store.applied ?? []).find(r => r.txid === txid && r.topic === topic)
|
|
80
|
-
if (row !== undefined) {
|
|
81
|
-
row.proven = false
|
|
82
|
-
row.blockHeight = undefined
|
|
83
|
-
row.blockHash = undefined
|
|
84
|
-
row.blockIndex = undefined
|
|
85
|
-
row.merkleRoot = undefined
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function makeEngine (
|
|
92
|
-
store: FakeStore,
|
|
93
|
-
opts: { resolver?: (h: number) => Promise<{ blockHeight: number, blockHash: string }>, currentHeight?: number, isValidRootForHeight?: (root: string, height: number) => Promise<boolean> } = {}
|
|
94
|
-
): Engine {
|
|
95
|
-
const managers = { tm_test: { identifyAdmissibleOutputs: jest.fn(), getDocumentation: async () => '', getMetaData: async () => ({ name: 'm', shortDescription: 's' }) } } as any
|
|
96
|
-
const resolver = opts.resolver ?? (async (blockHeight: number) => ({ blockHeight, blockHash: blockHashFor(blockHeight) }))
|
|
97
|
-
return new Engine(
|
|
98
|
-
managers,
|
|
99
|
-
{},
|
|
100
|
-
makeStorage(store),
|
|
101
|
-
{ isValidRootForHeight: opts.isValidRootForHeight ?? (async () => true), currentHeight: async () => opts.currentHeight ?? 105 } as any,
|
|
102
|
-
'https://example.com',
|
|
103
|
-
undefined, undefined, undefined, undefined, undefined,
|
|
104
|
-
false, '[T] ', false, undefined as any, console, true,
|
|
105
|
-
resolver, false, 144
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
describe('BRC-136 BASM anchor chain continuity', () => {
|
|
110
|
-
it('extends the chain with empty anchors so the TAC never resets across blocks with no admitted txs', async () => {
|
|
111
|
-
const store: FakeStore = { anchors: new Map(), admitted: new Map() }
|
|
112
|
-
|
|
113
|
-
// Genesis: topic admits two txs at height 100.
|
|
114
|
-
store.admitted!.set(100, [
|
|
115
|
-
{ txid: TXID_1, blockIndex: 0 },
|
|
116
|
-
{ txid: TXID_2, blockIndex: 1 }
|
|
117
|
-
])
|
|
118
|
-
const genesisRoot = computeBasmRoot(store.admitted!.get(100)!)
|
|
119
|
-
const genesisTac = computeTac(ZERO, blockHashFor(100), genesisRoot)
|
|
120
|
-
store.anchors.set('tm_test:100', {
|
|
121
|
-
topic: 'tm_test',
|
|
122
|
-
blockHeight: 100,
|
|
123
|
-
blockHash: blockHashFor(100),
|
|
124
|
-
basmRoot: genesisRoot,
|
|
125
|
-
admittedCount: 2,
|
|
126
|
-
tac: genesisTac
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const engine = makeEngine(store)
|
|
130
|
-
await engine.advanceTopicAnchorChains(105)
|
|
131
|
-
|
|
132
|
-
// Every height 100..105 must have an anchor — no gaps.
|
|
133
|
-
for (let h = 100; h <= 105; h++) {
|
|
134
|
-
expect(store.anchors.get(`tm_test:${h}`)).toBeDefined()
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 101..105 are empty anchors (zero root, zero count) chained off genesis.
|
|
138
|
-
let expectedTac = genesisTac
|
|
139
|
-
for (let h = 101; h <= 105; h++) {
|
|
140
|
-
const anchor = store.anchors.get(`tm_test:${h}`)!
|
|
141
|
-
expect(anchor.basmRoot).toBe(ZERO)
|
|
142
|
-
expect(anchor.admittedCount).toBe(0)
|
|
143
|
-
expectedTac = computeTac(expectedTac, blockHashFor(h), ZERO)
|
|
144
|
-
expect(anchor.tac).toBe(expectedTac)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// The tip TAC is a cumulative hash that still depends on the genesis block —
|
|
148
|
-
// i.e. it was NOT reset to a per-block value.
|
|
149
|
-
const tipTac = store.anchors.get('tm_test:105')!.tac
|
|
150
|
-
const resetTac = computeTac(ZERO, blockHashFor(105), ZERO)
|
|
151
|
-
expect(tipTac).not.toBe(resetTac)
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
/** Replacement canonical block hash for height 101 after a reorg. */
|
|
156
|
-
const H101_NEW = 'aaaa000000000000000000000000000000000000000000000000000000000000'
|
|
157
|
-
|
|
158
|
-
/** Header resolver where height 101's canonical block hash is H101_NEW (post-reorg). */
|
|
159
|
-
const reorgResolver = async (h: number): Promise<{ blockHeight: number, blockHash: string }> =>
|
|
160
|
-
({ blockHeight: h, blockHash: h === 101 ? H101_NEW : blockHashFor(h) })
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Builds a topic with a proven admission at height 100 (TXID_1, stays canonical)
|
|
164
|
-
* and a proven admission at height 101 (TXID_2) in a block that will be orphaned.
|
|
165
|
-
*/
|
|
166
|
-
function seedTwoBlockChain (): FakeStore {
|
|
167
|
-
const applied: AppliedRow[] = [
|
|
168
|
-
{ txid: TXID_1, topic: 'tm_test', blockHeight: 100, blockHash: blockHashFor(100), blockIndex: 0, merkleRoot: 'm100', firstSeenHeight: 100, proven: true },
|
|
169
|
-
{ txid: TXID_2, topic: 'tm_test', blockHeight: 101, blockHash: blockHashFor(101), blockIndex: 0, merkleRoot: 'm101', firstSeenHeight: 101, proven: true }
|
|
170
|
-
]
|
|
171
|
-
const root100 = computeBasmRoot([{ txid: TXID_1, blockIndex: 0 }])
|
|
172
|
-
const tac100 = computeTac(ZERO, blockHashFor(100), root100)
|
|
173
|
-
const root101 = computeBasmRoot([{ txid: TXID_2, blockIndex: 0 }])
|
|
174
|
-
const tac101 = computeTac(tac100, blockHashFor(101), root101)
|
|
175
|
-
const anchors = new Map<string, TopicBlockAnchor>()
|
|
176
|
-
anchors.set('tm_test:100', { topic: 'tm_test', blockHeight: 100, blockHash: blockHashFor(100), basmRoot: root100, admittedCount: 1, tac: tac100 })
|
|
177
|
-
anchors.set('tm_test:101', { topic: 'tm_test', blockHeight: 101, blockHash: blockHashFor(101), basmRoot: root101, admittedCount: 1, tac: tac101 })
|
|
178
|
-
return { anchors, applied }
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
describe('BRC-136 BASM reorg handling', () => {
|
|
182
|
-
it('demotes an orphaned-block tx to unproven and rebuilds the anchor over the canonical hash', async () => {
|
|
183
|
-
const store = seedTwoBlockChain()
|
|
184
|
-
// Reorg: height 101's block (blockHashFor(101)) is orphaned; the replacement
|
|
185
|
-
// canonical block at 101 (H101_NEW) does not re-include TXID_2.
|
|
186
|
-
const engine = makeEngine(store, { resolver: reorgResolver, currentHeight: 101 })
|
|
187
|
-
|
|
188
|
-
await (engine as any).handleReorg({
|
|
189
|
-
orphanedBlockHashes: [blockHashFor(101)],
|
|
190
|
-
rebuildFromHeight: 101,
|
|
191
|
-
newTipHeight: 101
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
// TXID_2 is demoted: no longer proven, block metadata cleared.
|
|
195
|
-
const row = store.applied!.find(r => r.txid === TXID_2)!
|
|
196
|
-
expect(row.proven).toBe(false)
|
|
197
|
-
expect(row.blockHeight).toBeUndefined()
|
|
198
|
-
// It survives as a receipt record (firstSeenHeight retained).
|
|
199
|
-
expect(row.firstSeenHeight).toBe(101)
|
|
200
|
-
|
|
201
|
-
// Anchor at 101 rebuilt: empty admitted set, canonical hash, recomputed TAC.
|
|
202
|
-
const anchor101 = store.anchors.get('tm_test:101')!
|
|
203
|
-
expect(anchor101.admittedCount).toBe(0)
|
|
204
|
-
expect(anchor101.basmRoot).toBe(ZERO)
|
|
205
|
-
expect(anchor101.blockHash).toBe(H101_NEW)
|
|
206
|
-
const tac100 = store.anchors.get('tm_test:100')!.tac
|
|
207
|
-
expect(anchor101.tac).toBe(computeTac(tac100, H101_NEW, ZERO))
|
|
208
|
-
|
|
209
|
-
// Height 100 is untouched.
|
|
210
|
-
const root100 = computeBasmRoot([{ txid: TXID_1, blockIndex: 0 }])
|
|
211
|
-
expect(store.anchors.get('tm_test:100')!.basmRoot).toBe(root100)
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('is idempotent: orphaned hashes matching no proven rows leave the chain unchanged', async () => {
|
|
215
|
-
const store = seedTwoBlockChain()
|
|
216
|
-
const engine = makeEngine(store, { currentHeight: 101 })
|
|
217
|
-
const before = store.anchors.get('tm_test:101')!.tac
|
|
218
|
-
|
|
219
|
-
await (engine as any).handleReorg({
|
|
220
|
-
orphanedBlockHashes: ['ffff000000000000000000000000000000000000000000000000000000000000'],
|
|
221
|
-
rebuildFromHeight: 101,
|
|
222
|
-
newTipHeight: 101
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
expect(store.applied!.find(r => r.txid === TXID_2)!.proven).toBe(true)
|
|
226
|
-
expect(store.anchors.get('tm_test:101')!.tac).toBe(before)
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
it('re-proving a demoted tx at a new height restores it to the admitted set', async () => {
|
|
230
|
-
const store = seedTwoBlockChain()
|
|
231
|
-
const engine = makeEngine(store, { resolver: reorgResolver, currentHeight: 101 })
|
|
232
|
-
|
|
233
|
-
await (engine as any).handleReorg({
|
|
234
|
-
orphanedBlockHashes: [blockHashFor(101)],
|
|
235
|
-
rebuildFromHeight: 101,
|
|
236
|
-
newTipHeight: 101
|
|
237
|
-
})
|
|
238
|
-
expect(store.anchors.get('tm_test:101')!.basmRoot).toBe(ZERO)
|
|
239
|
-
|
|
240
|
-
// TXID_2 re-mined into the new canonical block at 101.
|
|
241
|
-
const row = store.applied!.find(r => r.txid === TXID_2)!
|
|
242
|
-
row.proven = true
|
|
243
|
-
row.blockHeight = 101
|
|
244
|
-
row.blockHash = H101_NEW
|
|
245
|
-
row.blockIndex = 0
|
|
246
|
-
row.merkleRoot = 'm101new'
|
|
247
|
-
|
|
248
|
-
await (engine as any).handleReorg({
|
|
249
|
-
orphanedBlockHashes: [],
|
|
250
|
-
rebuildFromHeight: 101,
|
|
251
|
-
newTipHeight: 101
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
const anchor101 = store.anchors.get('tm_test:101')!
|
|
255
|
-
expect(anchor101.admittedCount).toBe(1)
|
|
256
|
-
expect(anchor101.basmRoot).toBe(computeBasmRoot([{ txid: TXID_2, blockIndex: 0 }]))
|
|
257
|
-
expect(anchor101.blockHash).toBe(H101_NEW)
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
describe('BRC-136 BASM reorg revalidation sweep', () => {
|
|
262
|
-
it('detects a stale proof root within the window and reorgs the affected height', async () => {
|
|
263
|
-
const store = seedTwoBlockChain()
|
|
264
|
-
// Height 101's proof root no longer validates (block orphaned); canonical
|
|
265
|
-
// header at 101 is now H101_NEW.
|
|
266
|
-
const isValidRootForHeight = async (root: string, height: number): Promise<boolean> => !(height === 101 && root === 'm101')
|
|
267
|
-
const engine = makeEngine(store, { resolver: reorgResolver, currentHeight: 101, isValidRootForHeight })
|
|
268
|
-
|
|
269
|
-
await (engine as any).revalidateRecentAnchors(3)
|
|
270
|
-
|
|
271
|
-
expect(store.applied!.find(r => r.txid === TXID_2)!.proven).toBe(false)
|
|
272
|
-
const anchor101 = store.anchors.get('tm_test:101')!
|
|
273
|
-
expect(anchor101.basmRoot).toBe(ZERO)
|
|
274
|
-
expect(anchor101.blockHash).toBe(H101_NEW)
|
|
275
|
-
// Height 100 still valid and untouched.
|
|
276
|
-
expect(store.applied!.find(r => r.txid === TXID_1)!.proven).toBe(true)
|
|
277
|
-
expect(store.anchors.get('tm_test:100')!.basmRoot).toBe(computeBasmRoot([{ txid: TXID_1, blockIndex: 0 }]))
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
it('leaves the chain unchanged when every proof in the window still validates', async () => {
|
|
281
|
-
const store = seedTwoBlockChain()
|
|
282
|
-
const engine = makeEngine(store, { currentHeight: 101, isValidRootForHeight: async () => true })
|
|
283
|
-
const before = store.anchors.get('tm_test:101')!.tac
|
|
284
|
-
|
|
285
|
-
await (engine as any).revalidateRecentAnchors(3)
|
|
286
|
-
|
|
287
|
-
expect(store.applied!.find(r => r.txid === TXID_2)!.proven).toBe(true)
|
|
288
|
-
expect(store.anchors.get('tm_test:101')!.tac).toBe(before)
|
|
289
|
-
})
|
|
290
|
-
})
|