@subsquid/batch-processor 0.2.0-portal-api.493495 → 0.2.0-portal-api.19316d
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/lib/database.d.ts +27 -6
- package/lib/database.d.ts.map +1 -1
- package/lib/run.d.ts +16 -2
- package/lib/run.d.ts.map +1 -1
- package/lib/run.js +134 -97
- package/lib/run.js.map +1 -1
- package/lib/template-registry.d.ts +37 -0
- package/lib/template-registry.d.ts.map +1 -0
- package/lib/template-registry.js +186 -0
- package/lib/template-registry.js.map +1 -0
- package/package.json +3 -2
- package/src/database.ts +36 -6
- package/src/run.ts +195 -96
- package/src/template-registry.test.ts +651 -0
- package/src/template-registry.ts +199 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type {TemplateRegistry as ITemplateRegistry, TemplateValue} from '@subsquid/util-internal-data-source'
|
|
2
|
+
import type {FiniteRange, Range} from '@subsquid/util-internal-range'
|
|
3
|
+
import {TemplateMutation} from './database'
|
|
4
|
+
|
|
5
|
+
export interface TemplateManager {
|
|
6
|
+
add(key: string, value: string, blockNumber: number): void
|
|
7
|
+
remove(key: string, value: string, blockNumber: number): void
|
|
8
|
+
has(key: string, value: string, blockNumber: number): boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class HandlerTemplates implements TemplateManager {
|
|
12
|
+
readonly data: TemplateMutation[] = []
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly range: FiniteRange,
|
|
16
|
+
private readonly registry: TemplateRegistry,
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
add(key: string, value: string, blockNumber: number): void {
|
|
20
|
+
this.mutate('add', key, value, blockNumber)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
remove(key: string, value: string, blockNumber: number): void {
|
|
24
|
+
this.mutate('delete', key, value, blockNumber)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
has(key: string, value: string, blockNumber: number): boolean {
|
|
28
|
+
return this.registry.has(key, value, blockNumber)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private mutate(type: TemplateMutation['type'], key: string, value: string, blockNumber: number): void {
|
|
32
|
+
if (blockNumber < this.range.from) {
|
|
33
|
+
throw new RangeError(`blockNumber ${blockNumber} is before batch start ${this.range.from}`)
|
|
34
|
+
}
|
|
35
|
+
let mutation: TemplateMutation = {type, key, value, blockNumber}
|
|
36
|
+
this.registry.apply(mutation)
|
|
37
|
+
this.data.push(mutation)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class TemplateRegistry implements ITemplateRegistry {
|
|
42
|
+
private byKey = new Map<string, Map<string, Range[]>>()
|
|
43
|
+
private baseMutations: TemplateMutation[] = []
|
|
44
|
+
private undoLog: Array<{blockNumber: number, templates: TemplateMutation[]}> = []
|
|
45
|
+
private pendingTemplates: TemplateMutation[] = []
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Rebuild state from finalized mutations and optionally replay hot block
|
|
49
|
+
* mutations to reconstruct the undo log.
|
|
50
|
+
*
|
|
51
|
+
* Finalized mutations form the base state. Hot block mutations are stored
|
|
52
|
+
* in the undo log so rollbackTo() works for hot block heights after restart.
|
|
53
|
+
*/
|
|
54
|
+
init(
|
|
55
|
+
mutations: TemplateMutation[],
|
|
56
|
+
hotBlocks?: {blockNumber: number, templates: TemplateMutation[]}[]
|
|
57
|
+
): void {
|
|
58
|
+
this.baseMutations = mutations
|
|
59
|
+
this.undoLog = hotBlocks ?? []
|
|
60
|
+
this.pendingTemplates = []
|
|
61
|
+
this.rebuild()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get(key: string): TemplateValue[] {
|
|
65
|
+
let m = this.byKey.get(key)
|
|
66
|
+
if (m == null) return []
|
|
67
|
+
let out: TemplateValue[] = []
|
|
68
|
+
for (let [value, ranges] of m) {
|
|
69
|
+
for (let range of ranges) {
|
|
70
|
+
out.push({value, range})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
has(key: string, value: string, block: number): boolean {
|
|
77
|
+
let byVal = this.byKey.get(key)
|
|
78
|
+
if (byVal == null) return false
|
|
79
|
+
let ranges = byVal.get(value)
|
|
80
|
+
if (ranges == null) return false
|
|
81
|
+
for (let range of ranges) {
|
|
82
|
+
if (block >= range.from && (range.to == null || block <= range.to)) {
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
apply(mutation: TemplateMutation): boolean {
|
|
90
|
+
let changed = this.applyToState(mutation)
|
|
91
|
+
if (changed) {
|
|
92
|
+
this.pendingTemplates.push(mutation)
|
|
93
|
+
}
|
|
94
|
+
return changed
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async transact(
|
|
98
|
+
range: FiniteRange,
|
|
99
|
+
fn: (templates: TemplateManager) => Promise<void>,
|
|
100
|
+
): Promise<{data: TemplateMutation[]; changed: boolean}> {
|
|
101
|
+
let templates = new HandlerTemplates(range, this)
|
|
102
|
+
try {
|
|
103
|
+
await fn(templates)
|
|
104
|
+
} catch (e) {
|
|
105
|
+
this.pendingTemplates = []
|
|
106
|
+
this.rebuild()
|
|
107
|
+
throw e
|
|
108
|
+
}
|
|
109
|
+
let changed = this.pendingTemplates.length > 0
|
|
110
|
+
if (changed) {
|
|
111
|
+
this.undoLog.push({blockNumber: range.to, templates: this.pendingTemplates})
|
|
112
|
+
}
|
|
113
|
+
this.pendingTemplates = []
|
|
114
|
+
return {data: templates.data, changed}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
rollbackTo(blockNumber: number): void {
|
|
118
|
+
let splitIdx = upperBound(this.undoLog, blockNumber)
|
|
119
|
+
if (splitIdx >= this.undoLog.length) return
|
|
120
|
+
this.undoLog.length = splitIdx
|
|
121
|
+
this.pendingTemplates = []
|
|
122
|
+
this.rebuild()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
prune(blockNumber: number): void {
|
|
126
|
+
let splitIdx = upperBound(this.undoLog, blockNumber)
|
|
127
|
+
for (let i = 0; i < splitIdx; i++) {
|
|
128
|
+
this.baseMutations.push(...this.undoLog[i].templates)
|
|
129
|
+
}
|
|
130
|
+
this.undoLog = this.undoLog.slice(splitIdx)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private applyToState({type, key, value, blockNumber}: TemplateMutation): boolean {
|
|
134
|
+
if (type === 'add') {
|
|
135
|
+
let byVal = this.byKey.get(key)
|
|
136
|
+
if (byVal == null) {
|
|
137
|
+
byVal = new Map()
|
|
138
|
+
this.byKey.set(key, byVal)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let ranges = byVal.get(value)
|
|
142
|
+
if (ranges == null) {
|
|
143
|
+
byVal.set(value, [{from: blockNumber}])
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let last = ranges[ranges.length - 1]
|
|
148
|
+
if (last.to != null) {
|
|
149
|
+
if (blockNumber < last.to) return false
|
|
150
|
+
ranges.push({from: blockNumber})
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (blockNumber >= last.from) return false
|
|
155
|
+
last.from = blockNumber
|
|
156
|
+
return true
|
|
157
|
+
} else {
|
|
158
|
+
let byVal = this.byKey.get(key)
|
|
159
|
+
if (!byVal) return false
|
|
160
|
+
let ranges = byVal.get(value)
|
|
161
|
+
if (!ranges || ranges.length === 0) return false
|
|
162
|
+
let last = ranges[ranges.length - 1]
|
|
163
|
+
if (last.to != null) return false
|
|
164
|
+
if (blockNumber < last.from) return false
|
|
165
|
+
last.to = blockNumber
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private rebuild(): void {
|
|
171
|
+
this.byKey.clear()
|
|
172
|
+
let allMutations: TemplateMutation[] = [...this.baseMutations]
|
|
173
|
+
for (let entry of this.undoLog) {
|
|
174
|
+
allMutations.push(...entry.templates)
|
|
175
|
+
}
|
|
176
|
+
allMutations.sort((a, b) => {
|
|
177
|
+
if (a.blockNumber !== b.blockNumber) return a.blockNumber - b.blockNumber
|
|
178
|
+
if (a.type !== b.type) return a.type === 'delete' ? -1 : 1
|
|
179
|
+
return 0
|
|
180
|
+
})
|
|
181
|
+
for (let m of allMutations) {
|
|
182
|
+
this.applyToState(m)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function upperBound(log: Array<{blockNumber: number}>, target: number): number {
|
|
188
|
+
let lo = 0
|
|
189
|
+
let hi = log.length
|
|
190
|
+
while (lo < hi) {
|
|
191
|
+
let mid = (lo + hi) >> 1
|
|
192
|
+
if (log[mid].blockNumber > target) {
|
|
193
|
+
hi = mid
|
|
194
|
+
} else {
|
|
195
|
+
lo = mid + 1
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return lo
|
|
199
|
+
}
|