envio 3.9.0 → 3.10.0-subgraph

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.
Files changed (143) hide show
  1. package/evm.schema.json +26 -0
  2. package/fuel.schema.json +26 -0
  3. package/index.d.ts +41 -8
  4. package/package.json +6 -7
  5. package/src/Batch.res +9 -0
  6. package/src/ChainState.res +35 -39
  7. package/src/ChainState.res.mjs +14 -11
  8. package/src/ChainState.resi +15 -7
  9. package/src/Config.res +61 -44
  10. package/src/Config.res.mjs +56 -19
  11. package/src/Core.res +19 -0
  12. package/src/Core.res.mjs +11 -0
  13. package/src/CrossChainState.res +15 -23
  14. package/src/CrossChainState.res.mjs +10 -18
  15. package/src/CrossChainState.resi +4 -1
  16. package/src/Ecosystem.res +7 -2
  17. package/src/Envio.res +6 -4
  18. package/src/EventConfigBuilder.res +169 -26
  19. package/src/EventConfigBuilder.res.mjs +124 -15
  20. package/src/EventProcessing.res +6 -6
  21. package/src/EventProcessing.res.mjs +8 -6
  22. package/src/HandlerLoader.res +20 -8
  23. package/src/HandlerLoader.res.mjs +11 -2
  24. package/src/HandlerRegister.res +21 -4
  25. package/src/HandlerRegister.res.mjs +24 -5
  26. package/src/InMemoryStore.res +9 -7
  27. package/src/InMemoryStore.res.mjs +3 -3
  28. package/src/IndexerState.res +43 -6
  29. package/src/IndexerState.res.mjs +36 -7
  30. package/src/IndexerState.resi +4 -4
  31. package/src/Internal.res +16 -17
  32. package/src/Metrics.res +72 -6
  33. package/src/Metrics.res.mjs +30 -2
  34. package/src/Persistence.res +36 -30
  35. package/src/Persistence.res.mjs +1 -20
  36. package/src/PgStorage.res +104 -56
  37. package/src/PgStorage.res.mjs +57 -35
  38. package/src/PruneStaleHistory.res +85 -52
  39. package/src/PruneStaleHistory.res.mjs +63 -37
  40. package/src/Rollback.res +68 -45
  41. package/src/Rollback.res.mjs +38 -19
  42. package/src/RollbackCommit.res +7 -9
  43. package/src/RollbackCommit.res.mjs +5 -6
  44. package/src/SafeCheckpointTracking.res +11 -11
  45. package/src/SafeCheckpointTracking.res.mjs +7 -6
  46. package/src/SimulateItems.res +33 -18
  47. package/src/SimulateItems.res.mjs +26 -22
  48. package/src/Sink.res +38 -26
  49. package/src/Sink.res.mjs +21 -16
  50. package/src/TestIndexer.res +5 -5
  51. package/src/TestIndexer.res.mjs +1 -1
  52. package/src/UserContext.res +358 -51
  53. package/src/UserContext.res.mjs +271 -35
  54. package/src/Utils.res +170 -0
  55. package/src/Utils.res.mjs +153 -0
  56. package/src/Writing.res +2 -2
  57. package/src/Writing.res.mjs +1 -1
  58. package/src/bindings/ClickHouse.res +366 -698
  59. package/src/bindings/ClickHouse.res.mjs +371 -421
  60. package/src/bindings/ClickHouseSink.res +337 -0
  61. package/src/bindings/ClickHouseSink.res.mjs +246 -0
  62. package/src/bindings/EventSource.res +8 -2
  63. package/src/bindings/NodeJs.res +6 -0
  64. package/src/bindings/Vitest.res +23 -0
  65. package/src/bindings/Vitest.res.mjs +3 -0
  66. package/src/bindings/WebSocket.res +9 -10
  67. package/src/db/CheckpointBounds.res +53 -0
  68. package/src/db/CheckpointBounds.res.mjs +44 -0
  69. package/src/db/EntityFilter.res +36 -17
  70. package/src/db/EntityFilter.res.mjs +25 -14
  71. package/src/db/EntityHistory.res +47 -22
  72. package/src/db/EntityHistory.res.mjs +19 -12
  73. package/src/db/InternalTable.res +84 -35
  74. package/src/db/InternalTable.res.mjs +56 -23
  75. package/src/db/RollbackFloors.res +55 -0
  76. package/src/db/RollbackFloors.res.mjs +92 -0
  77. package/src/db/Table.res +22 -4
  78. package/src/db/Table.res.mjs +30 -9
  79. package/src/sources/Evm.res +2 -0
  80. package/src/sources/Evm.res.mjs +2 -0
  81. package/src/sources/EvmHyperSyncSource.res +3 -3
  82. package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
  83. package/src/sources/EvmRpcWs.res +124 -0
  84. package/src/sources/EvmRpcWs.res.mjs +117 -0
  85. package/src/sources/Fuel.res +2 -0
  86. package/src/sources/Fuel.res.mjs +2 -0
  87. package/src/sources/FuelHyperSyncSource.res +1 -1
  88. package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
  89. package/src/sources/HeightFeed.res +568 -0
  90. package/src/sources/HeightFeed.res.mjs +452 -0
  91. package/src/sources/HeightStream.res +257 -0
  92. package/src/sources/HeightStream.res.mjs +194 -0
  93. package/src/sources/HyperSync.res +5 -0
  94. package/src/sources/HyperSync.res.mjs +3 -0
  95. package/src/sources/HyperSync.resi +4 -0
  96. package/src/sources/HyperSyncSSE.res +49 -0
  97. package/src/sources/HyperSyncSSE.res.mjs +79 -0
  98. package/src/sources/RpcSource.res +1 -1
  99. package/src/sources/RpcSource.res.mjs +2 -2
  100. package/src/sources/Source.res +54 -1
  101. package/src/sources/Source.res.mjs +25 -0
  102. package/src/sources/SourceManager.res +220 -227
  103. package/src/sources/SourceManager.res.mjs +139 -164
  104. package/src/sources/SourceManager.resi +8 -0
  105. package/src/sources/Svm.res +2 -0
  106. package/src/sources/Svm.res.mjs +2 -0
  107. package/src/sources/SvmHyperSyncClient.res +70 -127
  108. package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
  109. package/src/sources/SvmHyperSyncSource.res +18 -15
  110. package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
  111. package/src/subgraph/blocks.ts +176 -0
  112. package/src/subgraph/calls.ts +217 -0
  113. package/src/subgraph/conformance.ts +102 -0
  114. package/src/subgraph/division.ts +133 -0
  115. package/src/subgraph/errors.ts +90 -0
  116. package/src/subgraph/graph-ts-types/VERSION +2 -0
  117. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  118. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  119. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  120. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  121. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  122. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  123. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  124. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  125. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  126. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  127. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  128. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  129. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  130. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  131. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  132. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  133. package/src/subgraph/graph-ts.ts +1948 -0
  134. package/src/subgraph/hosts.ts +148 -0
  135. package/src/subgraph/runtime.ts +863 -0
  136. package/src/subgraph/scope.ts +66 -0
  137. package/svm.schema.json +6 -81
  138. package/src/MemoryStorage.res +0 -729
  139. package/src/MemoryStorage.res.mjs +0 -586
  140. package/src/sources/HyperSyncHeightStream.res +0 -129
  141. package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
  142. package/src/sources/RpcWebSocketHeightStream.res +0 -175
  143. package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
package/evm.schema.json CHANGED
@@ -126,6 +126,17 @@
126
126
  "type": "null"
127
127
  }
128
128
  ]
129
+ },
130
+ "bytes_type": {
131
+ "description": "How the `Bytes` scalar in schema.graphql is represented. `hex` keeps 0x-prefixed hex strings stored as text, `uint8array` exposes `Uint8Array` values in handlers and stores raw bytes (BYTEA in Postgres, String in ClickHouse). (default: hex)",
132
+ "anyOf": [
133
+ {
134
+ "$ref": "#/$defs/BytesType"
135
+ },
136
+ {
137
+ "type": "null"
138
+ }
139
+ ]
129
140
  }
130
141
  },
131
142
  "additionalProperties": false,
@@ -758,6 +769,21 @@
758
769
  "checksum",
759
770
  "lowercase"
760
771
  ]
772
+ },
773
+ "BytesType": {
774
+ "description": "How the schema.graphql `Bytes` scalar reaches handlers and storage.",
775
+ "oneOf": [
776
+ {
777
+ "description": "`0x`-prefixed hex strings, stored as text.",
778
+ "type": "string",
779
+ "const": "hex"
780
+ },
781
+ {
782
+ "description": "`Uint8Array` values, stored as raw bytes (`BYTEA` in Postgres, `String` in ClickHouse).",
783
+ "type": "string",
784
+ "const": "uint8array"
785
+ }
786
+ ]
761
787
  }
762
788
  }
763
789
  }
package/fuel.schema.json CHANGED
@@ -83,6 +83,17 @@
83
83
  "boolean",
84
84
  "null"
85
85
  ]
86
+ },
87
+ "bytes_type": {
88
+ "description": "How the `Bytes` scalar in schema.graphql is represented. `hex` keeps 0x-prefixed hex strings stored as text, `uint8array` exposes `Uint8Array` values in handlers and stores raw bytes (BYTEA in Postgres, String in ClickHouse). (default: hex)",
89
+ "anyOf": [
90
+ {
91
+ "$ref": "#/$defs/BytesType"
92
+ },
93
+ {
94
+ "type": "null"
95
+ }
96
+ ]
86
97
  }
87
98
  },
88
99
  "additionalProperties": false,
@@ -464,6 +475,21 @@
464
475
  }
465
476
  }
466
477
  ]
478
+ },
479
+ "BytesType": {
480
+ "description": "How the schema.graphql `Bytes` scalar reaches handlers and storage.",
481
+ "oneOf": [
482
+ {
483
+ "description": "`0x`-prefixed hex strings, stored as text.",
484
+ "type": "string",
485
+ "const": "hex"
486
+ },
487
+ {
488
+ "description": "`Uint8Array` values, stored as raw bytes (`BYTEA` in Postgres, `String` in ClickHouse).",
489
+ "type": "string",
490
+ "const": "uint8array"
491
+ }
492
+ ]
467
493
  }
468
494
  }
469
495
  }
package/index.d.ts CHANGED
@@ -1423,7 +1423,7 @@ type SvmSelectedInstruction<Fields extends SvmFieldsSelection, ProgInstr> = {
1423
1423
  readonly instructionName: string;
1424
1424
  readonly discriminator: string;
1425
1425
  readonly programId: SvmInstrField<Fields, "programId", string>;
1426
- readonly data: SvmInstrField<Fields, "data", string>;
1426
+ readonly data: SvmInstrField<Fields, "data", Uint8Array>;
1427
1427
  readonly path: SvmInstrField<Fields, "path", readonly number[]>;
1428
1428
  readonly isInner: SvmInstrField<Fields, "isInner", boolean>;
1429
1429
  readonly args: SvmInstrField<
@@ -1484,6 +1484,37 @@ export type SvmOnInstructionHandlerArgs<
1484
1484
  Instruction extends keyof SvmProgramsT[Program] & string = never,
1485
1485
  > = SvmOnInstructionArgsFor<SvmInstruction<Fields, Program, Instruction>, GlobalConfig>;
1486
1486
 
1487
+ /** The named accounts of a configured instruction. Falls back to an open
1488
+ * record when the instruction carries no resolved schema, so its filter keys
1489
+ * are unconstrained rather than unusable. */
1490
+ type SvmAccountsOf<Program extends string, Instruction extends string> =
1491
+ SvmConfiguredInstruction<Program, Instruction>["accounts"];
1492
+
1493
+ /** One AND-group of account narrowings: every named account must match one of
1494
+ * its listed pubkeys. Names are the instruction's own account names. */
1495
+ export type SvmAccountsFilter<Accounts> = {
1496
+ readonly [K in keyof Accounts]?: string | readonly string[];
1497
+ };
1498
+
1499
+ /** The `where` option value of `indexer.onInstruction`.
1500
+ *
1501
+ * `accounts` accepts either a single AND-group of account narrowings, or an
1502
+ * array of them (OR semantics). `block.slot._gte` promotes to the
1503
+ * registration's startBlock and overrides the chain-level `start_block` — use
1504
+ * it to restrict per-instruction processing without touching `config.yaml`.
1505
+ * Only `_gte` is supported here; use `indexer.onSlot` for `_lte` / `_every`. */
1506
+ export type SvmOnInstructionWhere<Accounts> = {
1507
+ /** Match only inner (CPI-invoked) or only outer instructions. Absent
1508
+ * matches both. */
1509
+ readonly isInner?: boolean;
1510
+ readonly accounts?: SvmAccountsFilter<Accounts> | readonly SvmAccountsFilter<Accounts>[];
1511
+ readonly block?: {
1512
+ readonly slot?: {
1513
+ readonly _gte?: number;
1514
+ };
1515
+ };
1516
+ };
1517
+
1487
1518
  /** Options for an SVM `indexer.onInstruction` registration. */
1488
1519
  export type SvmOnInstructionOptions<
1489
1520
  P extends string = string,
@@ -1493,10 +1524,11 @@ export type SvmOnInstructionOptions<
1493
1524
  /** Program name as declared under `chains[].programs[].name` in
1494
1525
  * `config.yaml`. */
1495
1526
  readonly program: P;
1496
- /** Instruction name as declared under
1497
- * `chains[].programs[].instructions[].name` in `config.yaml`. */
1527
+ /** Instruction name from the program's IDL, or from
1528
+ * `chains[].programs[].instructions[].name` when the layout is inline. */
1498
1529
  readonly instruction: I;
1499
1530
  readonly fields?: Fields & SvmFieldsLiteralCheck<Fields>;
1531
+ readonly where?: SvmOnInstructionWhere<SvmAccountsOf<P, I>>;
1500
1532
  };
1501
1533
 
1502
1534
  /** Handler function for an SVM `indexer.onInstruction` registration. Takes
@@ -1736,9 +1768,10 @@ type SvmEcosystem<Config extends IndexerConfigTypes = GlobalConfig> =
1736
1768
  }
1737
1769
  ? {
1738
1770
  /**
1739
- * Register an instruction handler. Dispatch matches on
1740
- * `(programId, discriminator)` from the YAML config.
1741
- * Handler `fields` is the only source of payload selection.
1771
+ * Register an instruction handler. `program` and `instruction`
1772
+ * name an entry from the IDL or YAML. Dispatch uses that
1773
+ * instruction's discriminator. Handler `fields` is the only
1774
+ * source of payload selection.
1742
1775
  */
1743
1776
  readonly onInstruction: <
1744
1777
  P extends keyof Programs & string,
@@ -1905,8 +1938,8 @@ type SvmSimulateItem<Config extends IndexerConfigTypes = GlobalConfig> =
1905
1938
  path?: readonly number[];
1906
1939
  /** Override the program id. Defaults to the configured `program_id`. */
1907
1940
  programId?: string;
1908
- /** Raw instruction data, `0x`-prefixed hex. */
1909
- data?: string;
1941
+ /** Raw instruction data. Defaults to the configured discriminator bytes. */
1942
+ data?: Uint8Array;
1910
1943
  /** Whether this is a CPI-invoked inner instruction. */
1911
1944
  isInner?: boolean;
1912
1945
  /** Decoded args. Keys match the instruction's arg names. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.9.0",
3
+ "version": "3.10.0-subgraph",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -41,7 +41,6 @@
41
41
  "licenses"
42
42
  ],
43
43
  "dependencies": {
44
- "@clickhouse/client": "1.17.0",
45
44
  "@elastic/ecs-pino-format": "1.4.0",
46
45
  "@fuel-ts/crypto": "0.96.1",
47
46
  "@fuel-ts/errors": "0.96.1",
@@ -69,10 +68,10 @@
69
68
  "tsx": "4.21.0"
70
69
  },
71
70
  "optionalDependencies": {
72
- "envio-linux-x64": "3.9.0",
73
- "envio-linux-x64-musl": "3.9.0",
74
- "envio-linux-arm64": "3.9.0",
75
- "envio-darwin-x64": "3.9.0",
76
- "envio-darwin-arm64": "3.9.0"
71
+ "envio-linux-x64": "3.10.0-subgraph",
72
+ "envio-linux-x64-musl": "3.10.0-subgraph",
73
+ "envio-linux-arm64": "3.10.0-subgraph",
74
+ "envio-darwin-x64": "3.10.0-subgraph",
75
+ "envio-darwin-arm64": "3.10.0-subgraph"
77
76
  }
78
77
  }
package/src/Batch.res CHANGED
@@ -189,6 +189,15 @@ let addReorgCheckpoints = (
189
189
  }
190
190
  }
191
191
 
192
+ // Checkpoint ids are handed out in ascending order as each chain's items are
193
+ // walked in block order, so within a chain a higher id always means a later
194
+ // block. Rollback preserves that: it deletes the chain's ids above its target
195
+ // before any higher one is allocated, so the ids the re-indexed blocks get are
196
+ // above everything the chain still holds. An isolated rollback leans on the
197
+ // invariant — it deletes by `chain_id = c AND id > target`, which is only the
198
+ // chain's stale suffix if ids and blocks agree on order within the chain. Ids
199
+ // interleave freely *across* chains, which is exactly why that predicate can't
200
+ // be the id bound alone.
192
201
  let prepareBatch = (
193
202
  ~checkpointIdBeforeBatch,
194
203
  ~chainsBeforeBatch: dict<chainBeforeBatch>,
@@ -310,7 +310,11 @@ let makeInternal = (
310
310
  chainId,
311
311
  endpointUrl: hypersyncUrl,
312
312
  apiToken,
313
- onEventRegistrations,
313
+ onEventRegistrations: onEventRegistrations->(
314
+ Utils.magic: array<Internal.onEventRegistration> => array<
315
+ Internal.svmOnEventRegistration,
316
+ >
317
+ ),
314
318
  clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
315
319
  addressStore,
316
320
  }),
@@ -1205,21 +1209,17 @@ let markReady = (cs: t, ~readyAt) =>
1205
1209
  cs.timestampCaughtUpToHeadOrEndblock = Some(readyAt)
1206
1210
  }
1207
1211
 
1208
- // Roll a chain back to a reorg target. With a progress diff, restore fetch/
1209
- // safe-checkpoint/progress state to `newProgressBlockNumber`. A reorg chain
1210
- // with no diff entry still rewinds fetch state + stores to the target -
1211
- // otherwise the stale block hash stays in the store and re-triggers the same
1212
- // reorg.
1213
- // Returns the address registrations the storage has to delete along with it —
1214
- // the store is what decides which ones died, so the two halves of a rollback
1215
- // can't disagree.
1216
- let rollback = (
1217
- cs: t,
1218
- ~newProgressBlockNumber,
1219
- ~eventsProcessedDiff,
1220
- ~rollbackTargetBlockNumber,
1221
- ~isReorgChain,
1222
- ): array<AddressRows.key> => {
1212
+ type progressDiff = {blockNumber: int, eventsProcessed: float}
1213
+
1214
+ type rolledBackTo =
1215
+ | RecomputedProgress(progressDiff)
1216
+ | ForkBlock(int)
1217
+ | Untouched
1218
+
1219
+ // Returns the address registrations the storage has to delete along with the
1220
+ // chain the store is what decides which ones died, so the two halves of a
1221
+ // rollback can't disagree.
1222
+ let rollback = (cs: t, ~rolledBackTo: rolledBackTo): array<AddressRows.key> => {
1223
1223
  let rollbackTo = targetBlockNumber => {
1224
1224
  let {fetchState, rolledBackAddresses} =
1225
1225
  cs.fetchState->FetchState.rollback(
@@ -1234,15 +1234,15 @@ let rollback = (
1234
1234
  })
1235
1235
  }
1236
1236
 
1237
- switch newProgressBlockNumber {
1238
- | Some(newProgressBlockNumber) =>
1239
- let newTotalEventsProcessed =
1240
- cs.numEventsProcessed -.
1241
- // Both dicts are populated together per progress-diff row, so a chain with
1242
- // a progress diff always has an events-processed diff too.
1243
- eventsProcessedDiff->Option.getOrThrow(
1244
- ~message="Missing events-processed diff for rolled-back chain",
1245
- )
1237
+ switch rolledBackTo {
1238
+ | RecomputedProgress({blockNumber, eventsProcessed}) =>
1239
+ // A rollback only ever takes a chain back. The diff recomputes progress from
1240
+ // the checkpoints still in the database, so a chain that an unwritten
1241
+ // rollback already took below them to a fork block only that rollback
1242
+ // knew would be handed their MIN back and moved forward onto blocks it
1243
+ // never re-indexed.
1244
+ let newProgressBlockNumber = Pervasives.min(blockNumber, cs.committedProgressBlockNumber)
1245
+ let newTotalEventsProcessed = cs.numEventsProcessed -. eventsProcessed
1246
1246
 
1247
1247
  switch cs.safeCheckpointTracking {
1248
1248
  | Some(safeCheckpointTracking) =>
@@ -1260,19 +1260,15 @@ let rollback = (
1260
1260
  cs.processingBlockNumber = newProgressBlockNumber
1261
1261
  cs.numEventsProcessed = newTotalEventsProcessed
1262
1262
  rolledBackAddresses
1263
- | None =>
1264
- if isReorgChain {
1265
- let rolledBackAddresses = rollbackTo(rollbackTargetBlockNumber)
1266
- cs.transactionStore->TransactionStore.rollback(rollbackTargetBlockNumber)
1267
- cs.blockStore->BlockStore.rollback(rollbackTargetBlockNumber)
1268
- cs.committedProgressBlockNumber = Pervasives.min(
1269
- cs.committedProgressBlockNumber,
1270
- rollbackTargetBlockNumber,
1271
- )
1272
- cs.processingBlockNumber = Pervasives.min(cs.processingBlockNumber, rollbackTargetBlockNumber)
1273
- rolledBackAddresses
1274
- } else {
1275
- []
1276
- }
1263
+ | ForkBlock(forkBlock) =>
1264
+ // Rewinds the stores too, not just progress: otherwise the stale block hash
1265
+ // survives and re-triggers the same reorg.
1266
+ let rolledBackAddresses = rollbackTo(forkBlock)
1267
+ cs.transactionStore->TransactionStore.rollback(forkBlock)
1268
+ cs.blockStore->BlockStore.rollback(forkBlock)
1269
+ cs.committedProgressBlockNumber = Pervasives.min(cs.committedProgressBlockNumber, forkBlock)
1270
+ cs.processingBlockNumber = Pervasives.min(cs.processingBlockNumber, forkBlock)
1271
+ rolledBackAddresses
1272
+ | Untouched => []
1277
1273
  }
1278
1274
  }
@@ -857,7 +857,7 @@ function markReady(cs, readyAt) {
857
857
  }
858
858
  }
859
859
 
860
- function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTargetBlockNumber, isReorgChain) {
860
+ function rollback(cs, rolledBackTo) {
861
861
  let rollbackTo = targetBlockNumber => {
862
862
  let match = FetchState.rollback(cs.fetchState, cs.addressStore, targetBlockNumber);
863
863
  cs.fetchState = match.fetchState;
@@ -867,8 +867,13 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
867
867
  contractId: param.contractId
868
868
  }));
869
869
  };
870
- if (newProgressBlockNumber !== undefined) {
871
- let newTotalEventsProcessed = cs.numEventsProcessed - Stdlib_Option.getOrThrow(eventsProcessedDiff, "Missing events-processed diff for rolled-back chain");
870
+ if (typeof rolledBackTo !== "object") {
871
+ return [];
872
+ }
873
+ if (rolledBackTo.TAG === "RecomputedProgress") {
874
+ let match = rolledBackTo._0;
875
+ let newProgressBlockNumber = Primitive_int.min(match.blockNumber, cs.committedProgressBlockNumber);
876
+ let newTotalEventsProcessed = cs.numEventsProcessed - match.eventsProcessed;
872
877
  let safeCheckpointTracking = cs.safeCheckpointTracking;
873
878
  if (safeCheckpointTracking !== undefined) {
874
879
  cs.safeCheckpointTracking = SafeCheckpointTracking.rollback(safeCheckpointTracking, newProgressBlockNumber);
@@ -881,14 +886,12 @@ function rollback(cs, newProgressBlockNumber, eventsProcessedDiff, rollbackTarge
881
886
  cs.numEventsProcessed = newTotalEventsProcessed;
882
887
  return rolledBackAddresses;
883
888
  }
884
- if (!isReorgChain) {
885
- return [];
886
- }
887
- let rolledBackAddresses$1 = rollbackTo(rollbackTargetBlockNumber);
888
- cs.transactionStore.rollback(rollbackTargetBlockNumber);
889
- cs.blockStore.rollback(rollbackTargetBlockNumber);
890
- cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, rollbackTargetBlockNumber);
891
- cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, rollbackTargetBlockNumber);
889
+ let forkBlock = rolledBackTo._0;
890
+ let rolledBackAddresses$1 = rollbackTo(forkBlock);
891
+ cs.transactionStore.rollback(forkBlock);
892
+ cs.blockStore.rollback(forkBlock);
893
+ cs.committedProgressBlockNumber = Primitive_int.min(cs.committedProgressBlockNumber, forkBlock);
894
+ cs.processingBlockNumber = Primitive_int.min(cs.processingBlockNumber, forkBlock);
892
895
  return rolledBackAddresses$1;
893
896
  }
894
897
 
@@ -157,10 +157,18 @@ let enterReorgThreshold: t => unit
157
157
  let advanceAfterBatch: (t, ~batch: Batch.t, ~enteringReorgThreshold: bool) => unit
158
158
  let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => unit
159
159
  let markReady: (t, ~readyAt: Date.t) => unit
160
- let rollback: (
161
- t,
162
- ~newProgressBlockNumber: option<int>,
163
- ~eventsProcessedDiff: option<float>,
164
- ~rollbackTargetBlockNumber: int,
165
- ~isReorgChain: bool,
166
- ) => array<AddressRows.key>
160
+ // What a rollback's progress diff says about one chain: the block its surviving
161
+ // checkpoints put it back to, and the events the deleted ones carried away. One
162
+ // record because one diff row yields both — they are never known separately.
163
+ type progressDiff = {blockNumber: int, eventsProcessed: float}
164
+
165
+ // Where a rollback leaves one chain.
166
+ type rolledBackTo =
167
+ | RecomputedProgress(progressDiff)
168
+ // The reorg chain with no checkpoint left at or below the target, so only the
169
+ // fork block says where it goes.
170
+ | ForkBlock(int)
171
+ // A chain this rollback doesn't reach.
172
+ | Untouched
173
+
174
+ let rollback: (t, ~rolledBackTo: rolledBackTo) => array<AddressRows.key>
package/src/Config.res CHANGED
@@ -127,6 +127,9 @@ type t = {
127
127
  userEntitiesByName: dict<Internal.entityConfig>,
128
128
  userEntities: array<Internal.entityConfig>,
129
129
  allEnums: array<Table.enumConfig<Table.enum>>,
130
+ // Set only in subgraph mode: the translated manifest, which is what makes
131
+ // the subgraph runtime take over handler registration.
132
+ subgraph: option<JSON.t>,
130
133
  }
131
134
 
132
135
  type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("realtime") Realtime
@@ -177,24 +180,6 @@ let publicConfigChainSchema = S.schema(s =>
177
180
  let svmEventDescriptorSchema = S.schema(s =>
178
181
  {
179
182
  "discriminator": s.matches(S.option(S.string)),
180
- "discriminatorByteLen": s.matches(S.int),
181
- // An array of AND-groups OR-ed together: the CLI normalizes both the flat
182
- // and `any_of` YAML shapes to `Vec<Vec<SvmAccountFilterJson>>`.
183
- "accountFilters": s.matches(
184
- S.option(
185
- S.array(
186
- S.array(
187
- S.schema(s =>
188
- {
189
- "position": s.matches(S.int),
190
- "values": s.matches(S.array(S.string)),
191
- }
192
- ),
193
- ),
194
- ),
195
- ),
196
- ),
197
- "isInner": s.matches(S.option(S.bool)),
198
183
  "accounts": s.matches(S.option(S.array(S.string))),
199
184
  "args": s.matches(S.option(S.json(~validate=false))),
200
185
  }
@@ -341,6 +326,7 @@ let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Tab
341
326
 
342
327
  let (fieldType, baseSchema) = switch typ {
343
328
  | "string" => (Table.String, S.string->S.toUnknown)
329
+ | "bytes" => (Table.Bytea, Utils.Schema.bytes->S.toUnknown)
344
330
  | "boolean" => (Table.Boolean, S.bool->S.toUnknown)
345
331
  | "int" => (Table.Int32, S.int->S.toUnknown)
346
332
  | "bigint" => (Table.BigInt({precision: ?prop["precision"]}), Utils.BigInt.schema->S.toUnknown)
@@ -374,13 +360,13 @@ let getFieldTypeAndSchema = (prop, ~enumConfigsByName: dict<Table.enumConfig<Tab
374
360
  | other => JsError.throwWithMessage("Unknown field type in entity config: " ++ other)
375
361
  }
376
362
 
377
- let fieldSchema = if isArray {
378
- S.array(baseSchema)->S.toUnknown
379
- } else {
380
- baseSchema
363
+ let fieldSchema = switch (fieldType, isArray) {
364
+ | (Table.Bytea, true) => Utils.Schema.bytesArray->S.toUnknown
365
+ | (_, true) => S.array(baseSchema)->S.toUnknown
366
+ | (_, false) => baseSchema
381
367
  }
382
368
  let fieldSchema = if isNullable {
383
- S.null(fieldSchema)->S.toUnknown
369
+ Utils.Schema.nullTolerant(fieldSchema)->S.toUnknown
384
370
  } else {
385
371
  fieldSchema
386
372
  }
@@ -563,6 +549,7 @@ let publicConfigSchema = S.schema(s =>
563
549
  "svm": s.matches(S.option(publicConfigEcosystemSchema)),
564
550
  "enums": s.matches(S.option(S.dict(S.array(S.string)))),
565
551
  "entities": s.matches(S.option(S.array(entityJsonSchema))),
552
+ "subgraph": s.matches(S.option(S.json(~validate=false))),
566
553
  }
567
554
  )
568
555
 
@@ -753,11 +740,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
753
740
  Utils.magic: _ => {
754
741
  "svm": option<{
755
742
  "discriminator": option<string>,
756
- "discriminatorByteLen": int,
757
- "accountFilters": option<
758
- array<array<{"position": int, "values": array<string>}>>,
759
- >,
760
- "isInner": option<bool>,
761
743
  "accounts": option<array<string>>,
762
744
  "args": option<JSON.t>,
763
745
  }>,
@@ -770,25 +752,11 @@ let fromPublic = (publicConfigJson: JSON.t) => {
770
752
  `SVM instruction ${contractName}.${eventName} is missing the "svm" descriptor in internal config`,
771
753
  )
772
754
  }
773
- let accountFilters =
774
- svm["accountFilters"]
775
- ->Option.getOr([])
776
- ->Array.map(group =>
777
- group->Array.map(
778
- af => {
779
- Internal.position: af["position"],
780
- values: af["values"]->SvmTypes.Pubkey.fromStringsUnsafe,
781
- },
782
- )
783
- )
784
755
  (EventConfigBuilder.buildSvmInstructionEventConfig(
785
756
  ~contractName,
786
757
  ~instructionName=eventName,
787
758
  ~programId,
788
759
  ~discriminator=svm["discriminator"],
789
- ~discriminatorByteLen=svm["discriminatorByteLen"],
790
- ~accountFilters,
791
- ~isInner=svm["isInner"],
792
760
  ~accounts=svm["accounts"]->Option.getOr([]),
793
761
  ~args=svm["args"]->Option.getOr(JSON.Null),
794
762
  ~definedTypes=svmDefinedTypes,
@@ -1067,9 +1035,18 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1067
1035
  userEntitiesByName,
1068
1036
  userEntities,
1069
1037
  allEnums,
1038
+ subgraph: publicConfig["subgraph"],
1070
1039
  }
1071
1040
  }
1072
1041
 
1042
+ // With no cross-chain entity, a reorg on one chain can never have changed a row
1043
+ // another chain owns, so its rollback stays isolated to that chain instead of
1044
+ // dragging every sibling back with it. A single chain has no sibling to spare,
1045
+ // and narrowing its rollback would only buy it a predicate that always holds.
1046
+ let isIsolatedMultichain = (config: t) =>
1047
+ config.chainMap->ChainMap.keys->Array.length > 1 &&
1048
+ config.userEntities->Array.every(entityConfig => !entityConfig.crossChain)
1049
+
1073
1050
  // Canonicalize a user-provided address to the configured casing so it matches
1074
1051
  // addresses parsed from config.yaml during routing. HyperSync/RPC data arrives
1075
1052
  // already canonical; only user-land input (simulate srcAddress, contractRegister
@@ -1141,8 +1118,17 @@ let getEventConfig = (config: t, ~contractName, ~eventName, ~chainId: option<Cha
1141
1118
  })
1142
1119
  }
1143
1120
 
1144
- let shouldSaveHistory = (config, ~isInReorgThreshold) =>
1145
- config.shouldSaveFullHistory || (config.shouldRollbackOnReorg && isInReorgThreshold)
1121
+ // A chain that can't be rolled back (maxReorgDepth = 0) has no history to keep,
1122
+ // unless a cross-chain entity lets another chain's rollback reach its rows.
1123
+ let shouldSaveHistory = (config, ~isInReorgThreshold, ~chainId: option<ChainId.t>=?) =>
1124
+ config.shouldSaveFullHistory ||
1125
+ (config.shouldRollbackOnReorg &&
1126
+ isInReorgThreshold &&
1127
+ switch chainId {
1128
+ | Some(chainId) if config->isIsolatedMultichain =>
1129
+ (config.chainMap->ChainMap.get(chainId)).maxReorgDepth > 0
1130
+ | _ => true
1131
+ })
1146
1132
 
1147
1133
  let shouldPruneHistory = (config, ~isInReorgThreshold) =>
1148
1134
  !config.shouldSaveFullHistory && (config.shouldRollbackOnReorg && isInReorgThreshold)
@@ -1203,6 +1189,10 @@ let stripSensitiveData = (json: JSON.t): JSON.t => {
1203
1189
  stripChains(obj->Dict.get("evm"))
1204
1190
  stripChains(obj->Dict.get("fuel"))
1205
1191
  stripChains(obj->Dict.get("svm"))
1192
+ // The subgraph blob is how the runtime finds mappings. None of it
1193
+ // describes stored data — that's already in `evm` / `entities` — so a
1194
+ // specVersion bump or a mapping-path edit must not force a reset.
1195
+ obj->Utils.Dict.deleteInPlace("subgraph")
1206
1196
  }
1207
1197
  | _ => ()
1208
1198
  }
@@ -1362,6 +1352,33 @@ let throwIfIncompatible = (
1362
1352
  }
1363
1353
  }
1364
1354
 
1355
+ let throwIfResumeIncompatible = (
1356
+ ~storedEnvioInfo: option<JSON.t>,
1357
+ ~storedContractMapping: ContractMapping.t,
1358
+ ~envioInfo: JSON.t,
1359
+ ~contractMapping: ContractMapping.t,
1360
+ ~resetCommand: string,
1361
+ ~runCommand: option<string>,
1362
+ ) => {
1363
+ let changedPaths = switch storedEnvioInfo {
1364
+ | None => ["storage was initialized by an older envio version"]
1365
+ | Some(stored) => diffPaths(~stored, ~current=envioInfo)
1366
+ }
1367
+ let changedPaths =
1368
+ storedContractMapping->ContractMapping.isEqual(contractMapping)
1369
+ ? changedPaths
1370
+ : changedPaths->Array.concat(["contracts"])
1371
+ let hasClickhouse = switch envioInfo {
1372
+ | Object(d) =>
1373
+ switch d->Dict.get("storage") {
1374
+ | Some(Object(s)) => s->Dict.get("clickhouse") == Some(Boolean(true))
1375
+ | _ => false
1376
+ }
1377
+ | _ => false
1378
+ }
1379
+ throwIfIncompatible(changedPaths, ~resetCommand, ~runCommand, ~hasClickhouse)
1380
+ }
1381
+
1365
1382
  // The returned value is a pure function of the JSON: it holds only event
1366
1383
  // definitions, never handler/contractRegister/where state (that's layered on
1367
1384
  // separately as `HandlerRegister.registrationsByChainId`). That purity is