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/Writing.res
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// The write loop: drains the processed-batch queue and staged chain metadata to
|
|
2
|
+
// storage off the processing path. A peer to ChainFetching/BatchProcessing/
|
|
3
|
+
// Rollback, but data-driven — `schedule` kicks a single write fiber whenever
|
|
4
|
+
// there's pending work, and processing waits on it through awaitCapacity/flush.
|
|
5
|
+
// State mutations go through IndexerState's domain operations.
|
|
6
|
+
|
|
7
|
+
// Max uncommitted entity/effect changes plus unwritten batch items before
|
|
8
|
+
// processing must wait for the cycle to free capacity.
|
|
9
|
+
let keepLatestChangesLimit = Env.inMemoryObjectsTarget
|
|
10
|
+
|
|
11
|
+
let getChangesCount = (state: IndexerState.t) => {
|
|
12
|
+
let total = ref(0.)
|
|
13
|
+
state
|
|
14
|
+
->IndexerState.allEntities
|
|
15
|
+
->Array.forEach(entityConfig => {
|
|
16
|
+
total := total.contents +. (state->InMemoryStore.getInMemTable(~entityConfig)).changesCount
|
|
17
|
+
})
|
|
18
|
+
state
|
|
19
|
+
->IndexerState.effects
|
|
20
|
+
->Utils.Dict.forEach(inMemTable => {
|
|
21
|
+
total := total.contents +. inMemTable.changesCount
|
|
22
|
+
})
|
|
23
|
+
state
|
|
24
|
+
->IndexerState.processedBatches
|
|
25
|
+
->Array.forEach(batch => {
|
|
26
|
+
total := total.contents +. batch.totalBatchSize->Int.toFloat
|
|
27
|
+
})
|
|
28
|
+
total.contents
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let waitForCommit = (state: IndexerState.t): promise<unit> =>
|
|
32
|
+
Promise.make((resolve, _) => {
|
|
33
|
+
state->IndexerState.addCommitWaiter(resolve)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Captures the cache:true outputs to persist. The dict is left intact — entries
|
|
37
|
+
// stay warm and are reclaimed later by dropCommittedEffects once committed.
|
|
38
|
+
let snapshotEffects = (state: IndexerState.t, ~cache): array<Persistence.updatedEffectCache> => {
|
|
39
|
+
let acc = []
|
|
40
|
+
state
|
|
41
|
+
->IndexerState.effects
|
|
42
|
+
->Utils.Dict.forEach(inMemTable => {
|
|
43
|
+
let {idsToStore, dict, effect, invalidationsCount} = inMemTable
|
|
44
|
+
switch idsToStore {
|
|
45
|
+
| [] => ()
|
|
46
|
+
| ids =>
|
|
47
|
+
let items = ids->Array.filterMap((id): option<Internal.effectCacheItem> =>
|
|
48
|
+
switch dict->Dict.getUnsafe(id) {
|
|
49
|
+
| Set({entity: output}) => Some({id, output})
|
|
50
|
+
| Delete(_) => None
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
let effectName = effect.name
|
|
54
|
+
let effectCacheRecord = switch cache->Utils.Dict.dangerouslyGetNonOption(effectName) {
|
|
55
|
+
| Some(c) => c
|
|
56
|
+
| None =>
|
|
57
|
+
let c: Persistence.effectCacheRecord = {effectName, count: 0}
|
|
58
|
+
cache->Dict.set(effectName, c)
|
|
59
|
+
c
|
|
60
|
+
}
|
|
61
|
+
let shouldInitialize = effectCacheRecord.count === 0
|
|
62
|
+
effectCacheRecord.count = effectCacheRecord.count + items->Array.length - invalidationsCount
|
|
63
|
+
Prometheus.EffectCacheCount.set(~count=effectCacheRecord.count, ~effectName)
|
|
64
|
+
acc->Array.push(({effect, items, shouldInitialize}: Persistence.updatedEffectCache))->ignore
|
|
65
|
+
}
|
|
66
|
+
inMemTable.idsToStore = []
|
|
67
|
+
inMemTable.invalidationsCount = 0
|
|
68
|
+
})
|
|
69
|
+
acc
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let runOneWrite = async (state: IndexerState.t) => {
|
|
73
|
+
let persistence = state->IndexerState.persistence
|
|
74
|
+
let config = state->IndexerState.config
|
|
75
|
+
let cache = switch persistence.storageStatus {
|
|
76
|
+
| Unknown
|
|
77
|
+
| Initializing(_) =>
|
|
78
|
+
JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`)
|
|
79
|
+
| Ready({cache}) => cache
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Snapshot before the await: the batch write reads this later, in-transaction.
|
|
83
|
+
let chainMetaData = state->IndexerState.takeChainMetaSnapshot
|
|
84
|
+
|
|
85
|
+
switch state->IndexerState.processedBatches {
|
|
86
|
+
| [] =>
|
|
87
|
+
// Metadata only: a cheap upsert, still serialized by the single write loop.
|
|
88
|
+
switch chainMetaData {
|
|
89
|
+
| Some(chainsData) =>
|
|
90
|
+
await persistence.storage.setChainMeta(chainsData)->Utils.Promise.ignoreValue
|
|
91
|
+
| None => ()
|
|
92
|
+
}
|
|
93
|
+
| _ =>
|
|
94
|
+
let committedCheckpointId = state->IndexerState.committedCheckpointId
|
|
95
|
+
let batch = state->IndexerState.drainBatchRun
|
|
96
|
+
// The run's last checkpoint; entity changes above it stay queued for the next write.
|
|
97
|
+
let upToCheckpointId = switch batch.checkpointIds->Utils.Array.last {
|
|
98
|
+
| Some(checkpointId) => checkpointId
|
|
99
|
+
| None => committedCheckpointId
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let rollback = state->IndexerState.takeRollback
|
|
103
|
+
|
|
104
|
+
let updatedEntities = persistence.allEntities->Array.filterMap(entityConfig => {
|
|
105
|
+
let table = state->InMemoryStore.getInMemTable(~entityConfig)
|
|
106
|
+
let changes =
|
|
107
|
+
table->InMemoryTable.Entity.snapshotChanges(~committedCheckpointId, ~upToCheckpointId)
|
|
108
|
+
if changes->Utils.Array.isEmpty {
|
|
109
|
+
None
|
|
110
|
+
} else {
|
|
111
|
+
Some(({entityConfig, changes}: Persistence.updatedEntity))
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
let updatedEffectsCache = snapshotEffects(state, ~cache)
|
|
115
|
+
|
|
116
|
+
await persistence.storage.writeBatch(
|
|
117
|
+
~batch,
|
|
118
|
+
~rollback,
|
|
119
|
+
~isInReorgThreshold=batch.isInReorgThreshold,
|
|
120
|
+
~config,
|
|
121
|
+
~allEntities=persistence.allEntities,
|
|
122
|
+
~updatedEntities,
|
|
123
|
+
~updatedEffectsCache,
|
|
124
|
+
~chainMetaData,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
state->IndexerState.markCommitted(~upToCheckpointId)
|
|
128
|
+
|
|
129
|
+
switch rollback {
|
|
130
|
+
| Some({progressBlockNumberByChainId}) if RollbackCommit.callbacks->Utils.Array.notEmpty =>
|
|
131
|
+
await RollbackCommit.fire(~progressBlockNumberByChainId)
|
|
132
|
+
| _ => ()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let hasPendingWrite = (state: IndexerState.t) =>
|
|
138
|
+
state->IndexerState.processedBatches->Utils.Array.notEmpty || state->IndexerState.chainMetaDirty
|
|
139
|
+
|
|
140
|
+
let runWriteLoop = async (state: IndexerState.t) => {
|
|
141
|
+
while state->hasPendingWrite && !(state->IndexerState.hasFailedWrite) {
|
|
142
|
+
try {
|
|
143
|
+
await runOneWrite(state)
|
|
144
|
+
state->IndexerState.wakeCommitWaiters
|
|
145
|
+
} catch {
|
|
146
|
+
| exn => state->IndexerState.recordWriteFailure(exn)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
state->IndexerState.endWriteFiber
|
|
150
|
+
state->IndexerState.wakeCommitWaiters
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Kicks the single write fiber if there's pending work and one isn't running.
|
|
154
|
+
let schedule = (state: IndexerState.t) =>
|
|
155
|
+
if (
|
|
156
|
+
state->IndexerState.writeFiber->Option.isNone &&
|
|
157
|
+
!(state->IndexerState.hasFailedWrite) &&
|
|
158
|
+
state->hasPendingWrite
|
|
159
|
+
) {
|
|
160
|
+
state->IndexerState.beginWriteFiber(runWriteLoop(state))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Stages chain metadata and throttles a metadata-only write when no batches flow.
|
|
164
|
+
let setChainMeta = (state: IndexerState.t, chainsData: dict<InternalTable.Chains.metaFields>) => {
|
|
165
|
+
state->IndexerState.stageChainMeta(chainsData)
|
|
166
|
+
if state->IndexerState.chainMetaDirty {
|
|
167
|
+
state
|
|
168
|
+
->IndexerState.chainMetaThrottler
|
|
169
|
+
->Throttler.schedule(() => {
|
|
170
|
+
state->schedule
|
|
171
|
+
Promise.resolve()
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Queues a processed batch and kicks the cycle. Returns immediately; the write
|
|
177
|
+
// happens off the processing path.
|
|
178
|
+
let commitBatch = (state: IndexerState.t, ~batch: Batch.t) => {
|
|
179
|
+
state->IndexerState.queueProcessedBatch(~batch)
|
|
180
|
+
state->schedule
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Drops committed entity and effect entries across all tables. With
|
|
184
|
+
// keepLoadedFromDb, entries seeded from a db read are spared.
|
|
185
|
+
let dropCommitted = (state: IndexerState.t, ~keepLoadedFromDb) => {
|
|
186
|
+
let committedCheckpointId = state->IndexerState.committedCheckpointId
|
|
187
|
+
state
|
|
188
|
+
->IndexerState.allEntities
|
|
189
|
+
->Array.forEach(entityConfig =>
|
|
190
|
+
state
|
|
191
|
+
->InMemoryStore.getInMemTable(~entityConfig)
|
|
192
|
+
->InMemoryTable.Entity.dropCommittedChanges(~committedCheckpointId, ~keepLoadedFromDb)
|
|
193
|
+
)
|
|
194
|
+
state
|
|
195
|
+
->IndexerState.effects
|
|
196
|
+
->Utils.Dict.forEach(inMemTable =>
|
|
197
|
+
inMemTable->InMemoryStore.dropCommittedEffects(~committedCheckpointId, ~keepLoadedFromDb)
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Blocks until the store holds fewer than keepLatestChangesLimit changes,
|
|
202
|
+
// freeing committed changes first and awaiting commits as a last resort.
|
|
203
|
+
let rec awaitCapacity = async (state: IndexerState.t) => {
|
|
204
|
+
// After a failed write nothing will free capacity, so bail instead of waiting
|
|
205
|
+
// on a commit that won't come (the error already went to onError).
|
|
206
|
+
if !(state->IndexerState.hasFailedWrite) && state->getChangesCount >= keepLatestChangesLimit {
|
|
207
|
+
// Drop committed writes first, sparing db-loaded entries (explicitly
|
|
208
|
+
// requested, so likelier to be read again).
|
|
209
|
+
state->dropCommitted(~keepLoadedFromDb=true)
|
|
210
|
+
|
|
211
|
+
// Still over: drop the db-loaded entries too.
|
|
212
|
+
if state->getChangesCount >= keepLatestChangesLimit {
|
|
213
|
+
state->dropCommitted(~keepLoadedFromDb=false)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Still over: what's left is uncommitted. Only wait if a queued batch can
|
|
217
|
+
// free it; otherwise (e.g. a large rollback diff with no batch) waiting
|
|
218
|
+
// would deadlock, so let processing proceed.
|
|
219
|
+
if (
|
|
220
|
+
state->getChangesCount >= keepLatestChangesLimit &&
|
|
221
|
+
state->IndexerState.processedBatches->Utils.Array.notEmpty
|
|
222
|
+
) {
|
|
223
|
+
state->schedule
|
|
224
|
+
await state->waitForCommit
|
|
225
|
+
await state->awaitCapacity
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Awaits until everything processed is persisted. On a failed write we stop
|
|
231
|
+
// draining (onError already surfaced it) rather than throw.
|
|
232
|
+
let rec flush = async (state: IndexerState.t) => {
|
|
233
|
+
if !(state->IndexerState.hasFailedWrite) {
|
|
234
|
+
state->schedule
|
|
235
|
+
switch state->IndexerState.writeFiber {
|
|
236
|
+
| Some(fiber) =>
|
|
237
|
+
await fiber
|
|
238
|
+
await state->flush
|
|
239
|
+
| None => ()
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Env from "./Env.res.mjs";
|
|
4
|
+
import * as Utils from "./Utils.res.mjs";
|
|
5
|
+
import * as Throttler from "./Throttler.res.mjs";
|
|
6
|
+
import * as Prometheus from "./Prometheus.res.mjs";
|
|
7
|
+
import * as IndexerState from "./IndexerState.res.mjs";
|
|
8
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
9
|
+
import * as InMemoryStore from "./InMemoryStore.res.mjs";
|
|
10
|
+
import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
11
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
12
|
+
import * as RollbackCommit from "./RollbackCommit.res.mjs";
|
|
13
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
14
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
15
|
+
|
|
16
|
+
function getChangesCount(state) {
|
|
17
|
+
let total = {
|
|
18
|
+
contents: 0
|
|
19
|
+
};
|
|
20
|
+
IndexerState.allEntities(state).forEach(entityConfig => {
|
|
21
|
+
total.contents = total.contents + InMemoryStore.getInMemTable(state, entityConfig).changesCount;
|
|
22
|
+
});
|
|
23
|
+
Utils.Dict.forEach(IndexerState.effects(state), inMemTable => {
|
|
24
|
+
total.contents = total.contents + inMemTable.changesCount;
|
|
25
|
+
});
|
|
26
|
+
IndexerState.processedBatches(state).forEach(batch => {
|
|
27
|
+
total.contents = total.contents + batch.totalBatchSize;
|
|
28
|
+
});
|
|
29
|
+
return total.contents;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function waitForCommit(state) {
|
|
33
|
+
return new Promise((resolve, param) => IndexerState.addCommitWaiter(state, resolve));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function snapshotEffects(state, cache) {
|
|
37
|
+
let acc = [];
|
|
38
|
+
Utils.Dict.forEach(IndexerState.effects(state), inMemTable => {
|
|
39
|
+
let idsToStore = inMemTable.idsToStore;
|
|
40
|
+
let invalidationsCount = inMemTable.invalidationsCount;
|
|
41
|
+
let dict = inMemTable.dict;
|
|
42
|
+
let effect = inMemTable.effect;
|
|
43
|
+
if (idsToStore.length !== 0) {
|
|
44
|
+
let items = Stdlib_Array.filterMap(idsToStore, id => {
|
|
45
|
+
let match = dict[id];
|
|
46
|
+
if (match.type === "SET") {
|
|
47
|
+
return {
|
|
48
|
+
id: id,
|
|
49
|
+
output: match.entity
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
let effectName = effect.name;
|
|
54
|
+
let c = cache[effectName];
|
|
55
|
+
let effectCacheRecord;
|
|
56
|
+
if (c !== undefined) {
|
|
57
|
+
effectCacheRecord = c;
|
|
58
|
+
} else {
|
|
59
|
+
let c$1 = {
|
|
60
|
+
effectName: effectName,
|
|
61
|
+
count: 0
|
|
62
|
+
};
|
|
63
|
+
cache[effectName] = c$1;
|
|
64
|
+
effectCacheRecord = c$1;
|
|
65
|
+
}
|
|
66
|
+
let shouldInitialize = effectCacheRecord.count === 0;
|
|
67
|
+
effectCacheRecord.count = (effectCacheRecord.count + items.length | 0) - invalidationsCount | 0;
|
|
68
|
+
Prometheus.EffectCacheCount.set(effectCacheRecord.count, effectName);
|
|
69
|
+
acc.push({
|
|
70
|
+
effect: effect,
|
|
71
|
+
items: items,
|
|
72
|
+
shouldInitialize: shouldInitialize
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
inMemTable.idsToStore = [];
|
|
76
|
+
inMemTable.invalidationsCount = 0;
|
|
77
|
+
});
|
|
78
|
+
return acc;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function runOneWrite(state) {
|
|
82
|
+
let persistence = IndexerState.persistence(state);
|
|
83
|
+
let config = IndexerState.config(state);
|
|
84
|
+
let match = persistence.storageStatus;
|
|
85
|
+
let cache;
|
|
86
|
+
cache = typeof match !== "object" || match.TAG === "Initializing" ? Stdlib_JsError.throwWithMessage(`Failed to access the indexer storage. The Persistence layer is not initialized.`) : match._0.cache;
|
|
87
|
+
let chainMetaData = IndexerState.takeChainMetaSnapshot(state);
|
|
88
|
+
let match$1 = IndexerState.processedBatches(state);
|
|
89
|
+
if (match$1.length === 0) {
|
|
90
|
+
if (chainMetaData !== undefined) {
|
|
91
|
+
return await persistence.storage.setChainMeta(chainMetaData);
|
|
92
|
+
} else {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let committedCheckpointId = IndexerState.committedCheckpointId(state);
|
|
97
|
+
let batch = IndexerState.drainBatchRun(state);
|
|
98
|
+
let checkpointId = Utils.$$Array.last(batch.checkpointIds);
|
|
99
|
+
let upToCheckpointId = checkpointId !== undefined ? checkpointId : committedCheckpointId;
|
|
100
|
+
let rollback = IndexerState.takeRollback(state);
|
|
101
|
+
let updatedEntities = Stdlib_Array.filterMap(persistence.allEntities, entityConfig => {
|
|
102
|
+
let table = InMemoryStore.getInMemTable(state, entityConfig);
|
|
103
|
+
let changes = InMemoryTable.Entity.snapshotChanges(table, committedCheckpointId, upToCheckpointId);
|
|
104
|
+
if (Utils.$$Array.isEmpty(changes)) {
|
|
105
|
+
return;
|
|
106
|
+
} else {
|
|
107
|
+
return {
|
|
108
|
+
entityConfig: entityConfig,
|
|
109
|
+
changes: changes
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
let updatedEffectsCache = snapshotEffects(state, cache);
|
|
114
|
+
await persistence.storage.writeBatch(batch, rollback, batch.isInReorgThreshold, config, persistence.allEntities, updatedEffectsCache, updatedEntities, chainMetaData);
|
|
115
|
+
IndexerState.markCommitted(state, upToCheckpointId);
|
|
116
|
+
if (rollback !== undefined && Utils.$$Array.notEmpty(RollbackCommit.callbacks)) {
|
|
117
|
+
return await RollbackCommit.fire(rollback.progressBlockNumberByChainId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function hasPendingWrite(state) {
|
|
122
|
+
if (Utils.$$Array.notEmpty(IndexerState.processedBatches(state))) {
|
|
123
|
+
return true;
|
|
124
|
+
} else {
|
|
125
|
+
return IndexerState.chainMetaDirty(state);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function runWriteLoop(state) {
|
|
130
|
+
while (hasPendingWrite(state) && !IndexerState.hasFailedWrite(state)) {
|
|
131
|
+
try {
|
|
132
|
+
await runOneWrite(state);
|
|
133
|
+
IndexerState.wakeCommitWaiters(state);
|
|
134
|
+
} catch (raw_exn) {
|
|
135
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
136
|
+
IndexerState.recordWriteFailure(state, exn);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
IndexerState.endWriteFiber(state);
|
|
140
|
+
return IndexerState.wakeCommitWaiters(state);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function schedule(state) {
|
|
144
|
+
if (Stdlib_Option.isNone(IndexerState.writeFiber(state)) && !IndexerState.hasFailedWrite(state) && hasPendingWrite(state)) {
|
|
145
|
+
return IndexerState.beginWriteFiber(state, runWriteLoop(state));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function setChainMeta(state, chainsData) {
|
|
150
|
+
IndexerState.stageChainMeta(state, chainsData);
|
|
151
|
+
if (IndexerState.chainMetaDirty(state)) {
|
|
152
|
+
return Throttler.schedule(IndexerState.chainMetaThrottler(state), () => {
|
|
153
|
+
schedule(state);
|
|
154
|
+
return Promise.resolve();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function commitBatch(state, batch) {
|
|
160
|
+
IndexerState.queueProcessedBatch(state, batch);
|
|
161
|
+
schedule(state);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function dropCommitted(state, keepLoadedFromDb) {
|
|
165
|
+
let committedCheckpointId = IndexerState.committedCheckpointId(state);
|
|
166
|
+
IndexerState.allEntities(state).forEach(entityConfig => InMemoryTable.Entity.dropCommittedChanges(InMemoryStore.getInMemTable(state, entityConfig), committedCheckpointId, keepLoadedFromDb));
|
|
167
|
+
Utils.Dict.forEach(IndexerState.effects(state), inMemTable => InMemoryStore.dropCommittedEffects(inMemTable, committedCheckpointId, keepLoadedFromDb));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function awaitCapacity(state) {
|
|
171
|
+
if (!IndexerState.hasFailedWrite(state) && getChangesCount(state) >= Env.inMemoryObjectsTarget) {
|
|
172
|
+
dropCommitted(state, true);
|
|
173
|
+
if (getChangesCount(state) >= Env.inMemoryObjectsTarget) {
|
|
174
|
+
dropCommitted(state, false);
|
|
175
|
+
}
|
|
176
|
+
if (getChangesCount(state) >= Env.inMemoryObjectsTarget && Utils.$$Array.notEmpty(IndexerState.processedBatches(state))) {
|
|
177
|
+
schedule(state);
|
|
178
|
+
await new Promise((resolve, param) => IndexerState.addCommitWaiter(state, resolve));
|
|
179
|
+
return await awaitCapacity(state);
|
|
180
|
+
} else {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function flush(state) {
|
|
187
|
+
if (IndexerState.hasFailedWrite(state)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
schedule(state);
|
|
191
|
+
let fiber = IndexerState.writeFiber(state);
|
|
192
|
+
if (fiber !== undefined) {
|
|
193
|
+
await fiber;
|
|
194
|
+
return await flush(state);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let keepLatestChangesLimit = Env.inMemoryObjectsTarget;
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
keepLatestChangesLimit,
|
|
202
|
+
getChangesCount,
|
|
203
|
+
waitForCommit,
|
|
204
|
+
snapshotEffects,
|
|
205
|
+
runOneWrite,
|
|
206
|
+
hasPendingWrite,
|
|
207
|
+
runWriteLoop,
|
|
208
|
+
schedule,
|
|
209
|
+
setChainMeta,
|
|
210
|
+
commitBatch,
|
|
211
|
+
dropCommitted,
|
|
212
|
+
awaitCapacity,
|
|
213
|
+
flush,
|
|
214
|
+
}
|
|
215
|
+
/* Env Not a pure module */
|
package/src/db/InternalTable.res
CHANGED
|
@@ -571,33 +571,20 @@ GROUP BY "${(#chain_id: field :> string)}";`
|
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
module RawEvents = {
|
|
574
|
-
type t =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
let schema = S.schema(s => {
|
|
590
|
-
chainId: s.matches(S.int),
|
|
591
|
-
eventId: s.matches(S.bigint),
|
|
592
|
-
eventName: s.matches(S.string),
|
|
593
|
-
contractName: s.matches(S.string),
|
|
594
|
-
blockNumber: s.matches(S.int),
|
|
595
|
-
logIndex: s.matches(S.int),
|
|
596
|
-
srcAddress: s.matches(Address.schema),
|
|
597
|
-
blockHash: s.matches(S.string),
|
|
598
|
-
blockTimestamp: s.matches(S.int),
|
|
599
|
-
blockFields: s.matches(S.json(~validate=false)),
|
|
600
|
-
transactionFields: s.matches(S.json(~validate=false)),
|
|
574
|
+
type t = Internal.rawEvent
|
|
575
|
+
|
|
576
|
+
let schema = S.schema((s): t => {
|
|
577
|
+
chain_id: s.matches(S.int),
|
|
578
|
+
event_id: s.matches(S.bigint),
|
|
579
|
+
event_name: s.matches(S.string),
|
|
580
|
+
contract_name: s.matches(S.string),
|
|
581
|
+
block_number: s.matches(S.int),
|
|
582
|
+
log_index: s.matches(S.int),
|
|
583
|
+
src_address: s.matches(Address.schema),
|
|
584
|
+
block_hash: s.matches(S.string),
|
|
585
|
+
block_timestamp: s.matches(S.int),
|
|
586
|
+
block_fields: s.matches(S.json(~validate=false)),
|
|
587
|
+
transaction_fields: s.matches(S.json(~validate=false)),
|
|
601
588
|
params: s.matches(S.json(~validate=false)),
|
|
602
589
|
})
|
|
603
590
|
|
package/src/sources/Evm.res
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
+
// EVM's concrete item payload. Erased to `Internal.eventPayload` on the item
|
|
2
|
+
// and recovered here via `toPayload`.
|
|
3
|
+
type payload = {
|
|
4
|
+
contractName: string,
|
|
5
|
+
eventName: string,
|
|
6
|
+
params: Internal.eventParams,
|
|
7
|
+
chainId: int,
|
|
8
|
+
srcAddress: Address.t,
|
|
9
|
+
logIndex: int,
|
|
10
|
+
transaction: Internal.eventTransaction,
|
|
11
|
+
block: Internal.eventBlock,
|
|
12
|
+
}
|
|
13
|
+
external fromPayload: payload => Internal.eventPayload = "%identity"
|
|
14
|
+
external toPayload: Internal.eventPayload => payload = "%identity"
|
|
15
|
+
|
|
1
16
|
let cleanUpRawEventFieldsInPlace: JSON.t => unit = %raw(`fields => {
|
|
2
17
|
delete fields.hash
|
|
3
18
|
delete fields.number
|
|
4
19
|
delete fields.timestamp
|
|
5
20
|
}`)
|
|
6
21
|
|
|
7
|
-
let
|
|
22
|
+
let make = (~logger: Pino.t): Ecosystem.t => {
|
|
8
23
|
name: Evm,
|
|
9
24
|
blockFields: [
|
|
10
25
|
"number",
|
|
@@ -90,4 +105,28 @@ let ecosystem: Ecosystem.t = {
|
|
|
90
105
|
onEventBlockFilterSchema: S.object(s =>
|
|
91
106
|
s.field("block", S.option(S.object(s2 => s2.field("number", S.unknown))))
|
|
92
107
|
),
|
|
108
|
+
logger,
|
|
109
|
+
toEvent: eventItem => eventItem.payload->Internal.payloadToEvent,
|
|
110
|
+
toEventLogger: eventItem =>
|
|
111
|
+
Logging.createChildFrom(
|
|
112
|
+
~logger,
|
|
113
|
+
~params={
|
|
114
|
+
"contract": eventItem.eventConfig.contractName,
|
|
115
|
+
"event": eventItem.eventConfig.name,
|
|
116
|
+
"chainId": eventItem.chain->ChainMap.Chain.toChainId,
|
|
117
|
+
"block": eventItem.blockNumber,
|
|
118
|
+
"logIndex": eventItem.logIndex,
|
|
119
|
+
"address": (eventItem.payload->toPayload).srcAddress,
|
|
120
|
+
},
|
|
121
|
+
),
|
|
122
|
+
toRawEvent: eventItem => {
|
|
123
|
+
let payload = eventItem.payload->toPayload
|
|
124
|
+
eventItem->RawEvent.make(
|
|
125
|
+
~block=payload.block,
|
|
126
|
+
~transaction=payload.transaction,
|
|
127
|
+
~params=payload.params,
|
|
128
|
+
~srcAddress=payload.srcAddress,
|
|
129
|
+
~cleanUpRawEventFieldsInPlace,
|
|
130
|
+
)
|
|
131
|
+
},
|
|
93
132
|
}
|