envio 3.5.0 → 3.5.1-svm-alpha.2
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/evm.schema.json +7 -0
- package/fuel.schema.json +7 -0
- package/index.d.ts +33 -10
- package/package.json +6 -6
- package/src/ChainFetching.res +11 -21
- package/src/ChainFetching.res.mjs +12 -20
- package/src/ChainState.res +41 -4
- package/src/ChainState.res.mjs +39 -5
- package/src/ChainState.resi +10 -1
- package/src/Config.res +62 -3
- package/src/Config.res.mjs +49 -13
- package/src/EffectState.res +2 -2
- package/src/EffectState.res.mjs +2 -2
- package/src/EntityTables.res +32 -0
- package/src/EntityTables.res.mjs +44 -0
- package/src/Envio.res +7 -7
- package/src/Envio.res.mjs +5 -6
- package/src/FetchState.res +38 -75
- package/src/FetchState.res.mjs +20 -62
- package/src/Hasura.res +37 -3
- package/src/Hasura.res.mjs +23 -5
- package/src/InMemoryStore.res +95 -43
- package/src/InMemoryStore.res.mjs +69 -38
- package/src/IndexerState.res +30 -24
- package/src/IndexerState.res.mjs +20 -35
- package/src/IndexerState.resi +8 -6
- package/src/Internal.res +29 -18
- package/src/Internal.res.mjs +27 -10
- package/src/LoadLayer.res +39 -8
- package/src/LoadLayer.res.mjs +36 -9
- package/src/LoadLayer.resi +2 -0
- package/src/Persistence.res +12 -1
- package/src/PgStorage.res +183 -37
- package/src/PgStorage.res.mjs +149 -34
- package/src/PruneStaleHistory.res +1 -0
- package/src/PruneStaleHistory.res.mjs +2 -1
- package/src/Sink.res +3 -3
- package/src/Sink.res.mjs +2 -2
- package/src/TestIndexer.res +128 -17
- package/src/TestIndexer.res.mjs +75 -9
- package/src/UserContext.res +19 -4
- package/src/UserContext.res.mjs +15 -9
- package/src/Writing.res +8 -16
- package/src/Writing.res.mjs +10 -10
- package/src/bindings/ClickHouse.res +40 -4
- package/src/bindings/ClickHouse.res.mjs +37 -5
- package/src/bindings/Vitest.res +0 -3
- package/src/bindings/Vitest.res.mjs +2 -12
- package/src/db/EntityHistory.res +64 -21
- package/src/db/EntityHistory.res.mjs +43 -22
- package/src/db/InternalTable.res.mjs +31 -31
- package/src/db/Table.res +24 -1
- package/src/db/Table.res.mjs +26 -4
- package/src/sources/AddressStore.res +77 -26
- package/src/sources/AddressStore.res.mjs +21 -10
- package/src/sources/SvmHyperSyncClient.res +8 -7
- package/src/sources/SvmHyperSyncSource.res +38 -1
- package/src/sources/SvmHyperSyncSource.res.mjs +24 -0
- package/svm.schema.json +17 -4
package/src/InMemoryStore.res
CHANGED
|
@@ -2,11 +2,45 @@
|
|
|
2
2
|
// mutations route through IndexerState's domain operations; the write loop and
|
|
3
3
|
// capacity/flush coordination live in Writing.
|
|
4
4
|
|
|
5
|
+
// The scope must match the entity's own: a cross-chain entity has one table on
|
|
6
|
+
// the indexer, a per-chain entity one table per ChainState. Taking the scope
|
|
7
|
+
// rather than an optional chain id keeps a dummy chain id unrepresentable.
|
|
5
8
|
let getInMemTable = (
|
|
6
9
|
state: IndexerState.t,
|
|
7
10
|
~entityConfig: Internal.entityConfig,
|
|
11
|
+
~scope: Internal.chainScope,
|
|
8
12
|
): InMemoryTable.Entity.t =>
|
|
9
|
-
|
|
13
|
+
switch scope {
|
|
14
|
+
| CrossChain => state->IndexerState.entities
|
|
15
|
+
| Chain(chainId) => state->IndexerState.getChainState(~chainId)->ChainState.entities
|
|
16
|
+
}->EntityTables.get(~entityName=entityConfig.name)
|
|
17
|
+
|
|
18
|
+
// The scope a given entity's rows live in when reached from a handler running
|
|
19
|
+
// on `chainId`.
|
|
20
|
+
let entityScope = (entityConfig: Internal.entityConfig, ~chainId): Internal.chainScope =>
|
|
21
|
+
entityConfig.crossChain ? CrossChain : Chain(chainId)
|
|
22
|
+
|
|
23
|
+
// The chain a row loaded from storage belongs to, taken off the row so what's
|
|
24
|
+
// left matches the entity schema the handlers see. A per-chain entity whose row
|
|
25
|
+
// carries no chain id would silently land in the wrong partition, so it throws.
|
|
26
|
+
let takeRowScope = (
|
|
27
|
+
entity: Internal.entity,
|
|
28
|
+
~entityConfig: Internal.entityConfig,
|
|
29
|
+
): Internal.chainScope =>
|
|
30
|
+
switch entityConfig.table->Table.getChainIdField {
|
|
31
|
+
| None => CrossChain
|
|
32
|
+
| Some(field) =>
|
|
33
|
+
let row = entity->(Utils.magic: Internal.entity => dict<ChainId.t>)
|
|
34
|
+
switch row->Utils.Dict.dangerouslyGetNonOption(field.fieldName) {
|
|
35
|
+
| Some(chainId) =>
|
|
36
|
+
row->Utils.Dict.deleteInPlace(field.fieldName)
|
|
37
|
+
Chain(chainId)
|
|
38
|
+
| None =>
|
|
39
|
+
JsError.throwWithMessage(
|
|
40
|
+
`Rollback row for the per-chain entity "${entityConfig.name}" with id "${entity.id}" is missing its "${field.fieldName}" value.`,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
10
44
|
|
|
11
45
|
let getEffectInMemTable = (
|
|
12
46
|
state: IndexerState.t,
|
|
@@ -114,16 +148,16 @@ let prepareRollbackDiff = async (
|
|
|
114
148
|
let _ = await persistence.allEntities
|
|
115
149
|
->Array.filter(entityConfig => entityConfig.storage.postgres)
|
|
116
150
|
->Array.map(async entityConfig => {
|
|
117
|
-
let
|
|
118
|
-
|
|
119
|
-
let (removedIds, restoredEntitiesResult) = await persistence.storage.getRollbackData(
|
|
151
|
+
let (removals, restoredEntitiesResult) = await persistence.storage.getRollbackData(
|
|
120
152
|
~entityConfig,
|
|
121
153
|
~rollbackTargetCheckpointId,
|
|
122
154
|
)
|
|
123
155
|
|
|
124
|
-
|
|
156
|
+
removals->Array.forEach(({entityId, scope}: Persistence.rollbackRemoval) => {
|
|
125
157
|
deletedEntities->Utils.Dict.push(entityConfig.name, entityId)
|
|
126
|
-
|
|
158
|
+
state
|
|
159
|
+
->getInMemTable(~entityConfig, ~scope)
|
|
160
|
+
->InMemoryTable.Entity.set(
|
|
127
161
|
~committedCheckpointId,
|
|
128
162
|
Delete({
|
|
129
163
|
entityId,
|
|
@@ -138,8 +172,11 @@ let prepareRollbackDiff = async (
|
|
|
138
172
|
->(Utils.magic: array<unknown> => array<Internal.entity>)
|
|
139
173
|
|
|
140
174
|
restoredEntities->Array.forEach((entity: Internal.entity) => {
|
|
175
|
+
let scope = entity->takeRowScope(~entityConfig)
|
|
141
176
|
setEntities->Utils.Dict.push(entityConfig.name, entity.id)
|
|
142
|
-
|
|
177
|
+
state
|
|
178
|
+
->getInMemTable(~entityConfig, ~scope)
|
|
179
|
+
->InMemoryTable.Entity.set(
|
|
143
180
|
~committedCheckpointId,
|
|
144
181
|
Set({
|
|
145
182
|
entityId: entity.id->EntityId.unsafeOfString,
|
|
@@ -157,46 +194,61 @@ let prepareRollbackDiff = async (
|
|
|
157
194
|
}
|
|
158
195
|
}
|
|
159
196
|
|
|
197
|
+
// Stages the addresses registered by this batch's events for the write. They're
|
|
198
|
+
// drained from each chain's address store rather than carried on the items that
|
|
199
|
+
// registered them: the store is where they already live, and it knows which
|
|
200
|
+
// ones the database hasn't seen yet.
|
|
160
201
|
let setBatchDcs = (state: IndexerState.t, ~batch: Batch.t) => {
|
|
161
|
-
let inMemTable = state->getInMemTable(
|
|
202
|
+
let inMemTable = state->getInMemTable(
|
|
203
|
+
~entityConfig=InternalTable.EnvioAddresses.entityConfig,
|
|
204
|
+
~scope=CrossChain,
|
|
205
|
+
)
|
|
162
206
|
let committedCheckpointId = state->IndexerState.committedCheckpointId
|
|
163
207
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
let entity: InternalTable.EnvioAddresses.t = {
|
|
181
|
-
id: InternalTable.EnvioAddresses.makeId(~chainId, ~address=dc.address),
|
|
182
|
-
chainId,
|
|
183
|
-
contractName: dc.contractName,
|
|
184
|
-
registrationBlock: eventItem.blockNumber,
|
|
185
|
-
registrationLogIndex: eventItem.logIndex,
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
inMemTable->InMemoryTable.Entity.set(
|
|
189
|
-
~committedCheckpointId,
|
|
190
|
-
Set({
|
|
191
|
-
entityId: entity.id->EntityId.unsafeOfString,
|
|
192
|
-
checkpointId,
|
|
193
|
-
entity: entity->InternalTable.EnvioAddresses.castToInternal,
|
|
194
|
-
}),
|
|
195
|
-
)
|
|
208
|
+
batch.progressedChainsById->Utils.Dict.forEach(progressedChain => {
|
|
209
|
+
let chainId = progressedChain.fetchState.chainId
|
|
210
|
+
let chainState = state->IndexerState.getChainState(~chainId)
|
|
211
|
+
|
|
212
|
+
// Most batches register nothing, so the checkpoints are only collected for a
|
|
213
|
+
// chain that has something waiting.
|
|
214
|
+
if chainState->ChainState.hasAddressesToWrite {
|
|
215
|
+
// The store pairs each drained registration with a checkpoint of its own
|
|
216
|
+
// chain, so only this chain's checkpoints go in — and the index it returns
|
|
217
|
+
// points back into these two parallel arrays.
|
|
218
|
+
let checkpointIds = []
|
|
219
|
+
let checkpointBlockNumbers = []
|
|
220
|
+
for idx in 0 to batch.checkpointIds->Array.length - 1 {
|
|
221
|
+
if batch.checkpointChainIds->Array.getUnsafe(idx) === chainId {
|
|
222
|
+
checkpointIds->Array.push(batch.checkpointIds->Array.getUnsafe(idx))
|
|
223
|
+
checkpointBlockNumbers->Array.push(batch.checkpointBlockNumbers->Array.getUnsafe(idx))
|
|
196
224
|
}
|
|
197
225
|
}
|
|
198
|
-
}
|
|
199
226
|
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
chainState
|
|
228
|
+
->ChainState.drainAddressesForWrite(
|
|
229
|
+
~toBlockInclusive=progressedChain.progressBlockNumber,
|
|
230
|
+
~checkpointBlockNumbers,
|
|
231
|
+
)
|
|
232
|
+
->Array.forEach(dc => {
|
|
233
|
+
let entity: InternalTable.EnvioAddresses.t = {
|
|
234
|
+
id: InternalTable.EnvioAddresses.makeId(~chainId, ~address=dc.address),
|
|
235
|
+
chainId,
|
|
236
|
+
contractName: dc.contractName,
|
|
237
|
+
registrationBlock: dc.registrationBlock,
|
|
238
|
+
// Only ever written, never read back. Kept on the table so the column
|
|
239
|
+
// doesn't need a migration.
|
|
240
|
+
registrationLogIndex: -1,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
inMemTable->InMemoryTable.Entity.set(
|
|
244
|
+
~committedCheckpointId,
|
|
245
|
+
Set({
|
|
246
|
+
entityId: entity.id->EntityId.unsafeOfString,
|
|
247
|
+
checkpointId: checkpointIds->Array.getUnsafe(dc.checkpointIdx),
|
|
248
|
+
entity: entity->InternalTable.EnvioAddresses.castToInternal,
|
|
249
|
+
}),
|
|
250
|
+
)
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
})
|
|
202
254
|
}
|
|
@@ -4,14 +4,42 @@ import * as Table from "./db/Table.res.mjs";
|
|
|
4
4
|
import * as Utils from "./Utils.res.mjs";
|
|
5
5
|
import * as Config from "./Config.res.mjs";
|
|
6
6
|
import * as Internal from "./Internal.res.mjs";
|
|
7
|
+
import * as ChainState from "./ChainState.res.mjs";
|
|
7
8
|
import * as EffectState from "./EffectState.res.mjs";
|
|
9
|
+
import * as EntityTables from "./EntityTables.res.mjs";
|
|
8
10
|
import * as IndexerState from "./IndexerState.res.mjs";
|
|
9
11
|
import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
10
12
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
13
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
14
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
11
15
|
import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
|
|
12
16
|
|
|
13
|
-
function getInMemTable(state, entityConfig) {
|
|
14
|
-
|
|
17
|
+
function getInMemTable(state, entityConfig, scope) {
|
|
18
|
+
let tmp;
|
|
19
|
+
tmp = scope === "crossChain" ? IndexerState.entities(state) : ChainState.entities(IndexerState.getChainState(state, scope));
|
|
20
|
+
return EntityTables.get(tmp, entityConfig.name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function entityScope(entityConfig, chainId) {
|
|
24
|
+
if (entityConfig.crossChain) {
|
|
25
|
+
return "crossChain";
|
|
26
|
+
} else {
|
|
27
|
+
return chainId;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function takeRowScope(entity, entityConfig) {
|
|
32
|
+
let field = Table.getChainIdField(entityConfig.table);
|
|
33
|
+
if (field === undefined) {
|
|
34
|
+
return "crossChain";
|
|
35
|
+
}
|
|
36
|
+
let chainId = entity[field.fieldName];
|
|
37
|
+
if (chainId !== undefined) {
|
|
38
|
+
Utils.Dict.deleteInPlace(entity, field.fieldName);
|
|
39
|
+
return Primitive_option.valFromOption(chainId);
|
|
40
|
+
} else {
|
|
41
|
+
return Stdlib_JsError.throwWithMessage(`Rollback row for the per-chain entity "` + entityConfig.name + `" with id "` + entity.id + `" is missing its "` + field.fieldName + `" value.`);
|
|
42
|
+
}
|
|
15
43
|
}
|
|
16
44
|
|
|
17
45
|
function getEffectInMemTable(state, effect, scope) {
|
|
@@ -87,11 +115,11 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
|
|
|
87
115
|
let deletedEntities = {};
|
|
88
116
|
let setEntities = {};
|
|
89
117
|
await Promise.all(persistence.allEntities.filter(entityConfig => entityConfig.storage.postgres).map(async entityConfig => {
|
|
90
|
-
let entityTable = getInMemTable(state, entityConfig);
|
|
91
118
|
let match = await persistence.storage.getRollbackData(entityConfig, rollbackTargetCheckpointId);
|
|
92
|
-
match[0].forEach(
|
|
119
|
+
match[0].forEach(param => {
|
|
120
|
+
let entityId = param.entityId;
|
|
93
121
|
Utils.Dict.push(deletedEntities, entityConfig.name, entityId);
|
|
94
|
-
InMemoryTable.Entity.set(
|
|
122
|
+
InMemoryTable.Entity.set(getInMemTable(state, entityConfig, param.scope), committedCheckpointId, {
|
|
95
123
|
type: "DELETE",
|
|
96
124
|
entityId: entityId,
|
|
97
125
|
checkpointId: rollbackDiffCheckpointId
|
|
@@ -99,8 +127,9 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
|
|
|
99
127
|
});
|
|
100
128
|
let restoredEntities = S$RescriptSchema.parseOrThrow(match[1], Table.pgRowsSchema(entityConfig.table));
|
|
101
129
|
restoredEntities.forEach(entity => {
|
|
130
|
+
let scope = takeRowScope(entity, entityConfig);
|
|
102
131
|
Utils.Dict.push(setEntities, entityConfig.name, entity.id);
|
|
103
|
-
InMemoryTable.Entity.set(
|
|
132
|
+
InMemoryTable.Entity.set(getInMemTable(state, entityConfig, scope), committedCheckpointId, {
|
|
104
133
|
type: "SET",
|
|
105
134
|
entityId: entity.id,
|
|
106
135
|
entity: entity,
|
|
@@ -115,45 +144,47 @@ async function prepareRollbackDiff(state, rollbackTargetCheckpointId, rollbackDi
|
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
function setBatchDcs(state, batch) {
|
|
118
|
-
let inMemTable = getInMemTable(state, Config.EnvioAddresses.entityConfig);
|
|
147
|
+
let inMemTable = getInMemTable(state, Config.EnvioAddresses.entityConfig, "crossChain");
|
|
119
148
|
let committedCheckpointId = IndexerState.committedCheckpointId(state);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let entity_registration_block = item.blockNumber;
|
|
133
|
-
let entity_registration_log_index = item.logIndex;
|
|
134
|
-
let entity_contract_name = dc.contractName;
|
|
135
|
-
let entity = {
|
|
136
|
-
id: entity_id,
|
|
137
|
-
chain_id: chainId,
|
|
138
|
-
registration_block: entity_registration_block,
|
|
139
|
-
registration_log_index: entity_registration_log_index,
|
|
140
|
-
contract_name: entity_contract_name
|
|
141
|
-
};
|
|
142
|
-
InMemoryTable.Entity.set(inMemTable, committedCheckpointId, {
|
|
143
|
-
type: "SET",
|
|
144
|
-
entityId: entity_id,
|
|
145
|
-
entity: entity,
|
|
146
|
-
checkpointId: checkpointId
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
+
Utils.Dict.forEach(batch.progressedChainsById, progressedChain => {
|
|
150
|
+
let chainId = progressedChain.fetchState.chainId;
|
|
151
|
+
let chainState = IndexerState.getChainState(state, chainId);
|
|
152
|
+
if (!ChainState.hasAddressesToWrite(chainState)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let checkpointIds = [];
|
|
156
|
+
let checkpointBlockNumbers = [];
|
|
157
|
+
for (let idx = 0, idx_finish = batch.checkpointIds.length; idx < idx_finish; ++idx) {
|
|
158
|
+
if (batch.checkpointChainIds[idx] === chainId) {
|
|
159
|
+
checkpointIds.push(batch.checkpointIds[idx]);
|
|
160
|
+
checkpointBlockNumbers.push(batch.checkpointBlockNumbers[idx]);
|
|
149
161
|
}
|
|
150
162
|
}
|
|
151
|
-
|
|
152
|
-
|
|
163
|
+
ChainState.drainAddressesForWrite(chainState, progressedChain.progressBlockNumber, checkpointBlockNumbers).forEach(dc => {
|
|
164
|
+
let entity_id = Config.EnvioAddresses.makeId(chainId, dc.address);
|
|
165
|
+
let entity_registration_block = dc.registrationBlock;
|
|
166
|
+
let entity_contract_name = dc.contractName;
|
|
167
|
+
let entity = {
|
|
168
|
+
id: entity_id,
|
|
169
|
+
chain_id: chainId,
|
|
170
|
+
registration_block: entity_registration_block,
|
|
171
|
+
registration_log_index: -1,
|
|
172
|
+
contract_name: entity_contract_name
|
|
173
|
+
};
|
|
174
|
+
InMemoryTable.Entity.set(inMemTable, committedCheckpointId, {
|
|
175
|
+
type: "SET",
|
|
176
|
+
entityId: entity_id,
|
|
177
|
+
entity: entity,
|
|
178
|
+
checkpointId: checkpointIds[dc.checkpointIdx]
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
153
182
|
}
|
|
154
183
|
|
|
155
184
|
export {
|
|
156
185
|
getInMemTable,
|
|
186
|
+
entityScope,
|
|
187
|
+
takeRowScope,
|
|
157
188
|
getEffectInMemTable,
|
|
158
189
|
hasEffectOutput,
|
|
159
190
|
getEffectOutputUnsafe,
|
package/src/IndexerState.res
CHANGED
|
@@ -5,28 +5,6 @@ type rollbackState =
|
|
|
5
5
|
| FoundReorgDepth({chainId: ChainId.t, rollbackTargetBlockNumber: int})
|
|
6
6
|
| RollbackReady({eventsProcessedDiffByChain: dict<float>})
|
|
7
7
|
|
|
8
|
-
module EntityTables = {
|
|
9
|
-
type t = dict<InMemoryTable.Entity.t>
|
|
10
|
-
exception UndefinedEntity({entityName: string})
|
|
11
|
-
let make = (entities: array<Internal.entityConfig>): t => {
|
|
12
|
-
let init = Dict.make()
|
|
13
|
-
entities->Array.forEach(entityConfig => {
|
|
14
|
-
init->Dict.set((entityConfig.name :> string), InMemoryTable.Entity.make())
|
|
15
|
-
})
|
|
16
|
-
init
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let get = (self: t, ~entityName: string) => {
|
|
20
|
-
switch self->Utils.Dict.dangerouslyGetNonOption(entityName) {
|
|
21
|
-
| Some(table) => table
|
|
22
|
-
| None =>
|
|
23
|
-
UndefinedEntity({entityName: entityName})->ErrorHandling.mkLogAndRaise(
|
|
24
|
-
~msg="Unexpected, entity InMemoryTable is undefined",
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
8
|
// Per-(contract, event) handler counters rendered into the
|
|
31
9
|
// envio_processing_handler_* and envio_preload_handler_* metrics.
|
|
32
10
|
type handlerStat = {
|
|
@@ -75,6 +53,8 @@ type t = {
|
|
|
75
53
|
persistence: Persistence.t,
|
|
76
54
|
// --- In-memory store: entity/effect tables and the pending-write queue. ---
|
|
77
55
|
allEntities: array<Internal.entityConfig>,
|
|
56
|
+
// Cross-chain entities only; each ChainState holds its own partition for the
|
|
57
|
+
// per-chain ones.
|
|
78
58
|
mutable entities: EntityTables.t,
|
|
79
59
|
effectState: EffectState.t,
|
|
80
60
|
mutable rollback: option<Persistence.rollback>,
|
|
@@ -186,7 +166,7 @@ let make = (
|
|
|
186
166
|
config,
|
|
187
167
|
persistence,
|
|
188
168
|
allEntities: persistence.allEntities,
|
|
189
|
-
entities: EntityTables.make(persistence.allEntities),
|
|
169
|
+
entities: EntityTables.make(persistence.allEntities->EntityTables.crossChain),
|
|
190
170
|
effectState: EffectState.make(),
|
|
191
171
|
rollback: None,
|
|
192
172
|
committedCheckpointId,
|
|
@@ -467,6 +447,30 @@ let config = (state: t) => state.config
|
|
|
467
447
|
let persistence = (state: t) => state.persistence
|
|
468
448
|
let allEntities = (state: t) => state.allEntities
|
|
469
449
|
let entities = (state: t) => state.entities
|
|
450
|
+
|
|
451
|
+
// Every in-memory entity table across all scopes, cross-chain first. The size,
|
|
452
|
+
// drop and flush passes walk the whole store this way instead of assuming a
|
|
453
|
+
// single partition.
|
|
454
|
+
let eachEntityTable = (state: t, fn: (~entityConfig: Internal.entityConfig, ~scope: Internal.chainScope, ~table: InMemoryTable.Entity.t) => unit) => {
|
|
455
|
+
let chainStates = state.crossChainState->CrossChainState.chainStates
|
|
456
|
+
state.allEntities->Array.forEach(entityConfig =>
|
|
457
|
+
if entityConfig.crossChain {
|
|
458
|
+
fn(
|
|
459
|
+
~entityConfig,
|
|
460
|
+
~scope=Internal.CrossChain,
|
|
461
|
+
~table=state.entities->EntityTables.get(~entityName=entityConfig.name),
|
|
462
|
+
)
|
|
463
|
+
} else {
|
|
464
|
+
chainStates->Utils.Dict.forEach(chainState => {
|
|
465
|
+
fn(
|
|
466
|
+
~entityConfig,
|
|
467
|
+
~scope=Internal.Chain((chainState->ChainState.chainConfig).id),
|
|
468
|
+
~table=chainState->ChainState.entities->EntityTables.get(~entityName=entityConfig.name),
|
|
469
|
+
)
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
}
|
|
470
474
|
let effectState = (state: t) => state.effectState
|
|
471
475
|
let committedCheckpointId = (state: t) => state.committedCheckpointId
|
|
472
476
|
let processedCheckpointId = (state: t) => state.processedCheckpointId
|
|
@@ -802,7 +806,9 @@ let beginRollbackDiff = (
|
|
|
802
806
|
~diffCheckpointId,
|
|
803
807
|
~progressBlockNumberByChainId,
|
|
804
808
|
) => {
|
|
805
|
-
|
|
809
|
+
let perChainEntities = state.allEntities->EntityTables.perChain
|
|
810
|
+
state.entities = EntityTables.make(state.allEntities->EntityTables.crossChain)
|
|
811
|
+
state->chainStates->Utils.Dict.forEach(cs => cs->ChainState.resetEntities(~perChainEntities))
|
|
806
812
|
state.effectState->EffectState.resetForRollback
|
|
807
813
|
state.rollback = Some({
|
|
808
814
|
targetCheckpointId,
|
package/src/IndexerState.res.mjs
CHANGED
|
@@ -12,40 +12,17 @@ import * as ChainState from "./ChainState.res.mjs";
|
|
|
12
12
|
import * as EffectState from "./EffectState.res.mjs";
|
|
13
13
|
import * as LoadManager from "./LoadManager.res.mjs";
|
|
14
14
|
import * as Performance from "./bindings/Performance.res.mjs";
|
|
15
|
+
import * as EntityTables from "./EntityTables.res.mjs";
|
|
15
16
|
import * as ErrorHandling from "./ErrorHandling.res.mjs";
|
|
16
|
-
import * as InMemoryTable from "./InMemoryTable.res.mjs";
|
|
17
17
|
import * as SourceManager from "./sources/SourceManager.res.mjs";
|
|
18
18
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
19
19
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
20
20
|
import * as CrossChainState from "./CrossChainState.res.mjs";
|
|
21
21
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
22
22
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
23
|
-
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
24
23
|
import * as SimulateDeadInputTracker from "./SimulateDeadInputTracker.res.mjs";
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function make(entities) {
|
|
29
|
-
let init = {};
|
|
30
|
-
entities.forEach(entityConfig => {
|
|
31
|
-
init[entityConfig.name] = InMemoryTable.Entity.make();
|
|
32
|
-
});
|
|
33
|
-
return init;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function get(self, entityName) {
|
|
37
|
-
let table = self[entityName];
|
|
38
|
-
if (table !== undefined) {
|
|
39
|
-
return table;
|
|
40
|
-
} else {
|
|
41
|
-
return ErrorHandling.mkLogAndRaise(undefined, "Unexpected, entity InMemoryTable is undefined", {
|
|
42
|
-
RE_EXN_ID: UndefinedEntity,
|
|
43
|
-
entityName: entityName
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt, committedCheckpointIdOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
25
|
+
function make(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSizeOpt, committedCheckpointIdOpt, isDevelopmentModeOpt, shouldUseTuiOpt, exitAfterFirstEventBlockOpt, onError, onExit) {
|
|
49
26
|
let targetBufferSize = targetBufferSizeOpt !== undefined ? targetBufferSizeOpt : CrossChainState.calculateTargetBufferSize();
|
|
50
27
|
let committedCheckpointId = committedCheckpointIdOpt !== undefined ? committedCheckpointIdOpt : Internal.initialCheckpointId;
|
|
51
28
|
let isDevelopmentMode = isDevelopmentModeOpt !== undefined ? isDevelopmentModeOpt : false;
|
|
@@ -60,7 +37,7 @@ function make$1(config, persistence, chainStates, isInReorgThreshold, isRealtime
|
|
|
60
37
|
config: config,
|
|
61
38
|
persistence: persistence,
|
|
62
39
|
allEntities: persistence.allEntities,
|
|
63
|
-
entities: make(persistence.allEntities),
|
|
40
|
+
entities: EntityTables.make(EntityTables.crossChain(persistence.allEntities)),
|
|
64
41
|
effectState: EffectState.make(),
|
|
65
42
|
rollback: undefined,
|
|
66
43
|
committedCheckpointId: committedCheckpointId,
|
|
@@ -125,7 +102,7 @@ function makeFromDbState(config, persistence, initialState, registrationsByChain
|
|
|
125
102
|
let chainConfig = ChainMap.get(config.chainMap, chainId);
|
|
126
103
|
chainStates[resumedChainState.id] = ChainState.makeFromDbState(chainConfig, resumedChainState, initialState.reorgCheckpoints, isInReorgThreshold, isRealtime, config, registrationsByChainId, reducedPollingInterval);
|
|
127
104
|
});
|
|
128
|
-
let state = make
|
|
105
|
+
let state = make(config, persistence, chainStates, isInReorgThreshold, isRealtime, targetBufferSize, initialState.checkpointId, isDevelopmentMode, shouldUseTui, exitAfterFirstEventBlock, onError, onExit);
|
|
129
106
|
CrossChainState.markCaughtUpOnResume(state.crossChainState);
|
|
130
107
|
Utils.Dict.forEach(initialState.cache, param => {
|
|
131
108
|
let count = param.count;
|
|
@@ -288,6 +265,17 @@ function entities(state) {
|
|
|
288
265
|
return state.entities;
|
|
289
266
|
}
|
|
290
267
|
|
|
268
|
+
function eachEntityTable(state, fn) {
|
|
269
|
+
let chainStates = CrossChainState.chainStates(state.crossChainState);
|
|
270
|
+
state.allEntities.forEach(entityConfig => {
|
|
271
|
+
if (entityConfig.crossChain) {
|
|
272
|
+
return fn(entityConfig, "crossChain", EntityTables.get(state.entities, entityConfig.name));
|
|
273
|
+
} else {
|
|
274
|
+
return Utils.Dict.forEach(chainStates, chainState => fn(entityConfig, ChainState.chainConfig(chainState).id, EntityTables.get(ChainState.entities(chainState), entityConfig.name)));
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
291
279
|
function effectState(state) {
|
|
292
280
|
return state.effectState;
|
|
293
281
|
}
|
|
@@ -678,7 +666,9 @@ function markCommitted(state, upToCheckpointId) {
|
|
|
678
666
|
}
|
|
679
667
|
|
|
680
668
|
function beginRollbackDiff(state, targetCheckpointId, diffCheckpointId, progressBlockNumberByChainId) {
|
|
681
|
-
|
|
669
|
+
let perChainEntities = EntityTables.perChain(state.allEntities);
|
|
670
|
+
state.entities = EntityTables.make(EntityTables.crossChain(state.allEntities));
|
|
671
|
+
Utils.Dict.forEach(CrossChainState.chainStates(state.crossChainState), cs => ChainState.resetEntities(cs, perChainEntities));
|
|
682
672
|
EffectState.resetForRollback(state.effectState);
|
|
683
673
|
state.rollback = {
|
|
684
674
|
targetCheckpointId: targetCheckpointId,
|
|
@@ -745,16 +735,10 @@ function takeChainMetaSnapshot(state) {
|
|
|
745
735
|
}
|
|
746
736
|
}
|
|
747
737
|
|
|
748
|
-
let EntityTables = {
|
|
749
|
-
make: make,
|
|
750
|
-
get: get
|
|
751
|
-
};
|
|
752
|
-
|
|
753
738
|
let unexpectedErrorMsg = "Indexer has failed with an unexpected error";
|
|
754
739
|
|
|
755
740
|
export {
|
|
756
|
-
|
|
757
|
-
make$1 as make,
|
|
741
|
+
make,
|
|
758
742
|
makeFromDbState,
|
|
759
743
|
unexpectedErrorMsg,
|
|
760
744
|
isStale,
|
|
@@ -781,6 +765,7 @@ export {
|
|
|
781
765
|
persistence,
|
|
782
766
|
allEntities,
|
|
783
767
|
entities,
|
|
768
|
+
eachEntityTable,
|
|
784
769
|
effectState,
|
|
785
770
|
committedCheckpointId,
|
|
786
771
|
processedCheckpointId,
|
package/src/IndexerState.resi
CHANGED
|
@@ -9,12 +9,6 @@ type rollbackState =
|
|
|
9
9
|
| FoundReorgDepth({chainId: ChainId.t, rollbackTargetBlockNumber: int})
|
|
10
10
|
| RollbackReady({eventsProcessedDiffByChain: dict<float>})
|
|
11
11
|
|
|
12
|
-
module EntityTables: {
|
|
13
|
-
type t = dict<InMemoryTable.Entity.t>
|
|
14
|
-
let make: array<Internal.entityConfig> => t
|
|
15
|
-
let get: (t, ~entityName: string) => InMemoryTable.Entity.t
|
|
16
|
-
}
|
|
17
|
-
|
|
18
12
|
type t
|
|
19
13
|
|
|
20
14
|
let make: (
|
|
@@ -80,6 +74,14 @@ let config: t => Config.t
|
|
|
80
74
|
let persistence: t => Persistence.t
|
|
81
75
|
let allEntities: t => array<Internal.entityConfig>
|
|
82
76
|
let entities: t => EntityTables.t
|
|
77
|
+
let eachEntityTable: (
|
|
78
|
+
t,
|
|
79
|
+
(
|
|
80
|
+
~entityConfig: Internal.entityConfig,
|
|
81
|
+
~scope: Internal.chainScope,
|
|
82
|
+
~table: InMemoryTable.Entity.t,
|
|
83
|
+
) => unit,
|
|
84
|
+
) => unit
|
|
83
85
|
let effectState: t => EffectState.t
|
|
84
86
|
let committedCheckpointId: t => Internal.checkpointId
|
|
85
87
|
let processedCheckpointId: t => Internal.checkpointId
|
package/src/Internal.res
CHANGED
|
@@ -616,8 +616,6 @@ type indexingAddress = {
|
|
|
616
616
|
registrationBlock: int,
|
|
617
617
|
}
|
|
618
618
|
|
|
619
|
-
type dcs = array<indexingAddress>
|
|
620
|
-
|
|
621
619
|
// Duplicate the type from item to keep item properly unboxed. Runtime event
|
|
622
620
|
// items carry the registration their source already resolved from the
|
|
623
621
|
// ChainState-owned registration array.
|
|
@@ -698,11 +696,6 @@ let getItemChainId = item =>
|
|
|
698
696
|
| Block({onBlockRegistration: {chainId}}) => chainId
|
|
699
697
|
}
|
|
700
698
|
|
|
701
|
-
@get
|
|
702
|
-
external getItemDcs: item => option<dcs> = "dcs"
|
|
703
|
-
@set
|
|
704
|
-
external setItemDcs: (item, dcs) => unit = "dcs"
|
|
705
|
-
|
|
706
699
|
type eventOptions<'where> = {
|
|
707
700
|
wildcard?: bool,
|
|
708
701
|
where?: 'where,
|
|
@@ -751,6 +744,10 @@ type genericEntityConfig<'entity> = {
|
|
|
751
744
|
schema: S.t<'entity>,
|
|
752
745
|
table: Table.table,
|
|
753
746
|
storage: entityStorage,
|
|
747
|
+
// Resolved by the CLI against the config's `defaultCrossChain` and the
|
|
748
|
+
// entity's `@crossChain`. When false the table carries a chain-id column in
|
|
749
|
+
// its primary key and every row belongs to exactly one chain.
|
|
750
|
+
crossChain: bool,
|
|
754
751
|
}
|
|
755
752
|
type entityConfig = genericEntityConfig<entity>
|
|
756
753
|
external fromGenericEntityConfig: genericEntityConfig<'entity> => entityConfig = "%identity"
|
|
@@ -780,10 +777,11 @@ type effect = {
|
|
|
780
777
|
handler: effectArgs => promise<effectOutput>,
|
|
781
778
|
storageMeta: effectCacheStorageMeta,
|
|
782
779
|
defaultShouldCache: bool,
|
|
783
|
-
// When true
|
|
784
|
-
//
|
|
785
|
-
//
|
|
786
|
-
|
|
780
|
+
// When true a single cache is shared across every chain and the handler must
|
|
781
|
+
// not read context.chain. When false the cache is isolated per chain and
|
|
782
|
+
// context.chain.id is available. None means the effect didn't say, and the
|
|
783
|
+
// config's `defaultCrossChain` decides.
|
|
784
|
+
crossChain: option<bool>,
|
|
787
785
|
output: S.t<effectOutput>,
|
|
788
786
|
input: S.t<effectInput>,
|
|
789
787
|
rateLimit: option<rateLimitOptions>,
|
|
@@ -798,6 +796,26 @@ type chainScope =
|
|
|
798
796
|
| @as("crossChain") CrossChain
|
|
799
797
|
| Chain(ChainId.t)
|
|
800
798
|
|
|
799
|
+
let chainScopeToString = scope =>
|
|
800
|
+
switch scope {
|
|
801
|
+
| CrossChain => "crossChain"
|
|
802
|
+
| Chain(chainId) => chainId->ChainId.toString
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
let chainScopeChainId = scope =>
|
|
806
|
+
switch scope {
|
|
807
|
+
| CrossChain => None
|
|
808
|
+
| Chain(chainId) => Some(chainId)
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// A copy of the entity carrying the chain-id column a per-chain entity's row
|
|
812
|
+
// needs. Never mutates the handler's object, which stays in the in-memory table.
|
|
813
|
+
let stampChainId = (entity: entity, ~fieldName, ~chainId: ChainId.t): entity =>
|
|
814
|
+
Utils.Dict.merge(
|
|
815
|
+
entity->(Utils.magic: entity => dict<unknown>),
|
|
816
|
+
Dict.fromArray([(fieldName, chainId->(Utils.magic: ChainId.t => unknown))]),
|
|
817
|
+
)->(Utils.magic: dict<unknown> => entity)
|
|
818
|
+
|
|
801
819
|
let cacheTablePrefix = "envio_effect_"
|
|
802
820
|
|
|
803
821
|
// The single reversible mapping between an effect's (name, scope) and its
|
|
@@ -813,13 +831,6 @@ module EffectCache = {
|
|
|
813
831
|
| Chain(chainId) => `envio_${chainId->ChainId.toString}_effect_${effectName}`
|
|
814
832
|
}
|
|
815
833
|
|
|
816
|
-
// "crossChain" or the decimal chain id. Used as the `scope` Prometheus label.
|
|
817
|
-
let scopeToString = scope =>
|
|
818
|
-
switch scope {
|
|
819
|
-
| CrossChain => "crossChain"
|
|
820
|
-
| Chain(chainId) => chainId->ChainId.toString
|
|
821
|
-
}
|
|
822
|
-
|
|
823
834
|
// Only accepts a canonical decimal chain id ("7", not "007" or "1foo") —
|
|
824
835
|
// the schema's parser follows parseFloat semantics and accepts both.
|
|
825
836
|
let parseChainId = str =>
|