envio 3.8.0 → 3.9.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/README.md +2 -2
- package/index.d.ts +158 -104
- package/package.json +6 -6
- package/src/AddressRows.res +121 -0
- package/src/AddressRows.res.mjs +143 -0
- package/src/Batch.res +2 -0
- package/src/Batch.res.mjs +2 -1
- package/src/ChainState.res +76 -70
- package/src/ChainState.res.mjs +76 -65
- package/src/ChainState.resi +14 -9
- package/src/Config.res +23 -82
- package/src/Config.res.mjs +19 -63
- package/src/ContractMapping.res +66 -0
- package/src/ContractMapping.res.mjs +73 -0
- package/src/Core.res +20 -0
- package/src/Core.res.mjs +4 -0
- package/src/EventConfigBuilder.res +0 -2
- package/src/EventConfigBuilder.res.mjs +0 -2
- package/src/FetchState.res +59 -67
- package/src/FetchState.res.mjs +34 -44
- package/src/InMemoryStore.res +17 -30
- package/src/InMemoryStore.res.mjs +11 -23
- package/src/IndexerState.res +21 -1
- package/src/IndexerState.res.mjs +9 -3
- package/src/IndexerState.resi +1 -0
- package/src/Main.res +25 -15
- package/src/Main.res.mjs +19 -14
- package/src/MemoryStorage.res +39 -66
- package/src/MemoryStorage.res.mjs +28 -65
- package/src/Metrics.res +15 -1
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +30 -16
- package/src/Persistence.res.mjs +8 -5
- package/src/PgStorage.res +183 -65
- package/src/PgStorage.res.mjs +92 -58
- package/src/Rollback.res +14 -10
- package/src/Rollback.res.mjs +4 -2
- package/src/SimulateItems.res +5 -12
- package/src/SimulateItems.res.mjs +6 -10
- package/src/TestIndexer.res +93 -111
- package/src/TestIndexer.res.mjs +61 -88
- package/src/Utils.res +7 -0
- package/src/Utils.res.mjs +9 -2
- package/src/Writing.res +2 -0
- package/src/Writing.res.mjs +2 -1
- package/src/bindings/ClickHouse.res +6 -0
- package/src/bindings/ClickHouse.res.mjs +4 -0
- package/src/bindings/NodeJs.res +9 -0
- package/src/bindings/NodeJs.res.mjs +8 -1
- package/src/bindings/Postgres.res +9 -0
- package/src/bindings/Postgres.res.mjs +3 -0
- package/src/db/EntityHistory.res +12 -18
- package/src/db/EntityHistory.res.mjs +3 -14
- package/src/db/InternalTable.res +171 -65
- package/src/db/InternalTable.res.mjs +176 -73
- package/src/db/Table.res +24 -0
- package/src/db/Table.res.mjs +20 -1
- package/src/sources/AddressSet.res +0 -22
- package/src/sources/AddressStore.res +48 -88
- package/src/sources/AddressStore.res.mjs +11 -22
- package/src/sources/SvmHyperSyncClient.res +17 -21
- package/src/sources/SvmHyperSyncSource.res +4 -9
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -13
- package/svm.schema.json +10 -4
package/src/PgStorage.res
CHANGED
|
@@ -86,6 +86,7 @@ let makeCreateTableQuery = (
|
|
|
86
86
|
~pgSchema,
|
|
87
87
|
~isNumericArrayAsText,
|
|
88
88
|
~chainIdMode: ChainId.mode=Int32,
|
|
89
|
+
~partitionByColumn: option<string>=?,
|
|
89
90
|
) => {
|
|
90
91
|
let fieldsMapped =
|
|
91
92
|
table
|
|
@@ -115,7 +116,23 @@ let makeCreateTableQuery = (
|
|
|
115
116
|
|
|
116
117
|
`CREATE TABLE IF NOT EXISTS "${pgSchema}"."${table.tableName}"(${fieldsMapped}${primaryKeyFieldNames->Array.length > 0
|
|
117
118
|
? `, PRIMARY KEY(${primaryKey})`
|
|
118
|
-
: ""})
|
|
119
|
+
: ""})${switch partitionByColumn {
|
|
120
|
+
| Some(column) => ` PARTITION BY LIST ("${column}")`
|
|
121
|
+
| None => ""
|
|
122
|
+
}};`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// A per-chain entity's rows are partitioned by the chain that owns them, so a
|
|
126
|
+
// chain-filtered read scans one chain's partition rather than the whole table.
|
|
127
|
+
// `$` can't occur in a GraphQL entity name, so a partition name can never
|
|
128
|
+
// collide with the table another entity claims; past the identifier limit the
|
|
129
|
+
// entity index keeps what survives truncation unique.
|
|
130
|
+
let partitionTableName = (~entityConfig: Internal.entityConfig, ~chainId: ChainId.t) => {
|
|
131
|
+
let chainIdStr = chainId->ChainId.toString
|
|
132
|
+
Table.fitPgTableName(
|
|
133
|
+
`${entityConfig.table.tableName}$${chainIdStr}`,
|
|
134
|
+
~uniqueSuffix=`$${entityConfig.index->Int.toString}$${chainIdStr}`,
|
|
135
|
+
)
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
// The entity as it's stored: the handler-visible schema plus the chain-id
|
|
@@ -222,6 +239,40 @@ let getEntityHistory = (~entityConfig: Internal.entityConfig): EntityHistory.pgE
|
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
|
|
242
|
+
// Every table an entity needs: its own, one partition per chain when it's
|
|
243
|
+
// per-chain, and its history table. The chain set is fixed for the life of a
|
|
244
|
+
// schema — changing it fails the resume compat check against `envio_info` and
|
|
245
|
+
// forces a resync — so every partition the entity will ever need is created
|
|
246
|
+
// here, at init.
|
|
247
|
+
//
|
|
248
|
+
// History stays unpartitioned: it is only ever read by checkpoint, never by
|
|
249
|
+
// chain, so partitioning it would route every write and prune nothing.
|
|
250
|
+
let makeCreateEntityTableQueries = (
|
|
251
|
+
entityConfig: Internal.entityConfig,
|
|
252
|
+
~pgSchema,
|
|
253
|
+
~isNumericArrayAsText,
|
|
254
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
255
|
+
~chainIds: array<ChainId.t>,
|
|
256
|
+
) => {
|
|
257
|
+
let createTable = (table, ~partitionByColumn=?) =>
|
|
258
|
+
makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText, ~chainIdMode, ~partitionByColumn?)
|
|
259
|
+
|
|
260
|
+
switch entityConfig.table->Table.getChainIdField {
|
|
261
|
+
| None => [entityConfig.table->createTable]
|
|
262
|
+
| Some(chainIdField) =>
|
|
263
|
+
[
|
|
264
|
+
entityConfig.table->createTable(~partitionByColumn=chainIdField->Table.getPgDbFieldName),
|
|
265
|
+
]->Array.concat(
|
|
266
|
+
chainIds->Array.map(chainId =>
|
|
267
|
+
`CREATE TABLE IF NOT EXISTS "${pgSchema}"."${partitionTableName(
|
|
268
|
+
~entityConfig,
|
|
269
|
+
~chainId,
|
|
270
|
+
)}" PARTITION OF "${pgSchema}"."${entityConfig.table.tableName}" FOR VALUES IN (${chainId->ChainId.toString});`
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
}->Array.concat([getEntityHistory(~entityConfig).table->createTable])
|
|
274
|
+
}
|
|
275
|
+
|
|
225
276
|
let makeInitializeTransaction = (
|
|
226
277
|
~pgSchema,
|
|
227
278
|
~pgUser,
|
|
@@ -239,15 +290,29 @@ let makeInitializeTransaction = (
|
|
|
239
290
|
let generalTables = [
|
|
240
291
|
InternalTable.Chains.table,
|
|
241
292
|
InternalTable.EnvioInfo.table,
|
|
293
|
+
InternalTable.EnvioContracts.table,
|
|
294
|
+
InternalTable.EnvioAddresses.table,
|
|
242
295
|
InternalTable.Checkpoints.table,
|
|
243
296
|
InternalTable.RawEvents.table,
|
|
244
297
|
]
|
|
245
298
|
|
|
246
|
-
let
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
299
|
+
let chainIds = chainConfigs->Array.map((chainConfig: Config.chain) => chainConfig.id)
|
|
300
|
+
|
|
301
|
+
let tableQueries =
|
|
302
|
+
generalTables
|
|
303
|
+
->Array.map(table =>
|
|
304
|
+
makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText=isHasuraEnabled, ~chainIdMode)
|
|
305
|
+
)
|
|
306
|
+
->Array.concat(
|
|
307
|
+
entities->Array.flatMap((entityConfig: Internal.entityConfig) =>
|
|
308
|
+
entityConfig->makeCreateEntityTableQueries(
|
|
309
|
+
~pgSchema,
|
|
310
|
+
~isNumericArrayAsText=isHasuraEnabled,
|
|
311
|
+
~chainIdMode,
|
|
312
|
+
~chainIds,
|
|
313
|
+
)
|
|
314
|
+
),
|
|
315
|
+
)
|
|
251
316
|
|
|
252
317
|
let schemaIndexes = getSchemaIndexes(~entities)
|
|
253
318
|
|
|
@@ -276,11 +341,8 @@ GRANT ALL ON SCHEMA "${pgSchema}" TO public;`,
|
|
|
276
341
|
})
|
|
277
342
|
|
|
278
343
|
// Batch all table creation first (optimal for PostgreSQL)
|
|
279
|
-
|
|
280
|
-
query :=
|
|
281
|
-
query.contents ++
|
|
282
|
-
"\n" ++
|
|
283
|
-
makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText=isHasuraEnabled, ~chainIdMode)
|
|
344
|
+
tableQueries->Array.forEach(tableQuery => {
|
|
345
|
+
query := query.contents ++ "\n" ++ tableQuery
|
|
284
346
|
})
|
|
285
347
|
|
|
286
348
|
// Then batch all indexes (better performance when tables exist)
|
|
@@ -360,6 +422,26 @@ let rec makeFilterCondition = (
|
|
|
360
422
|
)}`
|
|
361
423
|
}
|
|
362
424
|
switch filter {
|
|
425
|
+
// A per-chain entity's table is partitioned by its chain-id column, and
|
|
426
|
+
// Postgres can only prune a plan it caches when that column is a constant in
|
|
427
|
+
// the SQL. Bound, the cached plan has to keep every partition, and the
|
|
428
|
+
// planner ends up throwing it away and re-planning on every execution
|
|
429
|
+
// instead — measured at 315us per load against 218us with the id written in,
|
|
430
|
+
// on 30 chains.
|
|
431
|
+
//
|
|
432
|
+
// The cost is that each chain gets its own query text, so Postgres caches a
|
|
433
|
+
// prepared statement per (entity, chain, filter shape) rather than per
|
|
434
|
+
// (entity, filter shape). Measured at ~8KB of plan cache each, which is ~10MB
|
|
435
|
+
// per connection for 40 entities across 30 chains — accepted, since the
|
|
436
|
+
// alternative is a cached plan that can't prune.
|
|
437
|
+
//
|
|
438
|
+
// `LoadLayer.scopeFilter` is what puts this filter here, and the value is
|
|
439
|
+
// range-checked to a non-negative safe integer, so it can carry nothing but
|
|
440
|
+
// digits.
|
|
441
|
+
| Eq({fieldName, fieldValue}) if (getQueryFieldOrThrow(fieldName)).isChainId =>
|
|
442
|
+
`"${(getQueryFieldOrThrow(fieldName)).pgDbFieldName}" = ${fieldValue
|
|
443
|
+
->ChainId.normalizeOrThrow
|
|
444
|
+
->ChainId.toString}`
|
|
363
445
|
| Eq({fieldName, fieldValue}) => scalarCondition(~fieldName, ~fieldValue, ~op="=")
|
|
364
446
|
| Gt({fieldName, fieldValue}) => scalarCondition(~fieldName, ~fieldValue, ~op=">")
|
|
365
447
|
| Lt({fieldName, fieldValue}) => scalarCondition(~fieldName, ~fieldValue, ~op="<")
|
|
@@ -387,12 +469,16 @@ let rec makeFilterCondition = (
|
|
|
387
469
|
}
|
|
388
470
|
|
|
389
471
|
// The chain-id predicate a per-chain entity's row-level SQL needs, already
|
|
390
|
-
// including the leading AND.
|
|
391
|
-
//
|
|
392
|
-
//
|
|
472
|
+
// including the leading AND. Empty for cross-chain entities and for internal
|
|
473
|
+
// tables, which have no such column.
|
|
474
|
+
//
|
|
475
|
+
// The chain id is written into the SQL rather than bound, because the table is
|
|
476
|
+
// partitioned by it — see `makeFilterCondition` for why a partition key has to
|
|
477
|
+
// be a constant.
|
|
393
478
|
let makeChainIdCondition = (~table: Table.table, ~chainId: option<ChainId.t>) =>
|
|
394
479
|
switch (table->Table.getChainIdField, chainId) {
|
|
395
|
-
| (Some(field), Some(
|
|
480
|
+
| (Some(field), Some(chainId)) =>
|
|
481
|
+
` AND "${field->Table.getPgDbFieldName}" = ${chainId->ChainId.toString}`
|
|
396
482
|
| _ => ""
|
|
397
483
|
}
|
|
398
484
|
|
|
@@ -835,24 +921,16 @@ let deleteByIdsOrThrow = async (
|
|
|
835
921
|
~chainId: option<ChainId.t>=None,
|
|
836
922
|
) => {
|
|
837
923
|
let chainIdCondition = makeChainIdCondition(~table, ~chainId)
|
|
838
|
-
let chainIdParams = switch chainId {
|
|
839
|
-
| Some(chainId) if chainIdCondition !== "" => [chainId->(Utils.magic: ChainId.t => unknown)]
|
|
840
|
-
| _ => []
|
|
841
|
-
}
|
|
842
924
|
// A JSON array of the serialized ids. For a single id the query binds it as
|
|
843
925
|
// `$1` directly (the array is the positional-params array); for many it binds
|
|
844
|
-
// the whole array to `$1` behind an `ANY(...)`.
|
|
845
|
-
// condition needs it, rides along as $2.
|
|
926
|
+
// the whole array to `$1` behind an `ANY(...)`.
|
|
846
927
|
let idsJson = table->Table.encodeIdsToJson(ids)
|
|
847
928
|
switch await (
|
|
848
929
|
switch ids {
|
|
849
930
|
| [_] =>
|
|
850
931
|
sql->Postgres.preparedUnsafe(
|
|
851
932
|
makeDeleteByIdQuery(~pgSchema, ~tableName=table.tableName, ~chainIdCondition),
|
|
852
|
-
idsJson
|
|
853
|
-
->(Utils.magic: JSON.t => array<unknown>)
|
|
854
|
-
->Array.concat(chainIdParams)
|
|
855
|
-
->Obj.magic,
|
|
933
|
+
idsJson->(Utils.magic: JSON.t => array<unknown>)->Obj.magic,
|
|
856
934
|
)
|
|
857
935
|
| _ =>
|
|
858
936
|
sql->Postgres.preparedUnsafe(
|
|
@@ -862,7 +940,7 @@ let deleteByIdsOrThrow = async (
|
|
|
862
940
|
~idPgType=table->Table.getIdPgFieldType(~pgSchema),
|
|
863
941
|
~chainIdCondition,
|
|
864
942
|
),
|
|
865
|
-
[idsJson->(Utils.magic: JSON.t => unknown)]->
|
|
943
|
+
[idsJson->(Utils.magic: JSON.t => unknown)]->Obj.magic,
|
|
866
944
|
)
|
|
867
945
|
}
|
|
868
946
|
) {
|
|
@@ -961,6 +1039,7 @@ let rec writeBatch = async (
|
|
|
961
1039
|
~setQueryCache,
|
|
962
1040
|
~updatedEffectsCache,
|
|
963
1041
|
~updatedEntities: array<Persistence.updatedEntity>,
|
|
1042
|
+
~registeredAddresses: array<AddressRows.staged>,
|
|
964
1043
|
~sinkPromise: option<promise<option<exn>>>,
|
|
965
1044
|
~chainMetaData: option<dict<InternalTable.Chains.metaFields>>,
|
|
966
1045
|
~escapeTables=?,
|
|
@@ -1217,7 +1296,7 @@ let rec writeBatch = async (
|
|
|
1217
1296
|
//valid event identifier, where all rows created after this eventIdentifier should
|
|
1218
1297
|
//be deleted
|
|
1219
1298
|
let rollbackTables = switch rollback {
|
|
1220
|
-
| Some({targetCheckpointId: rollbackTargetCheckpointId}) =>
|
|
1299
|
+
| Some({targetCheckpointId: rollbackTargetCheckpointId, rolledBackAddresses}) =>
|
|
1221
1300
|
Some(
|
|
1222
1301
|
sql => {
|
|
1223
1302
|
// Postgres owns history tables only for Postgres-backed entities;
|
|
@@ -1238,6 +1317,22 @@ let rec writeBatch = async (
|
|
|
1238
1317
|
sql->InternalTable.Checkpoints.rollback(~pgSchema, ~rollbackTargetCheckpointId),
|
|
1239
1318
|
)
|
|
1240
1319
|
->ignore
|
|
1320
|
+
|
|
1321
|
+
// Addresses are insert-only, so undoing their registrations is a
|
|
1322
|
+
// delete rather than a history replay. It runs before the batch's own
|
|
1323
|
+
// inserts in the same transaction, so a re-registered address lands
|
|
1324
|
+
// after its old row is gone.
|
|
1325
|
+
if rolledBackAddresses->Utils.Array.notEmpty {
|
|
1326
|
+
promises
|
|
1327
|
+
->Array.push(
|
|
1328
|
+
sql->InternalTable.EnvioAddresses.delete(
|
|
1329
|
+
~pgSchema,
|
|
1330
|
+
~keys=rolledBackAddresses,
|
|
1331
|
+
~chainIdMode,
|
|
1332
|
+
),
|
|
1333
|
+
)
|
|
1334
|
+
->ignore
|
|
1335
|
+
}
|
|
1241
1336
|
Promise.all(promises)
|
|
1242
1337
|
},
|
|
1243
1338
|
)
|
|
@@ -1280,6 +1375,16 @@ let rec writeBatch = async (
|
|
|
1280
1375
|
| None => ()
|
|
1281
1376
|
}
|
|
1282
1377
|
|
|
1378
|
+
if registeredAddresses->Utils.Array.notEmpty {
|
|
1379
|
+
setOperations->Array.push(sql =>
|
|
1380
|
+
sql->InternalTable.EnvioAddresses.insert(
|
|
1381
|
+
~pgSchema,
|
|
1382
|
+
~rows=registeredAddresses->Array.map(staged => staged.row),
|
|
1383
|
+
~chainIdMode,
|
|
1384
|
+
)
|
|
1385
|
+
)
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1283
1388
|
if shouldSaveHistory {
|
|
1284
1389
|
setOperations->Array.push(sql =>
|
|
1285
1390
|
sql->InternalTable.Checkpoints.insert(
|
|
@@ -1354,6 +1459,7 @@ let rec writeBatch = async (
|
|
|
1354
1459
|
~updatedEffectsCache,
|
|
1355
1460
|
~allEntities,
|
|
1356
1461
|
~updatedEntities,
|
|
1462
|
+
~registeredAddresses,
|
|
1357
1463
|
~sinkPromise,
|
|
1358
1464
|
~chainMetaData,
|
|
1359
1465
|
)
|
|
@@ -1469,6 +1575,9 @@ let make = (
|
|
|
1469
1575
|
~pgPassword,
|
|
1470
1576
|
~isHasuraEnabled,
|
|
1471
1577
|
~chainIdMode: ChainId.mode=Int32,
|
|
1578
|
+
// Decides how wide an address key is, both when the config's addresses are
|
|
1579
|
+
// encoded at initialize and when stored rows are grouped on resume.
|
|
1580
|
+
~ecosystem: Ecosystem.name,
|
|
1472
1581
|
~sink: option<Sink.t>=?,
|
|
1473
1582
|
~onInitialize=?,
|
|
1474
1583
|
~onNewTables=?,
|
|
@@ -1662,6 +1771,7 @@ let make = (
|
|
|
1662
1771
|
~chainConfigs=[],
|
|
1663
1772
|
~entities=[],
|
|
1664
1773
|
~enums=[],
|
|
1774
|
+
~contractMapping,
|
|
1665
1775
|
~envioInfo,
|
|
1666
1776
|
): Persistence.initialState => {
|
|
1667
1777
|
// Per-entity storage routing: PG owns tables only for entities that
|
|
@@ -1715,43 +1825,34 @@ let make = (
|
|
|
1715
1825
|
// Execute all queries within a single transaction for integrity.
|
|
1716
1826
|
// The envio_info row is written in the same transaction so a successful
|
|
1717
1827
|
// initialize is atomic — no schema can come up without the matching row.
|
|
1828
|
+
let rowsByChain =
|
|
1829
|
+
chainConfigs->Array.map(chainConfig =>
|
|
1830
|
+
chainConfig->ChainState.configStorageRows(~ecosystem, ~contractMapping)
|
|
1831
|
+
)
|
|
1832
|
+
let configAddressRows = rowsByChain->Array.flat
|
|
1833
|
+
|
|
1834
|
+
// The contract mapping and the config's addresses join the schema in the
|
|
1835
|
+
// same transaction as envio_info: a schema that comes up without them would
|
|
1836
|
+
// resume against ids nothing assigned.
|
|
1718
1837
|
let _ = await sql->Postgres.beginSql(async sql => {
|
|
1719
1838
|
// Promise.all might be not safe to use here,
|
|
1720
1839
|
// but it's just how it worked before.
|
|
1721
1840
|
let _ = await Promise.all(queries->Array.map(query => sql->Postgres.unsafe(query)))
|
|
1722
1841
|
await InternalTable.EnvioInfo.write(sql, ~pgSchema, ~envioInfo)
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
// Populate config addresses into envio_addresses with registration_block/log = -1
|
|
1726
|
-
let ids = []
|
|
1727
|
-
let addrChainIds = []
|
|
1728
|
-
let addrContractNames = []
|
|
1729
|
-
chainConfigs->Array.forEach(chain => {
|
|
1730
|
-
chain.contracts->Array.forEach(contract => {
|
|
1731
|
-
contract.addresses->Array.forEach(
|
|
1732
|
-
address => {
|
|
1733
|
-
ids->Array.push(Config.EnvioAddresses.makeId(~chainId=chain.id, ~address))->ignore
|
|
1734
|
-
addrChainIds->Array.push(chain.id)->ignore
|
|
1735
|
-
addrContractNames->Array.push(contract.name)->ignore
|
|
1736
|
-
},
|
|
1737
|
-
)
|
|
1738
|
-
})
|
|
1739
|
-
})
|
|
1740
|
-
if ids->Array.length > 0 {
|
|
1741
|
-
let addrChainIdArrayType = Table.getPgFieldType(
|
|
1742
|
-
~fieldType=ChainId,
|
|
1842
|
+
await InternalTable.EnvioContracts.insert(
|
|
1843
|
+
sql,
|
|
1743
1844
|
~pgSchema,
|
|
1744
|
-
~
|
|
1745
|
-
~isNumericArrayAsText=false,
|
|
1746
|
-
~isNullable=false,
|
|
1747
|
-
~chainIdMode,
|
|
1845
|
+
~contractNames=contractMapping->ContractMapping.names,
|
|
1748
1846
|
)
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1847
|
+
if configAddressRows->Utils.Array.notEmpty {
|
|
1848
|
+
await InternalTable.EnvioAddresses.insert(
|
|
1849
|
+
sql,
|
|
1850
|
+
~pgSchema,
|
|
1851
|
+
~rows=configAddressRows,
|
|
1852
|
+
~chainIdMode,
|
|
1853
|
+
)
|
|
1854
|
+
}
|
|
1855
|
+
})
|
|
1755
1856
|
|
|
1756
1857
|
let cache = await restoreEffectCache(~withUpload=true)
|
|
1757
1858
|
|
|
@@ -1767,10 +1868,12 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
1767
1868
|
cleanRun: true,
|
|
1768
1869
|
cache,
|
|
1769
1870
|
reorgCheckpoints: [],
|
|
1770
|
-
|
|
1771
|
-
// but keep the field consistent with the resume path's shape.
|
|
1871
|
+
contractMapping,
|
|
1772
1872
|
envioInfo: Some(envioInfo),
|
|
1773
|
-
chains: chainConfigs->Array.
|
|
1873
|
+
chains: chainConfigs->Array.mapWithIndex((
|
|
1874
|
+
chainConfig,
|
|
1875
|
+
idx,
|
|
1876
|
+
): Persistence.initialChainState => {
|
|
1774
1877
|
id: chainConfig.id,
|
|
1775
1878
|
startBlock: chainConfig.startBlock,
|
|
1776
1879
|
endBlock: chainConfig.endBlock,
|
|
@@ -1779,7 +1882,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
1779
1882
|
numEventsProcessed: 0.,
|
|
1780
1883
|
firstEventBlockNumber: None,
|
|
1781
1884
|
timestampCaughtUpToHeadOrEndblock: None,
|
|
1782
|
-
|
|
1885
|
+
addressRows: rowsByChain->Array.getUnsafe(idx)->AddressRows.seedRowsOf,
|
|
1783
1886
|
sourceBlockNumber: 0,
|
|
1784
1887
|
}),
|
|
1785
1888
|
checkpointId: InternalTable.Checkpoints.initialCheckpointId,
|
|
@@ -2169,7 +2272,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
2169
2272
|
}
|
|
2170
2273
|
|
|
2171
2274
|
let resumeInitialState = async (): Persistence.initialState => {
|
|
2172
|
-
let (cache, chains, checkpointIdResult, reorgCheckpoints, envioInfo) = await Promise.all5((
|
|
2275
|
+
let (cache, chains, checkpointIdResult, reorgCheckpoints, (envioInfo, contractMapping)) = await Promise.all5((
|
|
2173
2276
|
restoreEffectCache(~withUpload=false),
|
|
2174
2277
|
InternalTable.Chains.getInitialState(
|
|
2175
2278
|
sql,
|
|
@@ -2184,7 +2287,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
2184
2287
|
timestampCaughtUpToHeadOrEndblock: rawInitialState.timestampCaughtUpToHeadOrEndblock->Null.toOption,
|
|
2185
2288
|
numEventsProcessed: rawInitialState.numEventsProcessed,
|
|
2186
2289
|
progressBlockNumber: rawInitialState.progressBlockNumber,
|
|
2187
|
-
|
|
2290
|
+
addressRows: rawInitialState.addressRows,
|
|
2188
2291
|
sourceBlockNumber: rawInitialState.sourceBlockNumber,
|
|
2189
2292
|
})
|
|
2190
2293
|
}),
|
|
@@ -2203,7 +2306,18 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
2203
2306
|
}>,
|
|
2204
2307
|
>
|
|
2205
2308
|
),
|
|
2206
|
-
|
|
2309
|
+
Promise.all2((
|
|
2310
|
+
InternalTable.EnvioInfo.read(sql, ~pgSchema),
|
|
2311
|
+
InternalTable.EnvioContracts.read(sql, ~pgSchema),
|
|
2312
|
+
))->Promise.thenResolve(((info, names)) =>
|
|
2313
|
+
// Both tables join the schema in one transaction. A missing mapping
|
|
2314
|
+
// means an older envio wrote this schema, so treat the snapshot as
|
|
2315
|
+
// unreadable rather than decoding address rows against ids nothing assigned.
|
|
2316
|
+
switch (info, names) {
|
|
2317
|
+
| (Some(info), Some(names)) => (Some(info), ContractMapping.fromStoredNames(names))
|
|
2318
|
+
| _ => (None, ContractMapping.empty)
|
|
2319
|
+
}
|
|
2320
|
+
),
|
|
2207
2321
|
))
|
|
2208
2322
|
|
|
2209
2323
|
await reloadIndexCatalog()
|
|
@@ -2230,6 +2344,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
2230
2344
|
cache,
|
|
2231
2345
|
chains,
|
|
2232
2346
|
checkpointId,
|
|
2347
|
+
contractMapping,
|
|
2233
2348
|
envioInfo,
|
|
2234
2349
|
}
|
|
2235
2350
|
}
|
|
@@ -2324,6 +2439,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
2324
2439
|
~allEntities,
|
|
2325
2440
|
~updatedEffectsCache,
|
|
2326
2441
|
~updatedEntities,
|
|
2442
|
+
~registeredAddresses,
|
|
2327
2443
|
~chainMetaData,
|
|
2328
2444
|
~onWrite,
|
|
2329
2445
|
) => {
|
|
@@ -2370,6 +2486,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai
|
|
|
2370
2486
|
~setEffectCacheOrThrow,
|
|
2371
2487
|
~updatedEffectsCache,
|
|
2372
2488
|
~updatedEntities=pgUpdates,
|
|
2489
|
+
~registeredAddresses,
|
|
2373
2490
|
~sinkPromise,
|
|
2374
2491
|
~chainMetaData,
|
|
2375
2492
|
)
|
|
@@ -2415,6 +2532,7 @@ let makeStorageFromEnv = (
|
|
|
2415
2532
|
~pgDatabase=Env.Db.database,
|
|
2416
2533
|
~pgPassword=Env.Db.password,
|
|
2417
2534
|
~chainIdMode=config.chainIdMode,
|
|
2535
|
+
~ecosystem=config.ecosystem.name,
|
|
2418
2536
|
~sink=?{
|
|
2419
2537
|
// Internally ClickHouse storage is implemented as a sync of the
|
|
2420
2538
|
// Postgres storage. Required env vars are validated here only when
|
|
@@ -2467,7 +2585,7 @@ let makeStorageFromEnv = (
|
|
|
2467
2585
|
~pgSchema,
|
|
2468
2586
|
~userEntities=config->Config.getPgUserEntities,
|
|
2469
2587
|
~responseLimit=Env.Hasura.responseLimit,
|
|
2470
|
-
~schema=Schema.make(config.
|
|
2588
|
+
~schema=Schema.make(config.userEntities->Array.map(e => e.table)),
|
|
2471
2589
|
~aggregateEntities=Env.Hasura.aggregateEntities,
|
|
2472
2590
|
)->Promise.catch(err => {
|
|
2473
2591
|
Logging.errorWithExn(err->Utils.prettifyExn, `Error tracking tables`)->Promise.resolve
|