envio 3.2.0 → 3.3.0-alpha.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/package.json +6 -7
- package/src/Batch.res +12 -15
- package/src/Batch.res.mjs +4 -5
- package/src/BatchProcessing.res +199 -0
- package/src/BatchProcessing.res.mjs +125 -0
- package/src/ChainFetching.res +375 -0
- package/src/ChainFetching.res.mjs +206 -0
- package/src/ChainMetadata.res +27 -0
- package/src/ChainMetadata.res.mjs +27 -0
- package/src/ChainState.res +618 -0
- package/src/ChainState.res.mjs +461 -0
- package/src/ChainState.resi +85 -0
- package/src/Config.res +13 -4
- package/src/Config.res.mjs +8 -3
- package/src/ContractRegisterContext.res +106 -0
- package/src/ContractRegisterContext.res.mjs +76 -0
- package/src/Core.res +3 -0
- package/src/CrossChainState.res +269 -0
- package/src/CrossChainState.res.mjs +197 -0
- package/src/CrossChainState.resi +47 -0
- package/src/Ecosystem.res +58 -0
- package/src/Ecosystem.res.mjs +43 -1
- package/src/Env.res +7 -2
- package/src/Env.res.mjs +5 -2
- package/src/EventConfigBuilder.res +35 -0
- package/src/EventConfigBuilder.res.mjs +21 -0
- package/src/EventProcessing.res +60 -46
- package/src/EventProcessing.res.mjs +33 -32
- package/src/EventUtils.res +0 -4
- package/src/EventUtils.res.mjs +0 -4
- package/src/ExitOnCaughtUp.res +10 -0
- package/src/ExitOnCaughtUp.res.mjs +22 -0
- package/src/FetchState.res +162 -113
- package/src/FetchState.res.mjs +181 -119
- package/src/HandlerLoader.res +6 -1
- package/src/HandlerLoader.res.mjs +13 -36
- package/src/InMemoryStore.res +36 -451
- package/src/InMemoryStore.res.mjs +20 -369
- package/src/IndexerLoop.res +44 -0
- package/src/IndexerLoop.res.mjs +46 -0
- package/src/IndexerState.res +544 -0
- package/src/IndexerState.res.mjs +547 -0
- package/src/IndexerState.resi +132 -0
- package/src/Internal.res +44 -7
- package/src/LoadLayer.res +20 -18
- package/src/LoadLayer.res.mjs +14 -12
- package/src/LoadLayer.resi +6 -3
- package/src/LogSelection.res +86 -17
- package/src/LogSelection.res.mjs +67 -14
- package/src/Logging.res +11 -44
- package/src/Logging.res.mjs +10 -42
- package/src/Main.res +54 -79
- package/src/Main.res.mjs +44 -56
- package/src/PgStorage.res +8 -71
- package/src/PgStorage.res.mjs +3 -47
- package/src/Prometheus.res +7 -8
- package/src/Prometheus.res.mjs +7 -4
- package/src/PruneStaleHistory.res +45 -0
- package/src/PruneStaleHistory.res.mjs +46 -0
- package/src/RawEvent.res +73 -0
- package/src/RawEvent.res.mjs +51 -0
- package/src/Rollback.res +204 -0
- package/src/Rollback.res.mjs +118 -0
- package/src/SimulateItems.res +12 -10
- package/src/SimulateItems.res.mjs +1 -1
- package/src/TestIndexer.res +34 -27
- package/src/TestIndexer.res.mjs +4 -1
- package/src/UserContext.res +26 -114
- package/src/UserContext.res.mjs +15 -78
- package/src/Writing.res +242 -0
- package/src/Writing.res.mjs +215 -0
- package/src/db/InternalTable.res +14 -27
- package/src/sources/Evm.res +40 -1
- package/src/sources/Evm.res.mjs +94 -84
- package/src/sources/Fuel.res +40 -1
- package/src/sources/Fuel.res.mjs +36 -26
- package/src/sources/HyperFuel.res +41 -120
- package/src/sources/HyperFuel.res.mjs +42 -80
- package/src/sources/HyperFuel.resi +1 -24
- package/src/sources/HyperFuelClient.res +16 -297
- package/src/sources/HyperFuelClient.res.mjs +5 -4
- package/src/sources/HyperFuelSource.res +33 -8
- package/src/sources/HyperFuelSource.res.mjs +56 -10
- package/src/sources/HyperSyncSource.res +7 -5
- package/src/sources/HyperSyncSource.res.mjs +3 -3
- package/src/sources/RpcSource.res +2 -2
- package/src/sources/RpcSource.res.mjs +1 -1
- package/src/sources/SourceManager.res +8 -18
- package/src/sources/SourceManager.res.mjs +8 -5
- package/src/sources/SourceManager.resi +4 -1
- package/src/sources/Svm.res +18 -1
- package/src/sources/Svm.res.mjs +30 -22
- package/src/sources/SvmHyperSyncSource.res +1 -1
- package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
- package/src/tui/Tui.res +38 -36
- package/src/tui/Tui.res.mjs +51 -51
- package/src/ChainFetcher.res +0 -519
- package/src/ChainFetcher.res.mjs +0 -314
- package/src/ChainManager.res +0 -358
- package/src/ChainManager.res.mjs +0 -291
- package/src/Ctx.res +0 -6
- package/src/Ctx.res.mjs +0 -2
- package/src/GlobalState.res +0 -1056
- package/src/GlobalState.res.mjs +0 -1086
- package/src/GlobalStateManager.res +0 -57
- package/src/GlobalStateManager.res.mjs +0 -68
- package/src/GlobalStateManager.resi +0 -7
- package/src/Ports.res +0 -5
- package/src/Ports.res.mjs +0 -9
- package/src/adapters/MarkBatchProcessedAdapter.res +0 -5
- package/src/adapters/MarkBatchProcessedAdapter.res.mjs +0 -14
- package/src/sources/EnvioApiClient.res +0 -15
- package/src/sources/EnvioApiClient.res.mjs +0 -24
package/src/InMemoryStore.res
CHANGED
|
@@ -1,136 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
let make = (entities: array<Internal.entityConfig>): t => {
|
|
5
|
-
let init = Dict.make()
|
|
6
|
-
entities->Array.forEach(entityConfig => {
|
|
7
|
-
init->Dict.set((entityConfig.name :> string), InMemoryTable.Entity.make())
|
|
8
|
-
})
|
|
9
|
-
init
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let get = (self: t, ~entityName: string) => {
|
|
13
|
-
switch self->Utils.Dict.dangerouslyGetNonOption(entityName) {
|
|
14
|
-
| Some(table) => table
|
|
15
|
-
| None =>
|
|
16
|
-
UndefinedEntity({entityName: entityName})->ErrorHandling.mkLogAndRaise(
|
|
17
|
-
~msg="Unexpected, entity InMemoryTable is undefined",
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type effectCacheInMemTable = {
|
|
24
|
-
// Cache keys whose handler output is persisted on the next write. Drained
|
|
25
|
-
// each write; eviction is driven by the per-entry checkpointId instead.
|
|
26
|
-
mutable idsToStore: array<string>,
|
|
27
|
-
mutable invalidationsCount: int,
|
|
28
|
-
// Each entry is stamped with the checkpoint that referenced it (or
|
|
29
|
-
// loadedFromDbCheckpointId for db reads), so committed entries can be
|
|
30
|
-
// dropped once persisted/re-derivable, mirroring entity changes.
|
|
31
|
-
mutable dict: dict<Change.t<Internal.effectOutput>>,
|
|
32
|
-
mutable changesCount: float,
|
|
33
|
-
effect: Internal.effect,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type t = {
|
|
37
|
-
allEntities: array<Internal.entityConfig>,
|
|
38
|
-
mutable entities: dict<InMemoryTable.Entity.t>,
|
|
39
|
-
mutable effects: dict<effectCacheInMemTable>,
|
|
40
|
-
mutable rollback: option<Persistence.rollback>,
|
|
41
|
-
// Last checkpoint persisted to the db.
|
|
42
|
-
mutable committedCheckpointId: Internal.checkpointId,
|
|
43
|
-
// Processing frontier; runs ahead of committedCheckpointId while writes lag.
|
|
44
|
-
mutable processedCheckpointId: Internal.checkpointId,
|
|
45
|
-
// Processed but unwritten. The cycle drains them, splitting each write at a
|
|
46
|
-
// change in isInReorgThreshold so it never mixes history-saving modes.
|
|
47
|
-
mutable processedBatches: array<Batch.t>,
|
|
48
|
-
// True while a batch is being processed; guards ProcessEventBatch re-entry.
|
|
49
|
-
mutable isProcessing: bool,
|
|
50
|
-
// Count of processed batches; version-independent progress counter.
|
|
51
|
-
mutable processedBatchesCount: int,
|
|
52
|
-
// The single in-flight write loop, None when idle.
|
|
53
|
-
mutable writeFiber: option<promise<unit>>,
|
|
54
|
-
// Set once a write throws, to stop the loop. The error itself goes to onError.
|
|
55
|
-
mutable hasFailedWrite: bool,
|
|
56
|
-
// Called once on a write failure; the caller decides what to do (exit).
|
|
57
|
-
onError: exn => unit,
|
|
58
|
-
// Resolved after every commit so capacity/flush waiters can re-evaluate.
|
|
59
|
-
mutable commitWaiters: array<unit => unit>,
|
|
60
|
-
persistence: Persistence.t,
|
|
61
|
-
config: Config.t,
|
|
62
|
-
// Latest metadata staged per chain; used to skip unchanged restages.
|
|
63
|
-
mutable chainMeta: dict<InternalTable.Chains.metaFields>,
|
|
64
|
-
// Set on a real change. Folded into a batch write, else flushed on the throttle.
|
|
65
|
-
mutable chainMetaDirty: bool,
|
|
66
|
-
// Throttles metadata-only writes when no batches flow.
|
|
67
|
-
chainMetaThrottler: Throttler.t,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let make = (
|
|
71
|
-
~entities: array<Internal.entityConfig>,
|
|
72
|
-
~committedCheckpointId=Internal.initialCheckpointId,
|
|
73
|
-
~persistence: Persistence.t,
|
|
74
|
-
~config: Config.t,
|
|
75
|
-
~onError: exn => unit,
|
|
76
|
-
): t => {
|
|
77
|
-
let chainMetaThrottler = {
|
|
78
|
-
let intervalMillis = Env.ThrottleWrites.chainMetadataIntervalMillis
|
|
79
|
-
Throttler.make(
|
|
80
|
-
~intervalMillis,
|
|
81
|
-
~logger=Logging.createChild(
|
|
82
|
-
~params={
|
|
83
|
-
"context": "Throttler for chain metadata writes",
|
|
84
|
-
"intervalMillis": intervalMillis,
|
|
85
|
-
},
|
|
86
|
-
),
|
|
87
|
-
)
|
|
88
|
-
}
|
|
1
|
+
// Entity and effect table primitives over IndexerState's in-memory store. State
|
|
2
|
+
// mutations route through IndexerState's domain operations; the write loop and
|
|
3
|
+
// capacity/flush coordination live in Writing.
|
|
89
4
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
committedCheckpointId,
|
|
96
|
-
processedCheckpointId: committedCheckpointId,
|
|
97
|
-
processedBatches: [],
|
|
98
|
-
isProcessing: false,
|
|
99
|
-
processedBatchesCount: 0,
|
|
100
|
-
writeFiber: None,
|
|
101
|
-
hasFailedWrite: false,
|
|
102
|
-
onError,
|
|
103
|
-
commitWaiters: [],
|
|
104
|
-
persistence,
|
|
105
|
-
config,
|
|
106
|
-
chainMeta: Dict.make(),
|
|
107
|
-
chainMetaDirty: false,
|
|
108
|
-
chainMetaThrottler,
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Max uncommitted entity/effect changes plus unwritten batch items before
|
|
113
|
-
// processing must wait for the cycle to free capacity.
|
|
114
|
-
let keepLatestChangesLimit = Env.inMemoryObjectsTarget
|
|
5
|
+
let getInMemTable = (
|
|
6
|
+
state: IndexerState.t,
|
|
7
|
+
~entityConfig: Internal.entityConfig,
|
|
8
|
+
): InMemoryTable.Entity.t =>
|
|
9
|
+
state->IndexerState.entities->IndexerState.EntityTables.get(~entityName=entityConfig.name)
|
|
115
10
|
|
|
116
|
-
let getEffectInMemTable = (
|
|
11
|
+
let getEffectInMemTable = (state: IndexerState.t, ~effect: Internal.effect) => {
|
|
117
12
|
let key = effect.name
|
|
118
|
-
|
|
13
|
+
let effects = state->IndexerState.effects
|
|
14
|
+
switch effects->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
119
15
|
| Some(table) => table
|
|
120
16
|
| None =>
|
|
121
|
-
let table = {
|
|
17
|
+
let table: IndexerState.effectCacheInMemTable = {
|
|
122
18
|
idsToStore: [],
|
|
123
19
|
dict: Dict.make(),
|
|
124
20
|
changesCount: 0.,
|
|
125
21
|
invalidationsCount: 0,
|
|
126
22
|
effect,
|
|
127
23
|
}
|
|
128
|
-
|
|
24
|
+
effects->Dict.set(key, table)
|
|
129
25
|
table
|
|
130
26
|
}
|
|
131
27
|
}
|
|
132
28
|
|
|
133
|
-
let hasEffectOutput = (inMemTable: effectCacheInMemTable, key) =>
|
|
29
|
+
let hasEffectOutput = (inMemTable: IndexerState.effectCacheInMemTable, key) =>
|
|
134
30
|
switch inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
135
31
|
| Some(Set(_)) => true
|
|
136
32
|
| Some(Delete(_)) | None => false
|
|
@@ -139,7 +35,10 @@ let hasEffectOutput = (inMemTable: effectCacheInMemTable, key) =>
|
|
|
139
35
|
// Returns the raw output. The output is itself an option for effects with an
|
|
140
36
|
// optional output, so it must never be wrapped in another option here: Some(None)
|
|
141
37
|
// is encoded as the nested-option sentinel and would leak to the handler.
|
|
142
|
-
let getEffectOutputUnsafe = (
|
|
38
|
+
let getEffectOutputUnsafe = (
|
|
39
|
+
inMemTable: IndexerState.effectCacheInMemTable,
|
|
40
|
+
key,
|
|
41
|
+
): Internal.effectOutput =>
|
|
143
42
|
switch inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(key) {
|
|
144
43
|
| Some(Set({entity: output})) => output
|
|
145
44
|
| Some(Delete(_)) | None => %raw(`undefined`)
|
|
@@ -148,7 +47,7 @@ let getEffectOutputUnsafe = (inMemTable: effectCacheInMemTable, key): Internal.e
|
|
|
148
47
|
// Records a handler output. Persisted on the next write only when shouldCache;
|
|
149
48
|
// otherwise kept in memory (re-run on a later miss) but never written to the db.
|
|
150
49
|
let setEffectOutput = (
|
|
151
|
-
inMemTable: effectCacheInMemTable,
|
|
50
|
+
inMemTable: IndexerState.effectCacheInMemTable,
|
|
152
51
|
~checkpointId,
|
|
153
52
|
~cacheKey,
|
|
154
53
|
~output,
|
|
@@ -166,7 +65,7 @@ let setEffectOutput = (
|
|
|
166
65
|
|
|
167
66
|
// Seeds an entry from a db read. Stamped with loadedFromDbCheckpointId so it's
|
|
168
67
|
// always droppable (re-readable from the db) and never re-persisted.
|
|
169
|
-
let initEffectOutputFromDb = (inMemTable: effectCacheInMemTable, ~cacheKey, ~output) =>
|
|
68
|
+
let initEffectOutputFromDb = (inMemTable: IndexerState.effectCacheInMemTable, ~cacheKey, ~output) =>
|
|
170
69
|
if inMemTable.dict->Utils.Dict.dangerouslyGetNonOption(cacheKey)->Option.isNone {
|
|
171
70
|
inMemTable.changesCount = inMemTable.changesCount +. 1.
|
|
172
71
|
inMemTable.dict->Dict.set(
|
|
@@ -179,7 +78,7 @@ let initEffectOutputFromDb = (inMemTable: effectCacheInMemTable, ~cacheKey, ~out
|
|
|
179
78
|
// cache:false). Uncommitted entries stay warm. With keepLoadedFromDb, entries
|
|
180
79
|
// seeded from a db read are spared. Mirrors entity dropCommittedChanges.
|
|
181
80
|
let dropCommittedEffects = (
|
|
182
|
-
inMemTable: effectCacheInMemTable,
|
|
81
|
+
inMemTable: IndexerState.effectCacheInMemTable,
|
|
183
82
|
~committedCheckpointId,
|
|
184
83
|
~keepLoadedFromDb,
|
|
185
84
|
) => {
|
|
@@ -197,340 +96,26 @@ let dropCommittedEffects = (
|
|
|
197
96
|
inMemTable.changesCount = inMemTable.changesCount -. keysToDelete->Array.length->Int.toFloat
|
|
198
97
|
}
|
|
199
98
|
|
|
200
|
-
let getInMemTable = (
|
|
201
|
-
inMemoryStore: t,
|
|
202
|
-
~entityConfig: Internal.entityConfig,
|
|
203
|
-
): InMemoryTable.Entity.t => {
|
|
204
|
-
inMemoryStore.entities->EntityTables.get(~entityName=entityConfig.name)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
let isRollingBack = (inMemoryStore: t) => inMemoryStore.rollback !== None
|
|
208
|
-
|
|
209
|
-
let getChangesCount = (inMemoryStore: t) => {
|
|
210
|
-
let total = ref(0.)
|
|
211
|
-
inMemoryStore.allEntities->Array.forEach(entityConfig => {
|
|
212
|
-
total := total.contents +. (inMemoryStore->getInMemTable(~entityConfig)).changesCount
|
|
213
|
-
})
|
|
214
|
-
inMemoryStore.effects->Utils.Dict.forEach(inMemTable => {
|
|
215
|
-
total := total.contents +. inMemTable.changesCount
|
|
216
|
-
})
|
|
217
|
-
inMemoryStore.processedBatches->Array.forEach(batch => {
|
|
218
|
-
total := total.contents +. batch.totalBatchSize->Int.toFloat
|
|
219
|
-
})
|
|
220
|
-
total.contents
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
let wakeCommitWaiters = (inMemoryStore: t) => {
|
|
224
|
-
let waiters = inMemoryStore.commitWaiters
|
|
225
|
-
inMemoryStore.commitWaiters = []
|
|
226
|
-
waiters->Array.forEach(resolve => resolve())
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
let waitForCommit = (inMemoryStore: t): promise<unit> =>
|
|
230
|
-
Promise.make((resolve, _) => {
|
|
231
|
-
inMemoryStore.commitWaiters->Array.push(resolve)->ignore
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
// Merges the leading run of batches sharing isInReorgThreshold into one batch;
|
|
235
|
-
// the rest stay queued for the next write. Caller guarantees processedBatches
|
|
236
|
-
// is non-empty.
|
|
237
|
-
let drainBatchRun = (inMemoryStore: t): Batch.t => {
|
|
238
|
-
let all = inMemoryStore.processedBatches
|
|
239
|
-
let isInReorgThreshold = (all->Array.getUnsafe(0)).isInReorgThreshold
|
|
240
|
-
|
|
241
|
-
let rest = []
|
|
242
|
-
let progressedChainsById = Dict.make()
|
|
243
|
-
let totalBatchSize = ref(0)
|
|
244
|
-
let items = []
|
|
245
|
-
let checkpointIds = []
|
|
246
|
-
let checkpointChainIds = []
|
|
247
|
-
let checkpointBlockNumbers = []
|
|
248
|
-
let checkpointBlockHashes = []
|
|
249
|
-
let checkpointEventsProcessed = []
|
|
250
|
-
all->Array.forEach(batch => {
|
|
251
|
-
// Once one batch lands in rest, all later ones follow it, preserving order.
|
|
252
|
-
if rest->Utils.Array.isEmpty && batch.isInReorgThreshold == isInReorgThreshold {
|
|
253
|
-
batch.progressedChainsById->Utils.Dict.forEachWithKey((chainAfterBatch, key) =>
|
|
254
|
-
progressedChainsById->Dict.set(key, chainAfterBatch)
|
|
255
|
-
)
|
|
256
|
-
totalBatchSize := totalBatchSize.contents + batch.totalBatchSize
|
|
257
|
-
items->Array.pushMany(batch.items)
|
|
258
|
-
checkpointIds->Array.pushMany(batch.checkpointIds)
|
|
259
|
-
checkpointChainIds->Array.pushMany(batch.checkpointChainIds)
|
|
260
|
-
checkpointBlockNumbers->Array.pushMany(batch.checkpointBlockNumbers)
|
|
261
|
-
checkpointBlockHashes->Array.pushMany(batch.checkpointBlockHashes)
|
|
262
|
-
checkpointEventsProcessed->Array.pushMany(batch.checkpointEventsProcessed)
|
|
263
|
-
} else {
|
|
264
|
-
rest->Array.push(batch)
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
inMemoryStore.processedBatches = rest
|
|
268
|
-
|
|
269
|
-
{
|
|
270
|
-
totalBatchSize: totalBatchSize.contents,
|
|
271
|
-
items,
|
|
272
|
-
progressedChainsById,
|
|
273
|
-
isInReorgThreshold,
|
|
274
|
-
checkpointIds,
|
|
275
|
-
checkpointChainIds,
|
|
276
|
-
checkpointBlockNumbers,
|
|
277
|
-
checkpointBlockHashes,
|
|
278
|
-
checkpointEventsProcessed,
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Captures the cache:true outputs to persist. The dict is left intact — entries
|
|
283
|
-
// stay warm and are reclaimed later by dropCommittedEffects once committed.
|
|
284
|
-
let snapshotEffects = (inMemoryStore: t, ~cache): array<Persistence.updatedEffectCache> => {
|
|
285
|
-
let acc = []
|
|
286
|
-
inMemoryStore.effects->Utils.Dict.forEach(inMemTable => {
|
|
287
|
-
let {idsToStore, dict, effect, invalidationsCount} = inMemTable
|
|
288
|
-
switch idsToStore {
|
|
289
|
-
| [] => ()
|
|
290
|
-
| ids =>
|
|
291
|
-
let items = ids->Array.filterMap((id): option<Internal.effectCacheItem> =>
|
|
292
|
-
switch dict->Dict.getUnsafe(id) {
|
|
293
|
-
| Set({entity: output}) => Some({id, output})
|
|
294
|
-
| Delete(_) => None
|
|
295
|
-
}
|
|
296
|
-
)
|
|
297
|
-
let effectName = effect.name
|
|
298
|
-
let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(effectName) {
|
|
299
|
-
| Some(c) => c
|
|
300
|
-
| None =>
|
|
301
|
-
let c: Persistence.effectCacheRecord = {effectName, count: 0}
|
|
302
|
-
cache->Dict.set(effectName, c)
|
|
303
|
-
c
|
|
304
|
-
}
|
|
305
|
-
let shouldInitialize = effectCacheRecord.count === 0
|
|
306
|
-
effectCacheRecord.count = effectCacheRecord.count + items->Array.length - invalidationsCount
|
|
307
|
-
Prometheus.EffectCacheCount.set(~count=effectCacheRecord.count, ~effectName)
|
|
308
|
-
acc->Array.push(({effect, items, shouldInitialize}: Persistence.updatedEffectCache))->ignore
|
|
309
|
-
}
|
|
310
|
-
inMemTable.idsToStore = []
|
|
311
|
-
inMemTable.invalidationsCount = 0
|
|
312
|
-
})
|
|
313
|
-
acc
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
let runOneWrite = async (inMemoryStore: t, ~persistence: Persistence.t, ~config) => {
|
|
317
|
-
let cache = switch persistence.storageStatus {
|
|
318
|
-
| Unknown
|
|
319
|
-
| Initializing(_) =>
|
|
320
|
-
JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`)
|
|
321
|
-
| Ready({cache}) => cache
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Copy before the await: the batch write reads this later, in-transaction. A
|
|
325
|
-
// restage during the write re-dirties the flag and is rewritten next iteration.
|
|
326
|
-
let chainMetaData = if inMemoryStore.chainMetaDirty {
|
|
327
|
-
inMemoryStore.chainMetaDirty = false
|
|
328
|
-
Some(inMemoryStore.chainMeta->Utils.Dict.shallowCopy)
|
|
329
|
-
} else {
|
|
330
|
-
None
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
switch inMemoryStore.processedBatches {
|
|
334
|
-
| [] =>
|
|
335
|
-
// Metadata only: a cheap upsert, still serialized by the single write loop.
|
|
336
|
-
switch chainMetaData {
|
|
337
|
-
| Some(chainsData) =>
|
|
338
|
-
await persistence.storage.setChainMeta(chainsData)->Utils.Promise.ignoreValue
|
|
339
|
-
| None => ()
|
|
340
|
-
}
|
|
341
|
-
| _ =>
|
|
342
|
-
let committedCheckpointId = inMemoryStore.committedCheckpointId
|
|
343
|
-
let batch = inMemoryStore->drainBatchRun
|
|
344
|
-
// The run's last checkpoint; entity changes above it stay queued for the next write.
|
|
345
|
-
let upToCheckpointId = switch batch.checkpointIds->Utils.Array.last {
|
|
346
|
-
| Some(checkpointId) => checkpointId
|
|
347
|
-
| None => committedCheckpointId
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
let rollback = inMemoryStore.rollback
|
|
351
|
-
inMemoryStore.rollback = None
|
|
352
|
-
|
|
353
|
-
let updatedEntities = persistence.allEntities->Array.filterMap(entityConfig => {
|
|
354
|
-
let table = inMemoryStore->getInMemTable(~entityConfig)
|
|
355
|
-
let changes =
|
|
356
|
-
table->InMemoryTable.Entity.snapshotChanges(~committedCheckpointId, ~upToCheckpointId)
|
|
357
|
-
if changes->Utils.Array.isEmpty {
|
|
358
|
-
None
|
|
359
|
-
} else {
|
|
360
|
-
Some(({entityConfig, changes}: Persistence.updatedEntity))
|
|
361
|
-
}
|
|
362
|
-
})
|
|
363
|
-
let updatedEffectsCache = snapshotEffects(inMemoryStore, ~cache)
|
|
364
|
-
|
|
365
|
-
await persistence.storage.writeBatch(
|
|
366
|
-
~batch,
|
|
367
|
-
~rollback,
|
|
368
|
-
~isInReorgThreshold=batch.isInReorgThreshold,
|
|
369
|
-
~config,
|
|
370
|
-
~allEntities=persistence.allEntities,
|
|
371
|
-
~updatedEntities,
|
|
372
|
-
~updatedEffectsCache,
|
|
373
|
-
~chainMetaData,
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
inMemoryStore.committedCheckpointId = upToCheckpointId
|
|
377
|
-
|
|
378
|
-
switch rollback {
|
|
379
|
-
| Some({progressBlockNumberByChainId}) if RollbackCommit.callbacks->Utils.Array.notEmpty =>
|
|
380
|
-
await RollbackCommit.fire(~progressBlockNumberByChainId)
|
|
381
|
-
| _ => ()
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
let hasPendingWrite = (inMemoryStore: t) =>
|
|
387
|
-
inMemoryStore.processedBatches->Utils.Array.notEmpty || inMemoryStore.chainMetaDirty
|
|
388
|
-
|
|
389
|
-
let runWriteLoop = async (inMemoryStore: t) => {
|
|
390
|
-
while inMemoryStore->hasPendingWrite && !inMemoryStore.hasFailedWrite {
|
|
391
|
-
try {
|
|
392
|
-
await runOneWrite(
|
|
393
|
-
inMemoryStore,
|
|
394
|
-
~persistence=inMemoryStore.persistence,
|
|
395
|
-
~config=inMemoryStore.config,
|
|
396
|
-
)
|
|
397
|
-
inMemoryStore->wakeCommitWaiters
|
|
398
|
-
} catch {
|
|
399
|
-
| exn =>
|
|
400
|
-
inMemoryStore.hasFailedWrite = true
|
|
401
|
-
inMemoryStore.onError(exn)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
inMemoryStore.writeFiber = None
|
|
405
|
-
inMemoryStore->wakeCommitWaiters
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
let kick = (inMemoryStore: t) =>
|
|
409
|
-
if (
|
|
410
|
-
inMemoryStore.writeFiber->Option.isNone &&
|
|
411
|
-
!inMemoryStore.hasFailedWrite &&
|
|
412
|
-
inMemoryStore->hasPendingWrite
|
|
413
|
-
) {
|
|
414
|
-
inMemoryStore.writeFiber = Some(runWriteLoop(inMemoryStore))
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
let metaFieldsEqual = (a: InternalTable.Chains.metaFields, b: InternalTable.Chains.metaFields) =>
|
|
418
|
-
a.firstEventBlockNumber == b.firstEventBlockNumber &&
|
|
419
|
-
a.latestFetchedBlockNumber == b.latestFetchedBlockNumber &&
|
|
420
|
-
a.isHyperSync == b.isHyperSync &&
|
|
421
|
-
// Date is boxed; compare epoch ms.
|
|
422
|
-
a.timestampCaughtUpToHeadOrEndblock->Null.toOption->Option.map(Date.getTime) ==
|
|
423
|
-
b.timestampCaughtUpToHeadOrEndblock->Null.toOption->Option.map(Date.getTime)
|
|
424
|
-
|
|
425
|
-
// Stages chain metadata, dirtying only on a real change so restages are no-ops.
|
|
426
|
-
let setChainMeta = (inMemoryStore: t, chainsData: dict<InternalTable.Chains.metaFields>) => {
|
|
427
|
-
chainsData->Utils.Dict.forEachWithKey((meta, chainId) => {
|
|
428
|
-
let changed = switch inMemoryStore.chainMeta->Utils.Dict.dangerouslyGetNonOption(chainId) {
|
|
429
|
-
| Some(prev) => !metaFieldsEqual(meta, prev)
|
|
430
|
-
| None => true
|
|
431
|
-
}
|
|
432
|
-
if changed {
|
|
433
|
-
inMemoryStore.chainMeta->Dict.set(chainId, meta)
|
|
434
|
-
inMemoryStore.chainMetaDirty = true
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
|
-
if inMemoryStore.chainMetaDirty {
|
|
438
|
-
inMemoryStore.chainMetaThrottler->Throttler.schedule(() => {
|
|
439
|
-
inMemoryStore->kick
|
|
440
|
-
Promise.resolve()
|
|
441
|
-
})
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Queues a processed batch and kicks the cycle. Returns immediately; the write
|
|
446
|
-
// happens off the processing path.
|
|
447
|
-
let commitBatch = (inMemoryStore: t, ~batch: Batch.t) => {
|
|
448
|
-
inMemoryStore.processedBatches->Array.push(batch)->ignore
|
|
449
|
-
switch batch.checkpointIds->Utils.Array.last {
|
|
450
|
-
| Some(checkpointId) => inMemoryStore.processedCheckpointId = checkpointId
|
|
451
|
-
| None => ()
|
|
452
|
-
}
|
|
453
|
-
inMemoryStore->kick
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Drops committed entity and effect entries across all tables. With
|
|
457
|
-
// keepLoadedFromDb, entries seeded from a db read are spared.
|
|
458
|
-
let dropCommitted = (inMemoryStore: t, ~keepLoadedFromDb) => {
|
|
459
|
-
let committedCheckpointId = inMemoryStore.committedCheckpointId
|
|
460
|
-
inMemoryStore.allEntities->Array.forEach(entityConfig =>
|
|
461
|
-
inMemoryStore
|
|
462
|
-
->getInMemTable(~entityConfig)
|
|
463
|
-
->InMemoryTable.Entity.dropCommittedChanges(~committedCheckpointId, ~keepLoadedFromDb)
|
|
464
|
-
)
|
|
465
|
-
inMemoryStore.effects->Utils.Dict.forEach(inMemTable =>
|
|
466
|
-
inMemTable->dropCommittedEffects(~committedCheckpointId, ~keepLoadedFromDb)
|
|
467
|
-
)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// Blocks until the store holds fewer than keepLatestChangesLimit changes,
|
|
471
|
-
// freeing committed changes first and awaiting commits as a last resort.
|
|
472
|
-
let rec awaitCapacity = async (inMemoryStore: t) => {
|
|
473
|
-
// After a failed write nothing will free capacity, so bail instead of waiting
|
|
474
|
-
// on a commit that won't come (the error already went to onError).
|
|
475
|
-
if !inMemoryStore.hasFailedWrite && inMemoryStore->getChangesCount >= keepLatestChangesLimit {
|
|
476
|
-
// Drop committed writes first, sparing db-loaded entries (explicitly
|
|
477
|
-
// requested, so likelier to be read again).
|
|
478
|
-
inMemoryStore->dropCommitted(~keepLoadedFromDb=true)
|
|
479
|
-
|
|
480
|
-
// Still over: drop the db-loaded entries too.
|
|
481
|
-
if inMemoryStore->getChangesCount >= keepLatestChangesLimit {
|
|
482
|
-
inMemoryStore->dropCommitted(~keepLoadedFromDb=false)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Still over: what's left is uncommitted. Only wait if a queued batch can
|
|
486
|
-
// free it; otherwise (e.g. a large rollback diff with no batch) waiting
|
|
487
|
-
// would deadlock, so let processing proceed.
|
|
488
|
-
if (
|
|
489
|
-
inMemoryStore->getChangesCount >= keepLatestChangesLimit &&
|
|
490
|
-
inMemoryStore.processedBatches->Utils.Array.notEmpty
|
|
491
|
-
) {
|
|
492
|
-
inMemoryStore->kick
|
|
493
|
-
await inMemoryStore->waitForCommit
|
|
494
|
-
await inMemoryStore->awaitCapacity
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Awaits until everything processed is persisted. On a failed write we stop
|
|
500
|
-
// draining (onError already surfaced it) rather than throw.
|
|
501
|
-
let rec flush = async (inMemoryStore: t) => {
|
|
502
|
-
if !inMemoryStore.hasFailedWrite {
|
|
503
|
-
inMemoryStore->kick
|
|
504
|
-
switch inMemoryStore.writeFiber {
|
|
505
|
-
| Some(fiber) =>
|
|
506
|
-
await fiber
|
|
507
|
-
await inMemoryStore->flush
|
|
508
|
-
| None => ()
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
99
|
let prepareRollbackDiff = async (
|
|
514
|
-
|
|
515
|
-
~persistence: Persistence.t,
|
|
100
|
+
state: IndexerState.t,
|
|
516
101
|
~rollbackTargetCheckpointId,
|
|
517
102
|
~rollbackDiffCheckpointId,
|
|
518
103
|
~progressBlockNumberByChainId,
|
|
519
104
|
) => {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
105
|
+
state->IndexerState.beginRollbackDiff(
|
|
106
|
+
~targetCheckpointId=rollbackTargetCheckpointId,
|
|
107
|
+
~diffCheckpointId=rollbackDiffCheckpointId,
|
|
108
|
+
~progressBlockNumberByChainId,
|
|
109
|
+
)
|
|
110
|
+
let persistence = state->IndexerState.persistence
|
|
111
|
+
let committedCheckpointId = state->IndexerState.committedCheckpointId
|
|
527
112
|
|
|
528
113
|
let deletedEntities = Dict.make()
|
|
529
114
|
let setEntities = Dict.make()
|
|
530
115
|
|
|
531
116
|
let _ = await persistence.allEntities
|
|
532
117
|
->Array.map(async entityConfig => {
|
|
533
|
-
let entityTable =
|
|
118
|
+
let entityTable = state->getInMemTable(~entityConfig)
|
|
534
119
|
|
|
535
120
|
let (removedIdsResult, restoredEntitiesResult) = await persistence.storage.getRollbackData(
|
|
536
121
|
~entityConfig,
|
|
@@ -540,7 +125,7 @@ let prepareRollbackDiff = async (
|
|
|
540
125
|
removedIdsResult->Array.forEach(data => {
|
|
541
126
|
deletedEntities->Utils.Dict.push(entityConfig.name, data["id"])
|
|
542
127
|
entityTable->InMemoryTable.Entity.set(
|
|
543
|
-
~committedCheckpointId
|
|
128
|
+
~committedCheckpointId,
|
|
544
129
|
Delete({
|
|
545
130
|
entityId: data["id"],
|
|
546
131
|
checkpointId: rollbackDiffCheckpointId,
|
|
@@ -556,7 +141,7 @@ let prepareRollbackDiff = async (
|
|
|
556
141
|
restoredEntities->Array.forEach((entity: Internal.entity) => {
|
|
557
142
|
setEntities->Utils.Dict.push(entityConfig.name, entity.id)
|
|
558
143
|
entityTable->InMemoryTable.Entity.set(
|
|
559
|
-
~committedCheckpointId
|
|
144
|
+
~committedCheckpointId,
|
|
560
145
|
Set({
|
|
561
146
|
entityId: entity.id,
|
|
562
147
|
checkpointId: rollbackDiffCheckpointId,
|
|
@@ -573,9 +158,9 @@ let prepareRollbackDiff = async (
|
|
|
573
158
|
}
|
|
574
159
|
}
|
|
575
160
|
|
|
576
|
-
let setBatchDcs = (
|
|
577
|
-
let inMemTable =
|
|
578
|
-
|
|
161
|
+
let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
|
|
162
|
+
let inMemTable = state->getInMemTable(~entityConfig=InternalTable.EnvioAddresses.entityConfig)
|
|
163
|
+
let committedCheckpointId = state->IndexerState.committedCheckpointId
|
|
579
164
|
|
|
580
165
|
let itemIdx = ref(0)
|
|
581
166
|
|
|
@@ -602,7 +187,7 @@ let setBatchDcs = (inMemoryStore: t, ~batch: Batch.t) => {
|
|
|
602
187
|
}
|
|
603
188
|
|
|
604
189
|
inMemTable->InMemoryTable.Entity.set(
|
|
605
|
-
~committedCheckpointId
|
|
190
|
+
~committedCheckpointId,
|
|
606
191
|
Set({
|
|
607
192
|
entityId: entity.id,
|
|
608
193
|
checkpointId,
|