envio 3.7.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 +399 -156
- 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 +82 -68
- package/src/ChainState.res.mjs +81 -65
- package/src/ChainState.resi +14 -9
- package/src/Config.res +41 -91
- package/src/Config.res.mjs +34 -68
- 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/Envio.res +54 -76
- package/src/EventConfigBuilder.res +157 -30
- package/src/EventConfigBuilder.res.mjs +118 -24
- package/src/FetchState.res +59 -67
- package/src/FetchState.res.mjs +34 -44
- package/src/HandlerRegister.res +20 -11
- package/src/HandlerRegister.res.mjs +22 -5
- package/src/Hasura.res +30 -22
- package/src/Hasura.res.mjs +11 -5
- package/src/InMemoryStore.res +18 -36
- package/src/InMemoryStore.res.mjs +12 -26
- package/src/IndexerState.res +21 -1
- package/src/IndexerState.res.mjs +9 -3
- package/src/IndexerState.resi +1 -0
- package/src/Internal.res +33 -13
- package/src/Internal.res.mjs +12 -16
- package/src/Main.res +27 -20
- package/src/Main.res.mjs +19 -14
- package/src/MemoryStorage.res +78 -73
- package/src/MemoryStorage.res.mjs +66 -81
- package/src/Metrics.res +15 -1
- package/src/Metrics.res.mjs +5 -1
- package/src/Persistence.res +34 -18
- package/src/Persistence.res.mjs +8 -5
- package/src/PgStorage.res +189 -66
- package/src/PgStorage.res.mjs +93 -59
- package/src/Rollback.res +14 -10
- package/src/Rollback.res.mjs +4 -2
- package/src/SimulateItems.res +254 -9
- package/src/SimulateItems.res.mjs +214 -47
- package/src/TestIndexer.res +119 -121
- package/src/TestIndexer.res.mjs +95 -100
- 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 +39 -6
- package/src/bindings/ClickHouse.res.mjs +29 -4
- 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 +182 -65
- package/src/db/InternalTable.res.mjs +183 -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/EvmHyperSyncSource.res +3 -2
- package/src/sources/SimulateSource.res +8 -4
- package/src/sources/SimulateSource.res.mjs +7 -3
- package/src/sources/Svm.res +34 -7
- package/src/sources/Svm.res.mjs +32 -4
- package/src/sources/SvmHyperSyncClient.res +24 -30
- package/src/sources/SvmHyperSyncClient.res.mjs +3 -2
- package/src/sources/SvmHyperSyncSource.res +88 -36
- package/src/sources/SvmHyperSyncSource.res.mjs +71 -39
- package/src/sources/TransactionStore.res +38 -0
- package/src/sources/TransactionStore.res.mjs +5 -0
- package/svm.schema.json +37 -82
package/src/db/EntityHistory.res
CHANGED
|
@@ -50,18 +50,14 @@ type pgEntityHistory<'entity> = {
|
|
|
50
50
|
setChangeSchemaRows: S.t<array<Change.t<'entity>>>,
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
let maxPgTableNameLength = 63
|
|
54
53
|
let historyTablePrefix = "envio_history_"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
fullName
|
|
63
|
-
}
|
|
64
|
-
}
|
|
54
|
+
// `$` can't occur in a GraphQL entity name, so it marks where a truncated name
|
|
55
|
+
// stops and the index that keeps it unique begins. Without that boundary two
|
|
56
|
+
// long names whose indexes differ in digit count can truncate onto the same
|
|
57
|
+
// identifier, and `CREATE TABLE IF NOT EXISTS` would hand both entities one
|
|
58
|
+
// history table.
|
|
59
|
+
let historyTableName = (~entityName, ~entityIndex) =>
|
|
60
|
+
fitPgTableName(historyTablePrefix ++ entityName, ~uniqueSuffix=`$${entityIndex->Int.toString}`)
|
|
65
61
|
|
|
66
62
|
type safeReorgBlocks = {
|
|
67
63
|
chainIds: array<ChainId.t>,
|
|
@@ -136,7 +132,7 @@ let pruneStaleEntityHistory = (
|
|
|
136
132
|
// If an entity doesn't have a history before the update
|
|
137
133
|
// we create it automatically with envio_checkpoint_id 0
|
|
138
134
|
// The ids belong to a single chain (the flush group's scope), so the chain is
|
|
139
|
-
//
|
|
135
|
+
// named once in the query rather than unnested alongside them.
|
|
140
136
|
let makeBackfillHistoryQuery = (
|
|
141
137
|
~pgSchema,
|
|
142
138
|
~entityName,
|
|
@@ -146,8 +142,11 @@ let makeBackfillHistoryQuery = (
|
|
|
146
142
|
~chainId: option<ChainId.t>,
|
|
147
143
|
) => {
|
|
148
144
|
let historyTableRef = `"${pgSchema}"."${historyTableName(~entityName, ~entityIndex)}"`
|
|
145
|
+
// Written into the SQL rather than bound: this scans the entity table, which
|
|
146
|
+
// is partitioned by the chain-id column, and Postgres can only prune a plan
|
|
147
|
+
// it caches when that column is a constant.
|
|
149
148
|
let chainFilter = switch (chainIdColumn, chainId) {
|
|
150
|
-
| (Some(column), Some(
|
|
149
|
+
| (Some(column), Some(chainId)) => ` AND e."${column}" = ${chainId->ChainId.toString}`
|
|
151
150
|
| _ => ""
|
|
152
151
|
}
|
|
153
152
|
`WITH target_ids AS (
|
|
@@ -176,11 +175,6 @@ let backfillHistory = (
|
|
|
176
175
|
let idPgType = table->Table.getIdPgFieldType(~pgSchema)
|
|
177
176
|
let chainIdColumn = table->Table.getChainIdField->Option.map(Table.getPgDbFieldName)
|
|
178
177
|
let params = [table->Table.encodeIdsToJson(ids)->(Utils.magic: JSON.t => unknown)]
|
|
179
|
-
switch (chainIdColumn, chainId) {
|
|
180
|
-
| (Some(_), Some(chainId)) =>
|
|
181
|
-
params->Array.push(chainId->(Utils.magic: ChainId.t => unknown))->ignore
|
|
182
|
-
| _ => ()
|
|
183
|
-
}
|
|
184
178
|
sql
|
|
185
179
|
->Postgres.preparedUnsafe(
|
|
186
180
|
makeBackfillHistoryQuery(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Table from "./Table.res.mjs";
|
|
4
|
-
import * as
|
|
4
|
+
import * as ChainId from "../ChainId.res.mjs";
|
|
5
5
|
import * as Stdlib_BigInt from "@rescript/runtime/lib/es6/Stdlib_BigInt.js";
|
|
6
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
7
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
@@ -65,12 +65,7 @@ function makeSetUpdateSchema(idSchema, entitySchema) {
|
|
|
65
65
|
let historyTablePrefix = "envio_history_";
|
|
66
66
|
|
|
67
67
|
function historyTableName(entityName, entityIndex) {
|
|
68
|
-
|
|
69
|
-
if (fullName.length <= 63) {
|
|
70
|
-
return fullName;
|
|
71
|
-
}
|
|
72
|
-
let entityIndexStr = entityIndex.toString();
|
|
73
|
-
return Js_string.slice(0, 63 - entityIndexStr.length | 0, fullName) + entityIndexStr;
|
|
68
|
+
return Table.fitPgTableName(historyTablePrefix + entityName, `$` + entityIndex.toString());
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
function makeKeyColumns(chainIdColumn) {
|
|
@@ -112,7 +107,7 @@ function pruneStaleEntityHistory(sql, entityName, entityIndex, pgSchema, chainId
|
|
|
112
107
|
|
|
113
108
|
function makeBackfillHistoryQuery(pgSchema, entityName, entityIndex, idPgType, chainIdColumn, chainId) {
|
|
114
109
|
let historyTableRef = `"` + pgSchema + `"."` + historyTableName(entityName, entityIndex) + `"`;
|
|
115
|
-
let chainFilter = chainIdColumn !== undefined && chainId !== undefined ? ` AND e."` + chainIdColumn + `" =
|
|
110
|
+
let chainFilter = chainIdColumn !== undefined && chainId !== undefined ? ` AND e."` + chainIdColumn + `" = ` + ChainId.toString(Primitive_option.valFromOption(chainId)) : "";
|
|
116
111
|
return `WITH target_ids AS (
|
|
117
112
|
SELECT UNNEST($1::` + idPgType + `[]) AS id
|
|
118
113
|
),
|
|
@@ -132,9 +127,6 @@ function backfillHistory(sql, pgSchema, table, entityIndex, chainId, ids) {
|
|
|
132
127
|
let idPgType = Table.getIdPgFieldType(table, pgSchema);
|
|
133
128
|
let chainIdColumn = Stdlib_Option.map(Table.getChainIdField(table), Table.getPgDbFieldName);
|
|
134
129
|
let params = [Table.encodeIdsToJson(table, ids)];
|
|
135
|
-
if (chainIdColumn !== undefined && chainId !== undefined) {
|
|
136
|
-
params.push(Primitive_option.valFromOption(chainId));
|
|
137
|
-
}
|
|
138
130
|
return sql.unsafe(makeBackfillHistoryQuery(pgSchema, table.tableName, entityIndex, idPgType, chainIdColumn, chainId), params, {prepare: true});
|
|
139
131
|
}
|
|
140
132
|
|
|
@@ -144,8 +136,6 @@ function rollback(sql, pgSchema, entityName, entityIndex, rollbackTargetCheckpoi
|
|
|
144
136
|
|
|
145
137
|
let checkpointIdFieldType = "UInt64";
|
|
146
138
|
|
|
147
|
-
let maxPgTableNameLength = 63;
|
|
148
|
-
|
|
149
139
|
export {
|
|
150
140
|
RowAction,
|
|
151
141
|
changeFieldName,
|
|
@@ -154,7 +144,6 @@ export {
|
|
|
154
144
|
changeFieldType,
|
|
155
145
|
unsafeCheckpointIdSchema,
|
|
156
146
|
makeSetUpdateSchema,
|
|
157
|
-
maxPgTableNameLength,
|
|
158
147
|
historyTablePrefix,
|
|
159
148
|
historyTableName,
|
|
160
149
|
makeKeyColumns,
|
package/src/db/InternalTable.res
CHANGED
|
@@ -5,7 +5,167 @@ let isPrimaryKey = true
|
|
|
5
5
|
let isNullable = true
|
|
6
6
|
let isIndex = true
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Postgres SQLSTATE for "undefined_table" — what a read gets when the schema was
|
|
9
|
+
// initialized by an older envio that didn't have the table.
|
|
10
|
+
let undefinedTableSqlState = "42P01"
|
|
11
|
+
|
|
12
|
+
@get external getSqlStateCode: JsExn.t => option<string> = "code"
|
|
13
|
+
|
|
14
|
+
let isUndefinedTable = exn =>
|
|
15
|
+
switch exn->JsExn.anyToExnInternal {
|
|
16
|
+
| JsExn(e) => e->getSqlStateCode === Some(undefinedTableSqlState)
|
|
17
|
+
| _ => false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The array type an unnest binds a chain-id column to. Resolved from the
|
|
21
|
+
// config's mode, so every internal query casts the parameter the same way the
|
|
22
|
+
// column was created.
|
|
23
|
+
let chainIdArrayType = (~pgSchema, ~chainIdMode: ChainId.mode) =>
|
|
24
|
+
Table.getPgFieldType(
|
|
25
|
+
~fieldType=ChainId,
|
|
26
|
+
~pgSchema,
|
|
27
|
+
~isArray=true,
|
|
28
|
+
~isNumericArrayAsText=false,
|
|
29
|
+
~isNullable=false,
|
|
30
|
+
~chainIdMode,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// The canonical contract ids. Written once at initialize from the config's
|
|
34
|
+
// contract names in byte order, so an id names the same contract on every
|
|
35
|
+
// chain and across restarts; read back on resume, where the stored mapping is
|
|
36
|
+
// what every address row means.
|
|
37
|
+
module EnvioContracts = {
|
|
38
|
+
let table = mkTable(
|
|
39
|
+
"envio_contracts",
|
|
40
|
+
~fields=[
|
|
41
|
+
mkField("id", SmallInt, ~fieldSchema=S.int, ~isPrimaryKey),
|
|
42
|
+
mkField("name", String, ~fieldSchema=S.string),
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
let makeInsertQuery = (~pgSchema) =>
|
|
47
|
+
`INSERT INTO "${pgSchema}"."${table.tableName}" ("id", "name")
|
|
48
|
+
SELECT * FROM unnest($1::${(SmallInt: Postgres.columnType :> string)}[],$2::${(Text: Postgres.columnType :> string)}[]);`
|
|
49
|
+
|
|
50
|
+
// `contractNames` is the canonical list: a name's position is its id.
|
|
51
|
+
let insert = (sql, ~pgSchema, ~contractNames: array<string>) =>
|
|
52
|
+
sql
|
|
53
|
+
->Postgres.preparedUnsafe(
|
|
54
|
+
makeInsertQuery(~pgSchema),
|
|
55
|
+
(contractNames->Array.mapWithIndex((_, idx) => idx), contractNames)->(
|
|
56
|
+
Utils.magic: ((array<int>, array<string>)) => unknown
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
->Utils.Promise.ignoreValue
|
|
60
|
+
|
|
61
|
+
// Ordered by id, so the result is the canonical list itself. None when the
|
|
62
|
+
// schema has no such table: it was written by an envio that predates the
|
|
63
|
+
// contract mapping, and every address row in it is shaped differently — so a
|
|
64
|
+
// resume has to stop at the compat check rather than at a missing column.
|
|
65
|
+
let read = async (sql, ~pgSchema): option<array<string>> =>
|
|
66
|
+
try {
|
|
67
|
+
let rows: array<{
|
|
68
|
+
"name": string,
|
|
69
|
+
}> = await sql->Postgres.unsafe(
|
|
70
|
+
`SELECT "name" FROM "${pgSchema}"."${table.tableName}" ORDER BY "id";`,
|
|
71
|
+
)
|
|
72
|
+
Some(rows->Array.map(row => row["name"]))
|
|
73
|
+
} catch {
|
|
74
|
+
| exn => isUndefinedTable(exn) ? None : throw(exn)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module EnvioAddresses = {
|
|
79
|
+
let name = "envio_addresses"
|
|
80
|
+
|
|
81
|
+
let table = mkTable(
|
|
82
|
+
name,
|
|
83
|
+
~fields=[
|
|
84
|
+
mkField("chain_id", ChainId, ~fieldSchema=ChainId.schema, ~isPrimaryKey),
|
|
85
|
+
// The field schemas are unused: this table is read and written by the
|
|
86
|
+
// hand-written queries below, never through the generic row encoding.
|
|
87
|
+
mkField("address", Bytea, ~fieldSchema=S.string, ~isPrimaryKey),
|
|
88
|
+
mkField("contract_id", SmallInt, ~fieldSchema=S.int, ~isPrimaryKey),
|
|
89
|
+
mkField("registration_block", Int32, ~fieldSchema=S.int),
|
|
90
|
+
],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
let makeInsertQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
|
|
94
|
+
let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
|
|
95
|
+
`INSERT INTO "${pgSchema}"."${table.tableName}" ("chain_id", "address", "contract_id", "registration_block")
|
|
96
|
+
SELECT * FROM unnest($1::${chainIdArrayType},$2::${(Bytea: Postgres.columnType :> string)}[],$3::${(SmallInt: Postgres.columnType :> string)}[],$4::${(Integer: Postgres.columnType :> string)}[])
|
|
97
|
+
ON CONFLICT ("chain_id", "address", "contract_id") DO NOTHING;`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let insert = (
|
|
101
|
+
sql,
|
|
102
|
+
~pgSchema,
|
|
103
|
+
~rows: array<AddressRows.row>,
|
|
104
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
105
|
+
) => {
|
|
106
|
+
let chainIds = []
|
|
107
|
+
let addresses = []
|
|
108
|
+
let contractIds = []
|
|
109
|
+
let registrationBlocks = []
|
|
110
|
+
rows->Array.forEach(row => {
|
|
111
|
+
chainIds->Array.push(row.chainId)->ignore
|
|
112
|
+
addresses->Array.push(row.address)->ignore
|
|
113
|
+
contractIds->Array.push(row.contractId)->ignore
|
|
114
|
+
registrationBlocks->Array.push(row.registrationBlock)->ignore
|
|
115
|
+
})
|
|
116
|
+
sql
|
|
117
|
+
->Postgres.preparedUnsafe(
|
|
118
|
+
makeInsertQuery(~pgSchema, ~chainIdMode),
|
|
119
|
+
(
|
|
120
|
+
chainIds,
|
|
121
|
+
sql->Postgres.typed(addresses, Postgres.byteaArrayOid),
|
|
122
|
+
contractIds,
|
|
123
|
+
registrationBlocks,
|
|
124
|
+
)->(Utils.magic: ((array<ChainId.t>, unknown, array<int>, array<int>)) => unknown),
|
|
125
|
+
)
|
|
126
|
+
->Utils.Promise.ignoreValue
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let makeDeleteQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
|
|
130
|
+
let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
|
|
131
|
+
`DELETE FROM "${pgSchema}"."${table.tableName}"
|
|
132
|
+
USING unnest($1::${chainIdArrayType},$2::${(Bytea: Postgres.columnType :> string)}[],$3::${(SmallInt: Postgres.columnType :> string)}[]) AS dead(chain_id, address, contract_id)
|
|
133
|
+
WHERE "${table.tableName}"."chain_id" = dead.chain_id
|
|
134
|
+
AND "${table.tableName}"."address" = dead.address
|
|
135
|
+
AND "${table.tableName}"."contract_id" = dead.contract_id;`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let delete = (
|
|
139
|
+
sql,
|
|
140
|
+
~pgSchema,
|
|
141
|
+
~keys: array<AddressRows.key>,
|
|
142
|
+
~chainIdMode: ChainId.mode=Int32,
|
|
143
|
+
) => {
|
|
144
|
+
let chainIds = []
|
|
145
|
+
let addresses = []
|
|
146
|
+
let contractIds = []
|
|
147
|
+
keys->Array.forEach(key => {
|
|
148
|
+
chainIds->Array.push(key.chainId)->ignore
|
|
149
|
+
addresses->Array.push(key.address)->ignore
|
|
150
|
+
contractIds->Array.push(key.contractId)->ignore
|
|
151
|
+
})
|
|
152
|
+
sql
|
|
153
|
+
->Postgres.preparedUnsafe(
|
|
154
|
+
makeDeleteQuery(~pgSchema, ~chainIdMode),
|
|
155
|
+
(chainIds, sql->Postgres.typed(addresses, Postgres.byteaArrayOid), contractIds)->(
|
|
156
|
+
Utils.magic: ((array<ChainId.t>, unknown, array<int>)) => unknown
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
->Utils.Promise.ignoreValue
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let makeGetRowsQuery = (~pgSchema) =>
|
|
163
|
+
`SELECT "chain_id" as "chainId",
|
|
164
|
+
"address" as "address",
|
|
165
|
+
"contract_id" as "contractId",
|
|
166
|
+
"registration_block" as "registrationBlock"
|
|
167
|
+
FROM "${pgSchema}"."${table.tableName}";`
|
|
168
|
+
}
|
|
9
169
|
|
|
10
170
|
module Chains = {
|
|
11
171
|
type progressFields = [
|
|
@@ -17,6 +177,7 @@ module Chains = {
|
|
|
17
177
|
type field = [
|
|
18
178
|
| progressFields
|
|
19
179
|
| #id
|
|
180
|
+
| #ecosystem
|
|
20
181
|
| #start_block
|
|
21
182
|
| #end_block
|
|
22
183
|
| #max_reorg_depth
|
|
@@ -29,6 +190,7 @@ module Chains = {
|
|
|
29
190
|
|
|
30
191
|
let fields: array<field> = [
|
|
31
192
|
#id,
|
|
193
|
+
#ecosystem,
|
|
32
194
|
#start_block,
|
|
33
195
|
#end_block,
|
|
34
196
|
#max_reorg_depth,
|
|
@@ -57,6 +219,7 @@ module Chains = {
|
|
|
57
219
|
|
|
58
220
|
type t = {
|
|
59
221
|
@as("id") id: ChainId.t,
|
|
222
|
+
@as("ecosystem") ecosystem: string,
|
|
60
223
|
@as("start_block") startBlock: int,
|
|
61
224
|
@as("end_block") endBlock: Null.t<int>,
|
|
62
225
|
@as("max_reorg_depth") maxReorgDepth: int,
|
|
@@ -70,6 +233,10 @@ module Chains = {
|
|
|
70
233
|
"envio_chains",
|
|
71
234
|
~fields=[
|
|
72
235
|
mkField((#id: field :> string), ChainId, ~fieldSchema=ChainId.schema, ~isPrimaryKey),
|
|
236
|
+
// Which ecosystem the chain belongs to (evm / fuel / svm). Chain ids are
|
|
237
|
+
// only unique within an ecosystem (HOS-1880: Svm ids are Envio-assigned),
|
|
238
|
+
// so consumers should treat (ecosystem, id) as the chain's identity.
|
|
239
|
+
mkField((#ecosystem: field :> string), String, ~fieldSchema=S.string),
|
|
73
240
|
// Values populated from config
|
|
74
241
|
mkField((#start_block: field :> string), Int32, ~fieldSchema=S.int),
|
|
75
242
|
mkField((#end_block: field :> string), Int32, ~fieldSchema=S.null(S.int), ~isNullable),
|
|
@@ -104,6 +271,7 @@ module Chains = {
|
|
|
104
271
|
let initialFromConfig = (chainConfig: Config.chain) => {
|
|
105
272
|
{
|
|
106
273
|
id: chainConfig.id,
|
|
274
|
+
ecosystem: (chainConfig.ecosystem: Ecosystem.name :> string),
|
|
107
275
|
startBlock: chainConfig.startBlock,
|
|
108
276
|
endBlock: chainConfig.endBlock->Null.fromOption,
|
|
109
277
|
maxReorgDepth: chainConfig.maxReorgDepth,
|
|
@@ -131,6 +299,7 @@ module Chains = {
|
|
|
131
299
|
let value = initialValues->(Utils.magic: t => dict<unknown>)->Dict.get((field :> string))
|
|
132
300
|
switch typeof(value) {
|
|
133
301
|
| #object => "NULL"
|
|
302
|
+
| #string => `'${value->(Utils.magic: option<unknown> => string)}'`
|
|
134
303
|
| #number => value->(Utils.magic: option<unknown> => int)->Int.toString
|
|
135
304
|
| #bigint => value->(Utils.magic: option<unknown> => bigint)->BigInt.toString
|
|
136
305
|
| #boolean => value->(Utils.magic: option<unknown> => bool) ? "true" : "false"
|
|
@@ -189,7 +358,7 @@ WHERE "${(#id: field :> string)}" = $2
|
|
|
189
358
|
timestampCaughtUpToHeadOrEndblock: Null.t<Date.t>,
|
|
190
359
|
numEventsProcessed: float,
|
|
191
360
|
progressBlockNumber: int,
|
|
192
|
-
|
|
361
|
+
addressRows: AddressRows.seedRows,
|
|
193
362
|
sourceBlockNumber: int,
|
|
194
363
|
}
|
|
195
364
|
|
|
@@ -206,67 +375,30 @@ WHERE "${(#id: field :> string)}" = $2
|
|
|
206
375
|
FROM "${pgSchema}"."${table.tableName}";`
|
|
207
376
|
}
|
|
208
377
|
|
|
209
|
-
type rawIndexingAddress = {
|
|
210
|
-
chainId: ChainId.t,
|
|
211
|
-
address: Address.t,
|
|
212
|
-
contractName: string,
|
|
213
|
-
registrationBlock: int,
|
|
214
|
-
}
|
|
215
|
-
|
|
216
378
|
// Addresses are read as plain rows rather than aggregated per chain with
|
|
217
379
|
// json_agg: a single chain's aggregate can exceed V8's max string length
|
|
218
380
|
// (postgres.js decodes the column with Buffer.toString and throws
|
|
219
381
|
// ERR_STRING_TOO_LONG). Grouping happens in JS instead — see getInitialState.
|
|
220
|
-
let makeGetIndexingAddressesQuery = (~pgSchema) => {
|
|
221
|
-
// envio_addresses.id is a composite "{chainId}-{address}" string produced by
|
|
222
|
-
// Config.EnvioAddresses.makeId; extract the address by taking everything
|
|
223
|
-
// after the first '-'. Keep in sync with makeId / getAddress.
|
|
224
|
-
`SELECT "chain_id" as "chainId",
|
|
225
|
-
SUBSTRING("id" FROM POSITION('-' IN "id") + 1) as "address",
|
|
226
|
-
"contract_name" as "contractName",
|
|
227
|
-
"registration_block" as "registrationBlock"
|
|
228
|
-
FROM "${pgSchema}"."${EnvioAddresses.table.tableName}";`
|
|
229
|
-
}
|
|
230
|
-
|
|
231
382
|
let getInitialState = async (sql, ~pgSchema) => {
|
|
232
|
-
let (rawInitialStates,
|
|
383
|
+
let (rawInitialStates, rawAddressRows) = await Promise.all2((
|
|
233
384
|
sql
|
|
234
385
|
->Postgres.unsafe(makeGetInitialStateQuery(~pgSchema))
|
|
235
386
|
->(Utils.magic: promise<array<unknown>> => promise<array<rawInitialState>>),
|
|
236
387
|
sql
|
|
237
|
-
->Postgres.unsafe(
|
|
238
|
-
->(Utils.magic: promise<array<unknown>> => promise<array<
|
|
388
|
+
->Postgres.unsafe(EnvioAddresses.makeGetRowsQuery(~pgSchema))
|
|
389
|
+
->(Utils.magic: promise<array<unknown>> => promise<array<AddressRows.row>>),
|
|
239
390
|
))
|
|
240
391
|
|
|
241
|
-
let
|
|
242
|
-
rawIndexingAddresses->Array.forEach(row => {
|
|
243
|
-
// BIGINT chain ids come back as strings; normalizing here keeps the
|
|
244
|
-
// grouping key identical to the one derived from the chains rows below.
|
|
245
|
-
let key = row.chainId->ChainId.normalizeOrThrow->ChainId.toString
|
|
246
|
-
let addresses = switch indexingAddressesByChainId->Dict.get(key) {
|
|
247
|
-
| Some(addresses) => addresses
|
|
248
|
-
| None =>
|
|
249
|
-
let addresses: array<Internal.indexingAddress> = []
|
|
250
|
-
indexingAddressesByChainId->Dict.set(key, addresses)
|
|
251
|
-
addresses
|
|
252
|
-
}
|
|
253
|
-
addresses
|
|
254
|
-
->Array.push({
|
|
255
|
-
address: row.address,
|
|
256
|
-
contractName: row.contractName,
|
|
257
|
-
registrationBlock: row.registrationBlock,
|
|
258
|
-
})
|
|
259
|
-
->ignore
|
|
260
|
-
})
|
|
392
|
+
let addressRowsByChainId = rawAddressRows->AddressRows.group
|
|
261
393
|
|
|
262
394
|
rawInitialStates->Array.map(rawInitialState => {
|
|
263
395
|
let id = rawInitialState.id->ChainId.normalizeOrThrow
|
|
264
396
|
{
|
|
265
397
|
...rawInitialState,
|
|
266
398
|
id,
|
|
267
|
-
|
|
268
|
-
->Dict.
|
|
269
|
-
->Option.getOr(
|
|
399
|
+
addressRows: addressRowsByChainId
|
|
400
|
+
->Utils.Dict.dangerouslyGetNonOption(id->ChainId.toString)
|
|
401
|
+
->Option.getOr(AddressRows.emptySeedRows()),
|
|
270
402
|
}
|
|
271
403
|
})
|
|
272
404
|
}
|
|
@@ -362,23 +494,13 @@ module EnvioInfo = {
|
|
|
362
494
|
],
|
|
363
495
|
)
|
|
364
496
|
|
|
365
|
-
// Postgres SQLSTATE for "undefined_table" — what we get when the schema
|
|
366
|
-
// was initialized by an older envio that didn't have `envio_info`.
|
|
367
|
-
let undefinedTableSqlState = "42P01"
|
|
368
|
-
|
|
369
|
-
@get external getCode: JsExn.t => option<string> = "code"
|
|
370
|
-
|
|
371
497
|
let read = async (sql, ~pgSchema): option<JSON.t> => {
|
|
372
498
|
let rows: array<{
|
|
373
499
|
"config": string,
|
|
374
500
|
}> = try await sql->Postgres.unsafe(
|
|
375
501
|
`SELECT "config" FROM "${pgSchema}"."${table.tableName}" LIMIT 1;`,
|
|
376
502
|
) catch {
|
|
377
|
-
| exn =>
|
|
378
|
-
switch exn->JsExn.anyToExnInternal {
|
|
379
|
-
| JsExn(e) if e->getCode === Some(undefinedTableSqlState) => []
|
|
380
|
-
| _ => throw(exn)
|
|
381
|
-
}
|
|
503
|
+
| exn => isUndefinedTable(exn) ? [] : throw(exn)
|
|
382
504
|
}
|
|
383
505
|
rows->Array.get(0)->Option.map(row => row["config"]->JSON.parseOrThrow)
|
|
384
506
|
}
|
|
@@ -476,14 +598,7 @@ WHERE cp."${(#block_hash: field :> string)}" IS NOT NULL
|
|
|
476
598
|
}
|
|
477
599
|
|
|
478
600
|
let makeInsertCheckpointQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => {
|
|
479
|
-
let chainIdArrayType =
|
|
480
|
-
~fieldType=ChainId,
|
|
481
|
-
~pgSchema,
|
|
482
|
-
~isArray=true,
|
|
483
|
-
~isNumericArrayAsText=false,
|
|
484
|
-
~isNullable=false,
|
|
485
|
-
~chainIdMode,
|
|
486
|
-
)
|
|
601
|
+
let chainIdArrayType = chainIdArrayType(~pgSchema, ~chainIdMode)
|
|
487
602
|
`INSERT INTO "${pgSchema}"."${table.tableName}" ("${(#id: field :> string)}", "${(#chain_id: field :> string)}", "${(#block_number: field :> string)}", "${(#block_hash: field :> string)}", "${(#events_processed: field :> string)}")
|
|
488
603
|
SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${chainIdArrayType},$3::${(Integer: Postgres.columnType :> string)}[],$4::${(Text: Postgres.columnType :> string)}[],$5::${(Integer: Postgres.columnType :> string)}[]);`
|
|
489
604
|
}
|
|
@@ -648,6 +763,7 @@ module Views = {
|
|
|
648
763
|
`CREATE VIEW "${pgSchema}"."${metaViewName}" AS
|
|
649
764
|
SELECT
|
|
650
765
|
"${(#id: Chains.field :> string)}" AS "chainId",
|
|
766
|
+
"${(#ecosystem: Chains.field :> string)}" AS "ecosystem",
|
|
651
767
|
"${(#start_block: Chains.field :> string)}" AS "startBlock",
|
|
652
768
|
"${(#end_block: Chains.field :> string)}" AS "endBlock",
|
|
653
769
|
"${(#progress_block: Chains.field :> string)}" AS "progressBlock",
|
|
@@ -666,6 +782,7 @@ ORDER BY "${(#id: Chains.field :> string)}";`
|
|
|
666
782
|
SELECT
|
|
667
783
|
"${(#source_block: Chains.field :> string)}" AS "block_height",
|
|
668
784
|
"${(#id: Chains.field :> string)}" AS "chain_id",
|
|
785
|
+
"${(#ecosystem: Chains.field :> string)}" AS "ecosystem",
|
|
669
786
|
"${(#end_block: Chains.field :> string)}" AS "end_block",
|
|
670
787
|
"${(#first_event_block: Chains.field :> string)}" AS "first_event_block_number",
|
|
671
788
|
"${(#_is_hyper_sync: Chains.field :> string)}" AS "is_hyper_sync",
|