From f067f9ab80a9c17f5a3bde15d0aeec92bb207a5d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 15:59:50 +0000 Subject: [PATCH 1/6] Support chain IDs above int32 via a resolved chain-ID mode Chain ids beyond 2^31-1 (Tron Shasta 2494104990, Nile 3448148188) were rejected by the `S.int` config schema and would not fit the INTEGER columns the internal tables declare. The CLI now derives a `ChainIdMode` from the maximum active chain id (`<= i32::MAX` -> Int32, otherwise Int64) and emits it as `chainIdMode` in the public config, which is also the persisted envio_info fingerprint. It sits in its own diff tier, so a resume against a schema built for the other mode fails with the standard incompatible-config message instead of silently truncating ids. Ids above Number.MAX_SAFE_INTEGER are rejected at parse time. At runtime a new `ChainId` module carries the float-backed representation plus the validating schema, which also normalizes the strings Postgres BIGINT and ClickHouse UInt64 columns return. The internal tables declare their chain-id columns with a `ChainId` field type that resolves to INTEGER/Int32 or BIGINT/UInt64 from the mode, so they stay module-level constants and small-id projects keep generating identical DDL. Generated APIs are unchanged for small ids; a wide config falls back to `type chainId = ChainId.t` in ReScript (integer polyvariants are int32-bound) while TypeScript keeps its numeric literal union. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ECqzHdVS4cLxfCXPw8zpb6 --- .../cli/src/config_parsing/public_config.rs | 6 +- .../cli/src/config_parsing/system_config.rs | 47 ++++ .../src/hbs_templating/codegen_templates.rs | 112 +++++++-- ...al_config_json_code_generated_for_evm.snap | 1 + ...l_config_json_code_generated_for_fuel.snap | 2 +- ...al_config_json_code_generated_for_svm.snap | 1 + ...nal_config_json_code_with_all_options.snap | 1 + ...son_code_with_lowercase_contract_name.snap | 1 + ...fig_json_code_with_multiple_contracts.snap | 2 +- ...al_config_json_code_with_no_contracts.snap | 1 + .../test/lib_tests/ChainIdMode_test.res | 233 ++++++++++++++++++ packages/envio/src/ChainId.res | 55 +++++ packages/envio/src/ChainId.resi | 26 ++ packages/envio/src/ChainMap.res | 8 +- packages/envio/src/Config.res | 24 +- packages/envio/src/PgStorage.res | 83 ++++++- packages/envio/src/Sink.res | 4 +- packages/envio/src/bindings/ClickHouse.res | 22 +- packages/envio/src/db/InternalTable.res | 43 ++-- packages/envio/src/db/Table.res | 12 +- .../test/lib_tests/PgStorage_test.res | 2 +- 21 files changed, 617 insertions(+), 69 deletions(-) create mode 100644 packages/envio-tests/test/lib_tests/ChainIdMode_test.res create mode 100644 packages/envio/src/ChainId.res create mode 100644 packages/envio/src/ChainId.resi diff --git a/packages/cli/src/config_parsing/public_config.rs b/packages/cli/src/config_parsing/public_config.rs index f83e6f57cd..3bd5e144f3 100644 --- a/packages/cli/src/config_parsing/public_config.rs +++ b/packages/cli/src/config_parsing/public_config.rs @@ -3,8 +3,8 @@ use super::{ field_types, human_config::{self, evm::For, ColumnNameFormat}, system_config::{ - self, field_type_to_arg_type, named_field_to_arg_def, Abi, Ecosystem, EventKind, - FuelEventKind, SvmAbi, SvmSchemaSource, SystemConfig, + self, field_type_to_arg_type, named_field_to_arg_def, Abi, ChainIdMode, Ecosystem, + EventKind, FuelEventKind, SvmAbi, SvmSchemaSource, SystemConfig, }, }; use crate::{config_parsing::chain_helpers::Network, utils::text::Capitalize}; @@ -39,6 +39,7 @@ pub(crate) struct PublicConfigJson<'a> { save_full_history: bool, #[serde(skip_serializing_if = "is_false")] raw_events: bool, + chain_id_mode: ChainIdMode, storage: StorageConfig, #[serde(skip_serializing_if = "Option::is_none")] evm: Option>, @@ -842,6 +843,7 @@ impl SystemConfig { rollback_on_reorg: cfg.rollback_on_reorg, save_full_history: cfg.save_full_history, raw_events: cfg.enable_raw_events, + chain_id_mode: cfg.chain_id_mode, storage: (&cfg.storage).into(), evm, fuel, diff --git a/packages/cli/src/config_parsing/system_config.rs b/packages/cli/src/config_parsing/system_config.rs index 5c24b76913..35f5fd7cb7 100644 --- a/packages/cli/src/config_parsing/system_config.rs +++ b/packages/cli/src/config_parsing/system_config.rs @@ -299,12 +299,50 @@ pub fn get_envio_version(envio_package_dir: Option<&str>) -> Result { Ok(format!("file:{}", pkg.to_string_lossy())) } +/// Widest scalar the internal chain-id columns need. Derived once from the +/// maximum active chain id and carried through the public config, so a resume +/// against a schema built for the other mode is rejected rather than silently +/// truncating ids. +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub enum ChainIdMode { + Int32, + Int64, +} + +/// Chain ids cross the Rust → JSON → JS boundary as plain numbers, so an id +/// above `Number.MAX_SAFE_INTEGER` can't round-trip losslessly. +pub const MAX_SAFE_CHAIN_ID: u64 = 9_007_199_254_740_991; + +impl ChainIdMode { + fn resolve(chains: &ChainMap) -> Result { + let max_id = chains + .values() + .filter(|chain| !chain.skip) + .map(|chain| chain.id) + .max() + .unwrap_or(0); + if max_id > MAX_SAFE_CHAIN_ID { + return Err(anyhow!( + "Chain id {max_id} is above the maximum supported chain id \ + {MAX_SAFE_CHAIN_ID} (Number.MAX_SAFE_INTEGER)." + )); + } + Ok(if max_id <= i32::MAX as u64 { + Self::Int32 + } else { + Self::Int64 + }) + } +} + #[derive(Debug)] pub struct SystemConfig { pub name: String, pub schema_path: String, pub parsed_project_paths: ParsedProjectPaths, pub chains: ChainMap, + pub chain_id_mode: ChainIdMode, pub contracts: ContractMap, pub rollback_on_reorg: bool, pub save_full_history: bool, @@ -1003,6 +1041,8 @@ impl SystemConfig { has_rpc_sync_src, )?; + let chain_id_mode = ChainIdMode::resolve(&chains)?; + Ok(SystemConfig { name: base_config.name.clone(), parsed_project_paths: final_project_paths, @@ -1011,6 +1051,7 @@ impl SystemConfig { .clone() .unwrap_or_else(|| DEFAULT_SCHEMA_PATH.to_string()), chains, + chain_id_mode, contracts, rollback_on_reorg: evm_config.rollback_on_reorg.unwrap_or(true), save_full_history: evm_config.save_full_history.unwrap_or(false), @@ -1149,6 +1190,8 @@ impl SystemConfig { .context("Failed inserting chain at chains map")?; } + let chain_id_mode = ChainIdMode::resolve(&chains)?; + Ok(SystemConfig { name: base_config.name.clone(), parsed_project_paths: final_project_paths, @@ -1157,6 +1200,7 @@ impl SystemConfig { .clone() .unwrap_or_else(|| DEFAULT_SCHEMA_PATH.to_string()), chains, + chain_id_mode, contracts, rollback_on_reorg: false, save_full_history: false, @@ -1290,6 +1334,8 @@ impl SystemConfig { // keep it off for now. let uses_hypersync = svm_config.chains.iter().any(|n| n.experimental.is_some()); + let chain_id_mode = ChainIdMode::resolve(&chains)?; + Ok(SystemConfig { name: svm_config.base.name.clone(), parsed_project_paths: final_project_paths, @@ -1299,6 +1345,7 @@ impl SystemConfig { .clone() .unwrap_or_else(|| DEFAULT_SCHEMA_PATH.to_string()), chains, + chain_id_mode, contracts, rollback_on_reorg: uses_hypersync, save_full_history: false, diff --git a/packages/cli/src/hbs_templating/codegen_templates.rs b/packages/cli/src/hbs_templating/codegen_templates.rs index c995da7479..7219f74b59 100644 --- a/packages/cli/src/hbs_templating/codegen_templates.rs +++ b/packages/cli/src/hbs_templating/codegen_templates.rs @@ -12,7 +12,8 @@ use crate::{ field_types, human_config::HumanConfig, system_config::{ - self, Abi, Ecosystem, EventKind, FuelEventKind, SelectedField, SystemConfig, + self, Abi, ChainIdMode, Ecosystem, EventKind, FuelEventKind, SelectedField, + SystemConfig, }, }, constants::project_paths::{ENVIO_ENV_DTS_FILE, ENVIO_TYPES_FILE}, @@ -1336,14 +1337,20 @@ impl ProjectTemplate { Ecosystem::Svm => "Envio.svmOnSlotArgs => promise", }; - let chain_id_type = format!( - "type chainId = [{}]", - chain_id_cases - .iter() - .map(|chain_id_case| format!("#{}", chain_id_case)) - .collect::>() - .join(" | "), - ); + // ReScript integer polyvariants (`#137`) are int32-bound, so a config + // with a wider id falls back to the opaque runtime representation. + // TypeScript keeps its numeric literal union either way. + let chain_id_type = match cfg.chain_id_mode { + ChainIdMode::Int64 => "type chainId = ChainId.t".to_string(), + ChainIdMode::Int32 => format!( + "type chainId = [{}]", + chain_id_cases + .iter() + .map(|chain_id_case| format!("#{}", chain_id_case)) + .collect::>() + .join(" | "), + ), + }; // Generate indexer types and value let indexer_contract_type = r#"/** Contract configuration with name and ABI. */ @@ -1475,27 +1482,43 @@ type indexer = {{ ), }; - // Generate getChainById function - let get_chain_by_id_cases = chain_configs - .iter() - .map(|chain| { - format!( - " | #{} => indexer.chains.\\\"{}\"", - chain.network_config.id, chain.network_config.id - ) - }) - .collect::>() - .join("\n"); + // Generate getChainById function. `chainId` is only a polyvariant in + // Int32 mode, so the Int64 form looks the key up on the chains record + // (whose fields are already the decimal ids) instead of matching. + let get_chain_by_id = match cfg.chain_id_mode { + ChainIdMode::Int64 => r#"/** Get chain configuration by chain ID. */ +let getChainById = (indexer: indexer, chainId: chainId): indexerChain => { +switch indexer.chains +->(Utils.magic: indexerChains => dict) +->Dict.get(chainId->ChainId.toString) { +| Some(chain) => chain +| None => JsError.throwWithMessage("Chain " ++ chainId->ChainId.toString ++ " is not configured.") +} +}"# + .to_string(), + ChainIdMode::Int32 => { + let get_chain_by_id_cases = chain_configs + .iter() + .map(|chain| { + format!( + " | #{} => indexer.chains.\\\"{}\"", + chain.network_config.id, chain.network_config.id + ) + }) + .collect::>() + .join("\n"); - let get_chain_by_id = format!( - r#"/** Get chain configuration by chain ID with exhaustive pattern matching. */ + format!( + r#"/** Get chain configuration by chain ID with exhaustive pattern matching. */ let getChainById = (indexer: indexer, chainId: chainId): indexerChain => {{ switch chainId {{ {} }} }}"#, - get_chain_by_id_cases - ); + get_chain_by_id_cases + ) + } + }; // Generate Enums and Entities modules let enums_module_code = indent(&generate_enums_code(&gql_enums)); @@ -3486,6 +3509,47 @@ type Vault { } } + #[test] + fn indexer_code_chain_id_type_follows_chain_id_mode() { + let yaml_for = |chain_id: &str| { + format!( + r#" +name: chain-id-mode +chains: + - id: {chain_id} + rpc: + url: https://rpc.example.test + for: sync + start_block: 0 +"# + ) + }; + let schema = "type Transfer {\n id: ID!\n}\n"; + let indexer_code_for = |chain_id: &str| { + let config = SystemConfig::parse_yaml( + &yaml_for(chain_id), + Some(schema), + &HashMap::new(), + &HashMap::new(), + false, + ) + .expect("config should parse"); + super::ProjectTemplate::from_config(&config) + .expect("project template") + .indexer_code + }; + + let int32 = indexer_code_for("2147483647"); + assert!(int32.contains("type chainId = [#2147483647]"), "{int32}"); + assert!(int32.contains("| #2147483647 => indexer.chains.\\\"2147483647\"")); + + // ReScript integer polyvariants can't hold an id above int32, so the + // wide config falls back to the opaque runtime representation. + let int64 = indexer_code_for("2494104990"); + assert!(int64.contains("type chainId = ChainId.t"), "{int64}"); + assert!(int64.contains("chainId->ChainId.toString"), "{int64}"); + } + #[test] fn internal_config_json_code_with_lowercase_contract_name() { let json = get_internal_config_json_helper("lowercase-contract-name.yaml"); diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap index 0a52ef8444..25b3fb06e2 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap @@ -6,6 +6,7 @@ expression: json "version": "0.0.1-dev", "name": "config1", "description": "Gravatar for Ethereum", + "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap index 4468617d73..69d492e836 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap @@ -1,12 +1,12 @@ --- source: packages/cli/src/hbs_templating/codegen_templates.rs -assertion_line: 2761 expression: json --- { "version": "0.0.1-dev", "name": "Fuel indexer", "rollbackOnReorg": false, + "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap index 5c402b0fe5..1274a4fa25 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap @@ -5,6 +5,7 @@ expression: json { "version": "0.0.1-dev", "name": "Solana indexer", + "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap index 245ce17511..4ac8557c75 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap @@ -11,6 +11,7 @@ expression: json "rollbackOnReorg": false, "saveFullHistory": true, "rawEvents": true, + "chainIdMode": "int32", "storage": { "postgres": true, "clickhouse": true diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap index b814a47af4..2a3b488f6d 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap @@ -6,6 +6,7 @@ expression: json "version": "0.0.1-dev", "name": "lowercase-contract-name", "description": "Test config with lowercase contract name", + "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap index 07b9f8f04a..5672d187ee 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap @@ -1,12 +1,12 @@ --- source: packages/cli/src/hbs_templating/codegen_templates.rs -assertion_line: 2794 expression: json --- { "version": "0.0.1-dev", "name": "config2", "description": "Gravatar for Ethereum", + "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap index c56f15c982..aeb200df46 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap @@ -6,6 +6,7 @@ expression: json "version": "0.0.1-dev", "name": "config4", "description": "Gravatar for Ethereum", + "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res new file mode 100644 index 0000000000..5dbfbe5640 --- /dev/null +++ b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res @@ -0,0 +1,233 @@ +open Vitest + +let schema = ` +type Transfer { + id: ID! + amount: BigInt! +} +` + +let parse = (~name, ~chains) => + InternalTestIndexer.fromUserApi( + ~schema, + ~configYaml=` +name: ${name} +chains: +${chains} +`, + ).config + +let evmChain = (~id, ~rpc="https://rpc.example.test") => ` - id: ${id} + rpc: ${rpc} + start_block: 0` + +let maxInt32Config = parse(~name="max-int32", ~chains=evmChain(~id="2147483647")) +let overInt32Config = parse(~name="over-int32", ~chains=evmChain(~id="2147483648")) +let tronConfig = parse( + ~name="tron-testnets", + ~chains=evmChain(~id="2494104990") ++ "\n" ++ evmChain(~id="3448148188"), +) +let multichainConfig = parse( + ~name="multichain", + ~chains=evmChain(~id="1") ++ "\n" ++ evmChain(~id="4503599627370496"), +) +let maxSafeConfig = parse(~name="max-safe", ~chains=evmChain(~id="9007199254740991")) + +let chainsDdl = (config: Config.t) => + PgStorage.makeCreateTableQuery( + InternalTable.Chains.table, + ~pgSchema="test_schema", + ~isNumericArrayAsText=false, + ~chainIdMode=config.chainIdMode, + ) + +let addressesDdl = (config: Config.t) => + PgStorage.makeCreateTableQuery( + Config.EnvioAddresses.table, + ~pgSchema="test_schema", + ~isNumericArrayAsText=false, + ~chainIdMode=config.chainIdMode, + ) + +let rawEventsDdl = (config: Config.t) => + PgStorage.makeCreateTableQuery( + InternalTable.RawEvents.table, + ~pgSchema="test_schema", + ~isNumericArrayAsText=false, + ~chainIdMode=config.chainIdMode, + ) + +describe("ChainIdMode resolution", () => { + it("keeps Int32 at the int32 boundary and widens one above it", t => { + t.expect(( + maxInt32Config.chainIdMode, + overInt32Config.chainIdMode, + tronConfig.chainIdMode, + multichainConfig.chainIdMode, + maxSafeConfig.chainIdMode, + )).toEqual((ChainId.Int32, ChainId.Int64, ChainId.Int64, ChainId.Int64, ChainId.Int64)) + }) + + it("parses wide chain ids losslessly through the public config", t => { + t.expect( + [tronConfig, multichainConfig, maxSafeConfig]->Array.map(config => + config.chainMap->ChainMap.keys->Array.map(ChainMap.Chain.toString) + ), + ).toEqual([ + ["2494104990", "3448148188"], + ["1", "4503599627370496"], + ["9007199254740991"], + ]) + }) + + it("rejects a chain id above Number.MAX_SAFE_INTEGER", t => { + t->toThrowErrorEqual( + () => parse(~name="too-big", ~chains=evmChain(~id="9007199254740992"))->ignore, + "Config parse error: Chain id 9007199254740992 is above the maximum supported chain id 9007199254740991 (Number.MAX_SAFE_INTEGER).", + ) + }) + + it("resolves the mode from the widest chain, not the first one", t => { + t.expect( + parse(~name="wide-second", ~chains=evmChain(~id="1") ++ "\n" ++ evmChain(~id="2147483648")).chainIdMode, + ).toEqual(ChainId.Int64) + }) +}) + +describe("ChainIdMode Postgres schema", () => { + it("keeps INTEGER chain-id columns for small-id projects", t => { + t.expect(( + maxInt32Config->chainsDdl, + maxInt32Config->addressesDdl, + maxInt32Config->rawEventsDdl, + )).toEqual(( + `CREATE TABLE IF NOT EXISTS "test_schema"."envio_chains"("id" INTEGER NOT NULL, "start_block" INTEGER NOT NULL, "end_block" INTEGER, "max_reorg_depth" INTEGER NOT NULL, "buffer_block" INTEGER NOT NULL, "source_block" INTEGER NOT NULL, "first_event_block" INTEGER, "ready_at" TIMESTAMP WITH TIME ZONE NULL, "events_processed" BIGINT NOT NULL, "_is_hyper_sync" BOOLEAN NOT NULL, "progress_block" INTEGER NOT NULL, PRIMARY KEY("id"));`, + `CREATE TABLE IF NOT EXISTS "test_schema"."envio_addresses"("id" TEXT NOT NULL, "chain_id" INTEGER NOT NULL, "registration_block" INTEGER NOT NULL, "registration_log_index" INTEGER NOT NULL, "contract_name" TEXT NOT NULL, PRIMARY KEY("id"));`, + `CREATE TABLE IF NOT EXISTS "test_schema"."raw_events"("chain_id" INTEGER NOT NULL, "event_id" BIGINT NOT NULL, "event_name" TEXT NOT NULL, "contract_name" TEXT NOT NULL, "block_number" INTEGER NOT NULL, "log_index" INTEGER NOT NULL, "src_address" TEXT NOT NULL, "block_hash" TEXT NOT NULL, "block_timestamp" INTEGER NOT NULL, "block_fields" JSONB NOT NULL, "transaction_fields" JSONB NOT NULL, "params" JSONB NOT NULL, "serial" BIGSERIAL, PRIMARY KEY("serial"));`, + )) + }) + + it("widens every chain-id column to BIGINT in Int64 mode", t => { + t.expect(( + tronConfig->chainsDdl, + tronConfig->addressesDdl, + tronConfig->rawEventsDdl, + )).toEqual(( + maxInt32Config->chainsDdl->String.replace(`"id" INTEGER`, `"id" BIGINT`), + maxInt32Config->addressesDdl->String.replace(`"chain_id" INTEGER`, `"chain_id" BIGINT`), + maxInt32Config->rawEventsDdl->String.replace(`"chain_id" INTEGER`, `"chain_id" BIGINT`), + )) + }) + + it("selects the array cast for chain-id parameters from the mode", t => { + t.expect(( + InternalTable.Checkpoints.makeInsertCheckpointQuery( + ~pgSchema="test_schema", + ~chainIdMode=maxInt32Config.chainIdMode, + ), + InternalTable.Checkpoints.makeInsertCheckpointQuery( + ~pgSchema="test_schema", + ~chainIdMode=tronConfig.chainIdMode, + ), + PgStorage.makeInsertUnnestSetQuery( + ~pgSchema="test_schema", + ~table=Config.EnvioAddresses.table, + ~itemSchema=Config.EnvioAddresses.schema->S.toUnknown, + ~isRawEvents=false, + ~chainIdMode=tronConfig.chainIdMode, + )->String.includes("$2::BIGINT[]"), + )).toEqual(( + `INSERT INTO "test_schema"."envio_checkpoints" ("id", "chain_id", "block_number", "block_hash", "events_processed") +SELECT * FROM unnest($1::BIGINT[],$2::INTEGER[],$3::INTEGER[],$4::TEXT[],$5::INTEGER[]);`, + `INSERT INTO "test_schema"."envio_checkpoints" ("id", "chain_id", "block_number", "block_hash", "events_processed") +SELECT * FROM unnest($1::BIGINT[],$2::BIGINT[],$3::INTEGER[],$4::TEXT[],$5::INTEGER[]);`, + true, + )) + }) +}) + +describe("ChainIdMode ClickHouse schema", () => { + it("maps the checkpoints chain_id column from the mode", t => { + let chainIdColumn = (config: Config.t) => + ClickHouse.makeCreateCheckpointsTableQuery( + ~database="db", + ~chainIdMode=config.chainIdMode, + ) + ->String.split("\n") + ->Array.filter(line => line->String.includes("chain_id")) + t.expect((maxInt32Config->chainIdColumn, tronConfig->chainIdColumn)).toEqual(( + [" `chain_id` Int32,"], + [" `chain_id` UInt64,"], + )) + }) +}) + +describe("ChainId runtime representation", () => { + it("round-trips BIGINT results returned as strings", t => { + t.expect(( + "3448148188"->ChainId.normalizeOrThrow->ChainId.toString, + 3448148188.->ChainId.normalizeOrThrow->ChainId.toString, + ChainId.compare("1"->ChainId.normalizeOrThrow, "2147483648"->ChainId.normalizeOrThrow), + ChainId.equal("42"->ChainId.normalizeOrThrow, 42->ChainId.fromInt), + )).toEqual(("3448148188", "3448148188", -1., true)) + }) + + it("rejects values that can't be a chain id", t => { + t.expect( + ["-1", "1.5", "9007199254740992", "abc"]->Array.map(value => + try { + value->ChainId.normalizeOrThrow->ChainId.toString + } catch { + | _ => "rejected" + } + ), + ).toEqual(["rejected", "rejected", "rejected", "rejected"]) + }) +}) + +describe("ChainIdMode generated TypeScript surface", () => { + it("keeps chain.id a number and the id union a numeric literal union", _ => + InternalTestIndexer.fromUserApi( + ~schema, + ~configYaml=` +name: wide-ts-api +${"chains:\n" ++ evmChain(~id="2494104990") ++ "\n" ++ evmChain(~id="3448148188")} +`, + ~handlers=` +import type { EvmChainId } from "envio"; +import { expectType, type TypeEqual } from "ts-expect"; +import { indexer } from "envio"; + +expectType>(true); +expectType(indexer.chains[2494104990].id); +`, + )->ignore + ) +}) + +describe("ChainIdMode compat check", () => { + it("reports a stored/current mode mismatch on its own", t => { + let stored = `{"version": "1.0.0", "chainIdMode": "int32", "name": "demo"}`->JSON.parseOrThrow + let current = `{"version": "1.0.0", "chainIdMode": "int64", "name": "demo"}`->JSON.parseOrThrow + t.expect(Config.diffPaths(~stored, ~current)).toEqual(["chainIdMode"]) + }) + + it("fails the resume with the standard incompatible-config message", t => { + t->toThrowErrorEqual( + () => + Config.throwIfIncompatible( + ["chainIdMode"], + ~resetCommand="envio local db-migrate setup", + ~runCommand=None, + ~hasClickhouse=false, + ), + `The following config changes are incompatible with the existing indexer data: + + - chainIdMode + +Pick one: + 1. Revert the changes above # resume indexing where it left off + 2. envio local db-migrate setup # delete all indexed data and start over`, + ) + }) +}) diff --git a/packages/envio/src/ChainId.res b/packages/envio/src/ChainId.res new file mode 100644 index 0000000000..65cbb4ef54 --- /dev/null +++ b/packages/envio/src/ChainId.res @@ -0,0 +1,55 @@ +// Chain ids are identifiers, never arithmetic operands, so the runtime +// representation is a plain JS number. ReScript's `int` would cap them at +// 2^31-1, which networks like Tron Shasta (2494104990) already exceed. +type t = float + +// Widest scalar the internal chain-id columns need. Resolved by the CLI from +// the maximum active chain id and carried through the public config, so a +// resume against a schema built for the other mode is rejected instead of +// silently truncating ids. +type mode = | @as("int32") Int32 | @as("int64") Int64 + +let modeSchema = S.enum([Int32, Int64]) + +// Number.MAX_SAFE_INTEGER — above this a chain id can't round-trip through +// JSON or a JS number at all. +let maxSafe = 9007199254740991. + +@scope("Number") @val external isSafeInteger: float => bool = "isSafeInteger" + +external fromInt: int => t = "%identity" +external toInt: t => int = "%identity" +external toFloat: t => float = "%identity" + +let toString = (chainId: t) => chainId->Float.toString +let compare = (a: t, b: t) => a < b ? -1. : a > b ? 1. : 0. +let equal = (a: t, b: t) => a === b + +// PostgreSQL BIGINT and ClickHouse UInt64 columns come back as strings (a +// JS number can't hold their full range), so the parser accepts both and +// range-checks the result rather than trusting the driver. +let schema: S.t = S.float->S.preprocess(s => { + parser: value => { + let number = switch value->typeof { + | #string => value->(Utils.magic: unknown => string)->Float.parseFloat + | _ => value->(Utils.magic: unknown => float) + } + if !isSafeInteger(number) || number < 0. { + s.fail( + `Expected a chain id between 0 and ${maxSafe->Float.toString}, received ${value->( + Utils.magic: unknown => string + )}`, + ) + } + number + }, +}) + +// The same runtime schema, typed for the modules that still annotate chain ids +// as `int`. Safe because ReScript's `int` is a JS number at runtime — chain ids +// are only ever compared and stringified, never used in int32 arithmetic. +let intSchema = schema->(Utils.magic: S.t => S.t) + +// Postgres returns BIGINT columns as strings, so raw (schema-less) reads of a +// chain-id column go through this instead of trusting the driver's type. +let normalizeOrThrow = (value: 'a): t => value->S.parseOrThrow(schema) diff --git a/packages/envio/src/ChainId.resi b/packages/envio/src/ChainId.resi new file mode 100644 index 0000000000..a34a1a43c9 --- /dev/null +++ b/packages/envio/src/ChainId.resi @@ -0,0 +1,26 @@ +type t + +type mode = | @as("int32") Int32 | @as("int64") Int64 + +let modeSchema: S.t + +let maxSafe: float + +external fromInt: int => t = "%identity" +external toInt: t => int = "%identity" +external toFloat: t => float = "%identity" + +let toString: t => string +let compare: (t, t) => float +let equal: (t, t) => bool + +let schema: S.t + +// The same runtime schema, typed for the modules that still annotate chain ids +// as `int`. Safe because ReScript's `int` is a JS number at runtime — chain ids +// are only ever compared and stringified, never used in int32 arithmetic. +let intSchema: S.t + +// Postgres returns BIGINT columns as strings, so raw (schema-less) reads of a +// chain-id column go through this instead of trusting the driver's type. +let normalizeOrThrow: 'a => t diff --git a/packages/envio/src/ChainMap.res b/packages/envio/src/ChainMap.res index f210219fe6..449c14c5dc 100644 --- a/packages/envio/src/ChainMap.res +++ b/packages/envio/src/ChainMap.res @@ -1,16 +1,16 @@ module Chain = { - type t = int + type t = ChainId.t external toChainId: t => int = "%identity" - let toString = chainId => chainId->Int.toString + let toString = chainId => chainId->ChainId.toString - let makeUnsafe = (~chainId) => chainId + let makeUnsafe = (~chainId) => chainId->ChainId.fromInt } module ChainIdCmp = Belt.Id.MakeComparable({ type t = Chain.t - let cmp = (a, b) => Int.compare(a->Chain.toChainId, b->Chain.toChainId)->Int.fromFloat + let cmp = (a, b) => ChainId.compare(a, b)->Int.fromFloat }) type t<'a> = Belt.Map.t diff --git a/packages/envio/src/Config.res b/packages/envio/src/Config.res index e215b68b8b..fcf28281ba 100644 --- a/packages/envio/src/Config.res +++ b/packages/envio/src/Config.res @@ -73,6 +73,10 @@ type t = { shouldRollbackOnReorg: bool, shouldSaveFullHistory: bool, storage: storage, + // Widest scalar the internal chain-id columns need, resolved by the CLI from + // the maximum active chain id. Older configs predate the field, and every id + // they can express fits an INTEGER. + chainIdMode: ChainId.mode, chainMap: ChainMap.t, defaultChain: option, ecosystem: Ecosystem.t, @@ -123,7 +127,7 @@ module EnvioAddresses = { let schema = S.schema(s => { id: s.matches(S.string), - chainId: s.matches(S.int), + chainId: s.matches(ChainId.intSchema), registrationBlock: s.matches(S.int), registrationLogIndex: s.matches(S.int), contractName: s.matches(S.string), @@ -133,7 +137,7 @@ module EnvioAddresses = { name, ~fields=[ Table.mkField("id", String, ~isPrimaryKey=true, ~fieldSchema=S.string), - Table.mkField("chain_id", Int32, ~fieldSchema=S.int), + Table.mkField("chain_id", ChainId, ~fieldSchema=ChainId.intSchema), Table.mkField("registration_block", Int32, ~fieldSchema=S.int), // -1 sentinel when registered from a block handler (no log index) Table.mkField("registration_log_index", Int32, ~fieldSchema=S.int), @@ -185,7 +189,7 @@ let chainContractSchema = S.schema(s => let publicConfigChainSchema = S.schema(s => { - "id": s.matches(S.int), + "id": s.matches(ChainId.intSchema), "startBlock": s.matches(S.int), "endBlock": s.matches(S.option(S.int)), "maxReorgDepth": s.matches(S.option(S.int)), @@ -548,6 +552,7 @@ let publicConfigSchema = S.schema(s => "rollbackOnReorg": s.matches(S.option(S.bool)), "saveFullHistory": s.matches(S.option(S.bool)), "rawEvents": s.matches(S.option(S.bool)), + "chainIdMode": s.matches(S.option(ChainId.modeSchema)), "storage": s.matches(publicConfigStorageSchema), "evm": s.matches(S.option(publicConfigEvmSchema)), "fuel": s.matches(S.option(publicConfigEcosystemSchema)), @@ -1027,6 +1032,7 @@ let fromPublic = (publicConfigJson: JSON.t) => { shouldRollbackOnReorg: publicConfig["rollbackOnReorg"]->Option.getOr(true), shouldSaveFullHistory: publicConfig["saveFullHistory"]->Option.getOr(false), storage: globalStorage, + chainIdMode: publicConfig["chainIdMode"]->Option.getOr(Int32), chainMap, defaultChain: chains->Array.get(0), enableRawEvents: publicConfig["rawEvents"]->Option.getOr(false), @@ -1267,7 +1273,17 @@ let diffPaths = (~stored: JSON.t, ~current: JSON.t): array => { switch (stored, current) { | (Object(sObj), Object(cObj)) => - let tiers = [["version"], ["name"], ["storage"], ["evm", "fuel", "svm"], ["entities"]] + // chainIdMode sits right after version: it decides the physical type of + // every chain-id column, so a change to it is reported on its own rather + // than buried under the chain diffs that always accompany it. + let tiers = [ + ["version"], + ["chainIdMode"], + ["name"], + ["storage"], + ["evm", "fuel", "svm"], + ["entities"], + ] let firstHit = tiers->Array.reduce(None, (acc, tier) => switch acc { | Some(_) => acc diff --git a/packages/envio/src/PgStorage.res b/packages/envio/src/PgStorage.res index 5b45d49fee..b8d5cdd4af 100644 --- a/packages/envio/src/PgStorage.res +++ b/packages/envio/src/PgStorage.res @@ -73,7 +73,12 @@ let makeCreateTableIndicesQuery = (table: Table.table, ~pgSchema) => { compositeIndices->Array.map(createCompositeIndex)->Array.joinUnsafe("\n") } -let makeCreateTableQuery = (table: Table.table, ~pgSchema, ~isNumericArrayAsText) => { +let makeCreateTableQuery = ( + table: Table.table, + ~pgSchema, + ~isNumericArrayAsText, + ~chainIdMode: ChainId.mode=Int32, +) => { let fieldsMapped = table ->Table.getFields @@ -83,6 +88,7 @@ let makeCreateTableQuery = (table: Table.table, ~pgSchema, ~isNumericArrayAsText { `"${fieldName}" ${Table.getPgFieldType( + ~chainIdMode, ~fieldType, ~pgSchema, ~isArray, @@ -183,6 +189,7 @@ let makeInitializeTransaction = ( ~entities=[], ~enums=[], ~isEmptyPgSchema=false, + ~chainIdMode: ChainId.mode=Int32, ) => { let generalTables = [ InternalTable.Chains.table, @@ -229,7 +236,7 @@ GRANT ALL ON SCHEMA "${pgSchema}" TO public;`, query := query.contents ++ "\n" ++ - makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText=isHasuraEnabled) + makeCreateTableQuery(table, ~pgSchema, ~isNumericArrayAsText=isHasuraEnabled, ~chainIdMode) }) // Then batch all indices (better performance when tables exist) @@ -366,9 +373,15 @@ let makeLoadAllQuery = (~pgSchema, ~tableName) => { `SELECT * FROM "${pgSchema}"."${tableName}";` } -let makeInsertUnnestSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema, ~isRawEvents) => { +let makeInsertUnnestSetQuery = ( + ~pgSchema, + ~table: Table.table, + ~itemSchema, + ~isRawEvents, + ~chainIdMode: ChainId.mode=Int32, +) => { let {quotedFieldNames, quotedNonPrimaryFieldNames, arrayFieldTypes} = - table->Table.toSqlParams(~schema=itemSchema, ~pgSchema) + table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode) let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table) @@ -396,9 +409,15 @@ SELECT * FROM unnest(${arrayFieldTypes } ++ ";" } -let makeInsertValuesSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema, ~itemsCount) => { +let makeInsertValuesSetQuery = ( + ~pgSchema, + ~table: Table.table, + ~itemSchema, + ~itemsCount, + ~chainIdMode: ChainId.mode=Int32, +) => { let {quotedFieldNames, quotedNonPrimaryFieldNames} = - table->Table.toSqlParams(~schema=itemSchema, ~pgSchema) + table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode) let primaryKeyFieldNames = Table.getPgPrimaryKeyFieldNames(table) let fieldsCount = quotedFieldNames->Array.length @@ -441,8 +460,14 @@ VALUES${placeholders.contents}` ++ // Constants for chunking let maxItemsPerQuery = 500 -let makeTableBatchSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema: S.t<'item>) => { - let {dbSchema, hasArrayField} = table->Table.toSqlParams(~schema=itemSchema, ~pgSchema) +let makeTableBatchSetQuery = ( + ~pgSchema, + ~table: Table.table, + ~itemSchema: S.t<'item>, + ~chainIdMode: ChainId.mode=Int32, +) => { + let {dbSchema, hasArrayField} = + table->Table.toSqlParams(~schema=itemSchema, ~pgSchema, ~chainIdMode) // Should move this to a better place // We need it for the isRawEvents check in makeTableBatchSet @@ -466,7 +491,7 @@ let makeTableBatchSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema: S.t<' if (isRawEvents || !hasArrayField) && !isHistoryUpdate { { - "query": makeInsertUnnestSetQuery(~pgSchema, ~table, ~itemSchema, ~isRawEvents), + "query": makeInsertUnnestSetQuery(~pgSchema, ~table, ~itemSchema, ~isRawEvents, ~chainIdMode), "convertOrThrow": S.compile( S.unnest(dbSchema), ~input=Value, @@ -483,6 +508,7 @@ let makeTableBatchSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema: S.t<' ~table, ~itemSchema, ~itemsCount=maxItemsPerQuery, + ~chainIdMode, ), "convertOrThrow": S.compile( S.unnest(itemSchema)->S.preprocess(_ => { @@ -571,7 +597,14 @@ let classifyWriteError = (~specificError: ref>, ~table: Table.table, // WeakMap for caching table batch set queries let setQueryCache = Utils.WeakMap.make() -let setOrThrow = async (sql, ~items, ~table: Table.table, ~itemSchema, ~pgSchema) => { +let setOrThrow = async ( + sql, + ~items, + ~table: Table.table, + ~itemSchema, + ~pgSchema, + ~chainIdMode: ChainId.mode=Int32, +) => { if items->Array.length === 0 { () } else { @@ -583,6 +616,7 @@ let setOrThrow = async (sql, ~items, ~table: Table.table, ~itemSchema, ~pgSchema ~pgSchema, ~table, ~itemSchema=itemSchema->S.toUnknown, + ~chainIdMode, ) setQueryCache->Utils.WeakMap.set(table, newQuery)->ignore newQuery @@ -603,7 +637,13 @@ let setOrThrow = async (sql, ~items, ~table: Table.table, ~itemSchema, ~pgSchema let response = isFullChunk ? sql->Postgres.preparedUnsafe(data["query"], params) : sql->Postgres.unpreparedUnsafe( - makeInsertValuesSetQuery(~pgSchema, ~table, ~itemSchema, ~itemsCount=chunkSize), + makeInsertValuesSetQuery( + ~pgSchema, + ~table, + ~itemSchema, + ~itemsCount=chunkSize, + ~chainIdMode, + ), params, ) responses->Array.push(response)->ignore @@ -857,6 +897,7 @@ let rec writeBatch = async ( ~escapeTables=?, ) => { try { + let chainIdMode = config.chainIdMode let shouldSaveHistory = config->Config.shouldSaveHistory(~isInReorgThreshold) let specificError = ref(None) @@ -901,6 +942,7 @@ let rec writeBatch = async ( ~table=InternalTable.RawEvents.table, ~itemSchema=InternalTable.RawEvents.schema, ~pgSchema, + ~chainIdMode, ) }, ~items=rawEvents) } catch { @@ -1020,6 +1062,7 @@ let rec writeBatch = async ( ~itemSchema=entityHistory.setChangeSchema, ~table=entityHistory.table, ~pgSchema, + ~chainIdMode, ), ) ->ignore @@ -1036,6 +1079,7 @@ let rec writeBatch = async ( ~table=entityConfig.table, ~itemSchema=entityConfig.schema, ~pgSchema, + ~chainIdMode, ), ) } @@ -1131,6 +1175,7 @@ let rec writeBatch = async ( ~checkpointBlockNumbers=batch.checkpointBlockNumbers, ~checkpointBlockHashes=batch.checkpointBlockHashes, ~checkpointEventsProcessed=batch.checkpointEventsProcessed, + ~chainIdMode, ) ) } @@ -1277,6 +1322,7 @@ let make = ( ~pgDatabase, ~pgPassword, ~isHasuraEnabled, + ~chainIdMode: ChainId.mode=Int32, ~sink: option=?, ~onInitialize=?, ~onNewTables=?, @@ -1484,6 +1530,7 @@ let make = ( ~chainConfigs, ~isEmptyPgSchema=schemaTableNames->Utils.Array.isEmpty, ~isHasuraEnabled, + ~chainIdMode, ) // Execute all queries within a single transaction for integrity. // The envio_info row is written in the same transaction so a successful @@ -1511,9 +1558,17 @@ let make = ( }) }) if ids->Array.length > 0 { + let addrChainIdArrayType = Table.getPgFieldType( + ~fieldType=ChainId, + ~pgSchema, + ~isArray=true, + ~isNumericArrayAsText=false, + ~isNullable=false, + ~chainIdMode, + ) await sql->Postgres.unpreparedUnsafe( `INSERT INTO "${pgSchema}"."${Config.EnvioAddresses.table.tableName}" ("id", "chain_id", "registration_block", "registration_log_index", "contract_name") -SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::int[],$3::text[]) AS t(id, chain_id, contract_name);`, +SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChainIdArrayType},$3::text[]) AS t(id, chain_id, contract_name);`, (ids, addrChainIds, addrContractNames)->(Utils.magic: _ => unknown), ) } @@ -1725,7 +1780,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::int[],$3:: // Convert string checkpoint IDs from DB to bigint let reorgCheckpoints = Array.map(reorgCheckpoints, (raw): Internal.reorgCheckpoint => { checkpointId: raw["id"]->BigInt.fromStringOrThrow, - chainId: raw["chain_id"], + chainId: raw["chain_id"]->ChainId.normalizeOrThrow->ChainId.toInt, blockNumber: raw["block_number"], blockHash: raw["block_hash"], }) @@ -1907,6 +1962,7 @@ let makeStorageFromEnv = ( ~pgPort=Env.Db.port, ~pgDatabase=Env.Db.database, ~pgPassword=Env.Db.password, + ~chainIdMode=config.chainIdMode, ~sink=?{ // Internally ClickHouse storage is implemented as a sync of the // Postgres storage. Required env vars are validated here only when @@ -1939,6 +1995,7 @@ let makeStorageFromEnv = ( ~database=database->Option.getUnsafe, ~username=username->Option.getUnsafe, ~password=password->Option.getUnsafe, + ~chainIdMode=config.chainIdMode, ), ) } else { diff --git a/packages/envio/src/Sink.res b/packages/envio/src/Sink.res index fcc6eea064..dcafc5fcdc 100644 --- a/packages/envio/src/Sink.res +++ b/packages/envio/src/Sink.res @@ -12,7 +12,7 @@ type t = { ) => promise, } -let makeClickHouse = (~host, ~database, ~username, ~password): t => { +let makeClickHouse = (~host, ~database, ~username, ~password, ~chainIdMode: ChainId.mode=Int32): t => { let client = ClickHouse.createClient({ url: host, username, @@ -27,7 +27,7 @@ let makeClickHouse = (~host, ~database, ~username, ~password): t => { { name: "clickhouse", initialize: (~chainConfigs as _=[], ~entities=[], ~enums=[]) => { - ClickHouse.initialize(client, ~database, ~entities, ~enums) + ClickHouse.initialize(client, ~database, ~entities, ~enums, ~chainIdMode) }, resume: (~checkpointId) => { ClickHouse.resume(client, ~database, ~checkpointId) diff --git a/packages/envio/src/bindings/ClickHouse.res b/packages/envio/src/bindings/ClickHouse.res index d1292c502e..52a8cbabd9 100644 --- a/packages/envio/src/bindings/ClickHouse.res +++ b/packages/envio/src/bindings/ClickHouse.res @@ -44,9 +44,15 @@ let getClickHouseFieldType = ( ~fieldType: Table.fieldType, ~isNullable: bool, ~isArray: bool, + ~chainIdMode: ChainId.mode=Int32, ): string => { let baseType = switch fieldType { | Int32 => "Int32" + | ChainId => + switch chainIdMode { + | Int32 => "Int32" + | Int64 => "UInt64" + } | Uint32 => "UInt32" | UInt52 => "UInt64" | UInt64 => "UInt64" @@ -123,6 +129,7 @@ let makeClickHouseEntitySchema = (table: Table.table): S.t => { dateSchema } } + | ChainId => ChainId.intSchema->S.toUnknown // ClickHouse returns UInt64 values as strings, need to parse to float | UInt52 => { let uint52Schema = @@ -337,6 +344,7 @@ let makeCreateHistoryTableQuery = ( ~database: string, ~replicated: bool=false, ~onCluster: bool=false, + ~chainIdMode: ChainId.mode=Int32, ) => { let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()" let fieldDefinitions = entityConfig.table.fields->Array.filterMap(field => { @@ -348,6 +356,7 @@ let makeCreateHistoryTableQuery = ( ~fieldType=field.fieldType, ~isNullable=field.isNullable, ~isArray=field.isArray, + ~chainIdMode, ) `\`${fieldName}\` ${clickHouseType}` }) @@ -446,6 +455,7 @@ let makeCreateCheckpointsTableQuery = ( ~database: string, ~replicated: bool=false, ~onCluster: bool=false, + ~chainIdMode: ChainId.mode=Int32, ) => { let tableEngine = replicated ? "ReplicatedMergeTree" : "MergeTree()" let idField = (#id: InternalTable.Checkpoints.field :> string) @@ -459,9 +469,10 @@ let makeCreateCheckpointsTableQuery = ( )} ( \`${idField}\` ${getClickHouseFieldType(~fieldType=UInt64, ~isNullable=false, ~isArray=false)}, \`${chainIdField}\` ${getClickHouseFieldType( - ~fieldType=Int32, + ~fieldType=ChainId, ~isNullable=false, ~isArray=false, + ~chainIdMode, )}, \`${blockNumberField}\` ${getClickHouseFieldType( ~fieldType=Int32, @@ -528,6 +539,7 @@ let initialize = async ( ~database: string, ~entities: array, ~enums as _: array>, + ~chainIdMode: ChainId.mode=Int32, ) => { try { let databaseEngine = Env.ClickHouse.databaseEngine() @@ -600,12 +612,18 @@ let initialize = async ( ~database, ~replicated, ~onCluster=ddlOnCluster, + ~chainIdMode, ), }) ), )->Utils.Promise.ignoreValue await client->exec({ - query: makeCreateCheckpointsTableQuery(~database, ~replicated, ~onCluster=ddlOnCluster), + query: makeCreateCheckpointsTableQuery( + ~database, + ~replicated, + ~onCluster=ddlOnCluster, + ~chainIdMode, + ), }) // The client pools HTTP connections, so consecutive statements may reach diff --git a/packages/envio/src/db/InternalTable.res b/packages/envio/src/db/InternalTable.res index 3ea7219a44..91e48e1654 100644 --- a/packages/envio/src/db/InternalTable.res +++ b/packages/envio/src/db/InternalTable.res @@ -69,7 +69,7 @@ module Chains = { let table = mkTable( "envio_chains", ~fields=[ - mkField((#id: field :> string), Int32, ~fieldSchema=S.int, ~isPrimaryKey), + mkField((#id: field :> string), ChainId, ~fieldSchema=ChainId.intSchema, ~isPrimaryKey), // Values populated from config mkField((#start_block: field :> string), Int32, ~fieldSchema=S.int), mkField((#end_block: field :> string), Int32, ~fieldSchema=S.null(S.int), ~isNullable), @@ -224,7 +224,9 @@ FROM "${pgSchema}"."${EnvioAddresses.table.tableName}";` let indexingAddressesByChainId = Dict.make() rawIndexingAddresses->Array.forEach(row => { - let key = row.chainId->Int.toString + // BIGINT chain ids come back as strings; normalizing here keeps the + // grouping key identical to the one derived from the chains rows below. + let key = row.chainId->ChainId.normalizeOrThrow->ChainId.toString let addresses = switch indexingAddressesByChainId->Dict.get(key) { | Some(addresses) => addresses | None => @@ -242,10 +244,14 @@ FROM "${pgSchema}"."${EnvioAddresses.table.tableName}";` }) rawInitialStates->Array.map(rawInitialState => { - ...rawInitialState, - indexingAddresses: indexingAddressesByChainId - ->Dict.get(rawInitialState.id->Int.toString) - ->Option.getOr([]), + let id = rawInitialState.id->ChainId.normalizeOrThrow + { + ...rawInitialState, + id: id->ChainId.toInt, + indexingAddresses: indexingAddressesByChainId + ->Dict.get(id->ChainId.toString) + ->Option.getOr([]), + } }) } @@ -398,7 +404,7 @@ module Checkpoints = { // Schema for parsing DB results where BIGINT columns come back as strings let dbSchema = S.object(s => { id: s.field("id", Utils.BigInt.schema), - chainId: s.field("chain_id", S.int), + chainId: s.field("chain_id", ChainId.intSchema), blockNumber: s.field("block_number", S.int), blockHash: s.field( "block_hash", @@ -416,7 +422,7 @@ module Checkpoints = { "envio_checkpoints", ~fields=[ mkField((#id: field :> string), UInt64, ~fieldSchema=S.bigint, ~isPrimaryKey), - mkField((#chain_id: field :> string), Int32, ~fieldSchema=S.int), + mkField((#chain_id: field :> string), ChainId, ~fieldSchema=ChainId.intSchema), mkField((#block_number: field :> string), Int32, ~fieldSchema=S.int), mkField((#block_hash: field :> string), String, ~fieldSchema=S.null(S.string), ~isNullable), mkField((#events_processed: field :> string), Int32, ~fieldSchema=S.int), @@ -453,9 +459,17 @@ WHERE cp."${(#block_hash: field :> string)}" IS NOT NULL `SELECT COALESCE(MAX(${(#id: field :> string)}), ${initialCheckpointId->BigInt.toString}) AS id FROM "${pgSchema}"."${table.tableName}";` } - let makeInsertCheckpointQuery = (~pgSchema) => { + let makeInsertCheckpointQuery = (~pgSchema, ~chainIdMode: ChainId.mode=Int32) => { + let chainIdArrayType = Table.getPgFieldType( + ~fieldType=ChainId, + ~pgSchema, + ~isArray=true, + ~isNumericArrayAsText=false, + ~isNullable=false, + ~chainIdMode, + ) `INSERT INTO "${pgSchema}"."${table.tableName}" ("${(#id: field :> string)}", "${(#chain_id: field :> string)}", "${(#block_number: field :> string)}", "${(#block_hash: field :> string)}", "${(#events_processed: field :> string)}") -SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${(Integer: Postgres.columnType :> string)}[],$3::${(Integer: Postgres.columnType :> string)}[],$4::${(Text: Postgres.columnType :> string)}[],$5::${(Integer: Postgres.columnType :> string)}[]);` +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)}[]);` } let insert = ( @@ -466,8 +480,9 @@ SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${(Int ~checkpointBlockNumbers, ~checkpointBlockHashes, ~checkpointEventsProcessed, + ~chainIdMode: ChainId.mode=Int32, ) => { - let query = makeInsertCheckpointQuery(~pgSchema) + let query = makeInsertCheckpointQuery(~pgSchema, ~chainIdMode) // Convert bigint arrays to string arrays for postgres driver compatibility let checkpointIdStrings = checkpointIds->Utils.BigInt.arrayToStringArray @@ -540,7 +555,7 @@ LIMIT 1;` let makeGetRollbackProgressDiffQuery = (~pgSchema) => { `SELECT - "${(#chain_id: field :> string)}", + "${(#chain_id: field :> string)}"::float8 as "${(#chain_id: field :> string)}", SUM("${(#events_processed: field :> string)}") as events_processed_diff, MIN("${(#block_number: field :> string)}") - 1 as new_progress_block_number FROM "${pgSchema}"."${table.tableName}" @@ -574,7 +589,7 @@ module RawEvents = { type t = Internal.rawEvent let schema = S.schema((s): t => { - chain_id: s.matches(S.int), + chain_id: s.matches(ChainId.intSchema), event_id: s.matches(S.bigint), event_name: s.matches(S.string), contract_name: s.matches(S.string), @@ -591,7 +606,7 @@ module RawEvents = { let table = mkTable( "raw_events", ~fields=[ - mkField("chain_id", Int32, ~fieldSchema=S.int), + mkField("chain_id", ChainId, ~fieldSchema=ChainId.intSchema), mkField("event_id", UInt64, ~fieldSchema=S.bigint), mkField("event_name", String, ~fieldSchema=S.string), mkField("contract_name", String, ~fieldSchema=S.string), diff --git a/packages/envio/src/db/Table.res b/packages/envio/src/db/Table.res index 4b212e2c67..989c18e317 100644 --- a/packages/envio/src/db/Table.res +++ b/packages/envio/src/db/Table.res @@ -23,6 +23,9 @@ type fieldType = | UInt52 | UInt64 | Int32 + // Resolved to Int32 or UInt64 storage from the config's `ChainId.mode`, so + // the internal tables can stay module-level constants. + | ChainId | Number | BigInt({precision?: int}) | BigDecimal({config?: (int, int)}) // (precision, scale) @@ -138,11 +141,17 @@ let getPgFieldType = ( ~isArray, ~isNumericArrayAsText, ~isNullable, + ~chainIdMode: ChainId.mode=Int32, ) => { let columnType = switch fieldType { | String => (Postgres.Text :> string) | Boolean => (Postgres.Boolean :> string) | Int32 => (Postgres.Integer :> string) + | ChainId => + switch chainIdMode { + | Int32 => (Postgres.Integer :> string) + | Int64 => (Postgres.BigInt :> string) + } | Uint32 => (Postgres.BigInt :> string) | UInt52 => (Postgres.BigInt :> string) | UInt64 => (Postgres.BigInt :> string) @@ -378,7 +387,7 @@ type sqlParams<'entity> = { hasArrayField: bool, } -let toSqlParams = (table: table, ~schema, ~pgSchema) => { +let toSqlParams = (table: table, ~schema, ~pgSchema, ~chainIdMode: ChainId.mode=Int32) => { let quotedFieldNames = [] let quotedNonPrimaryFieldNames = [] let arrayFieldTypes = [] @@ -441,6 +450,7 @@ let toSqlParams = (table: table, ~schema, ~pgSchema) => { ~isArray=true, ~isNullable=f.isNullable, ~isNumericArrayAsText=false, + ~chainIdMode, ) switch f.fieldType { | Enum(_) => `${(Text: Postgres.columnType :> string)}[]::${pgFieldType}` diff --git a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res index 5bdc7e39fd..8cc7b2c0f8 100644 --- a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res +++ b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res @@ -918,7 +918,7 @@ LIMIT 1;` ) let expectedQuery = `SELECT - "chain_id", + "chain_id"::float8 as "chain_id", SUM("events_processed") as events_processed_diff, MIN("block_number") - 1 as new_progress_block_number FROM "test_schema"."envio_checkpoints" From d8a170364876929c862063b7cfef7356e21d7f41 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 18:19:18 +0000 Subject: [PATCH 2/6] Make the internal chain id an opaque type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ChainId.t` was only used at the config/table boundary; every module in between still annotated chain ids as `int`, which is exactly the type that can't represent them. Those annotations are now `ChainId.t`, so the compiler — not a comment — is what keeps a chain id from being treated as an int32. `ChainMap.Chain` is backed by it directly, and chain-keyed dictionaries go through `ChainId.Dict` instead of the int-keyed `Utils.Dict` helpers (which remain for the block-number-keyed dicts in FetchState and ReorgDetection). `ChainId.intSchema` is gone — `schema` is the only one left. Two boundaries deliberately stay `int` so no user code changes: `context.chain.id` (`Internal.chainInfo`) and `Envio.effectChain.id`. `fromInt`/`toInt` are the identity at runtime and mark those crossings, along with the int literals that construct chain ids in configs and tests. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ECqzHdVS4cLxfCXPw8zpb6 --- .../test/ClientAddressFilter_test.res | 12 +- packages/envio-tests/test/RateLimit_test.res | 2 +- .../envio-tests/test/ReorgDetection_test.res | 2 +- .../test/SvmHyperSyncSource_test.res | 2 +- .../test/UserApiValidation_test.res | 6 +- .../lib_tests/ChainState_materialize_test.res | 2 +- .../test/lib_tests/EffectCache_test.res | 18 +-- .../test/lib_tests/Metrics_test.res | 6 +- packages/envio/src/Batch.res | 14 +- packages/envio/src/ChainFetching.res | 2 +- packages/envio/src/ChainId.res | 13 +- packages/envio/src/ChainId.resi | 13 +- packages/envio/src/ChainMap.res | 4 +- packages/envio/src/ChainMap.resi | 4 +- packages/envio/src/ChainMetadata.res | 2 +- packages/envio/src/ChainState.res | 16 +- packages/envio/src/Config.res | 28 ++-- .../envio/src/ContractRegisterContext.res | 2 +- packages/envio/src/CrossChainState.res | 16 +- packages/envio/src/EventConfigBuilder.res | 2 +- packages/envio/src/EventProcessing.res | 10 +- packages/envio/src/FetchState.res | 6 +- packages/envio/src/HandlerRegister.res | 16 +- packages/envio/src/HandlerRegister.resi | 2 +- packages/envio/src/IndexerState.res | 4 +- packages/envio/src/Internal.res | 29 ++-- packages/envio/src/LoadLayer.res | 2 +- packages/envio/src/LogSelection.res | 4 +- packages/envio/src/Main.res | 10 +- packages/envio/src/Metrics.res | 12 +- packages/envio/src/Persistence.res | 8 +- packages/envio/src/PgStorage.res | 4 +- packages/envio/src/Rollback.res | 10 +- packages/envio/src/RollbackCommit.res | 4 +- packages/envio/src/SafeCheckpointTracking.res | 4 +- .../envio/src/SimulateDeadInputTracker.res | 16 +- packages/envio/src/SimulateItems.res | 6 +- packages/envio/src/TestIndexer.res | 20 ++- packages/envio/src/UserContext.res | 4 +- packages/envio/src/bindings/ClickHouse.res | 2 +- packages/envio/src/db/EntityHistory.res | 2 +- packages/envio/src/db/InternalTable.res | 30 ++-- packages/envio/src/sources/Evm.res | 2 +- packages/envio/src/sources/Fuel.res | 2 +- packages/envio/src/sources/HyperSync.resi | 4 +- packages/envio/src/sources/RpcSource.res | 2 +- packages/envio/src/sources/SourceManager.res | 4 +- packages/envio/src/sources/SourceManager.resi | 4 +- packages/envio/src/sources/Svm.res | 2 +- packages/envio/src/tui/Tui.res | 2 +- .../envio/src/tui/components/CustomHooks.res | 8 +- .../test/FuelHyperSyncSourceHeight_test.res | 2 +- .../test_codegen/test/ChainMeta_test.res | 2 +- scenarios/test_codegen/test/E2E_test.res | 6 +- .../test/EventBlockFilter_test.res | 20 +-- .../test_codegen/test/EventFilters_test.res | 18 +-- .../test/HandlerRegisterLifecycle_test.res | 4 +- .../test/IndexerStateStall_test.res | 2 +- .../test_codegen/test/IndexerState_test.res | 10 +- .../test_codegen/test/LoadLayer_test.res | 16 +- .../test/RpcSourceContract_test.res | 2 +- .../test_codegen/test/RpcSource_test.res | 2 +- .../test/SourceBlockHashes_test.res | 2 +- .../test/__mocks__/MockConfig.res | 12 +- .../test/__mocks__/MockEvents.res | 16 +- .../test_codegen/test/helpers/MockIndexer.res | 8 +- .../test/helpers/RpcSourcePins.res | 2 +- .../test/lib_tests/ChainState_test.res | 6 +- .../test/lib_tests/CrossChainState_test.res | 147 +++++++++--------- .../DynamicContractsStartupSize_test.res | 4 +- .../test/lib_tests/EntityIdType_test.res | 2 +- .../lib_tests/FetchState_onBlock_test.res | 2 +- .../test/lib_tests/FetchState_test.res | 6 +- .../test/lib_tests/HyperSyncDecoder_test.res | 4 +- .../test/lib_tests/IndexerLoop_test.res | 2 +- .../test/lib_tests/PgStorage_test.res | 20 +-- .../SameSignatureEventDecode_test.res | 2 +- .../test/lib_tests/SourceManager_test.res | 2 +- .../test/rollback/ChainMocking.res | 2 +- .../test/rollback/Rollback_test.res | 54 +++---- 80 files changed, 400 insertions(+), 378 deletions(-) diff --git a/packages/envio-tests/test/ClientAddressFilter_test.res b/packages/envio-tests/test/ClientAddressFilter_test.res index 4afb596c74..b65d7106c9 100644 --- a/packages/envio-tests/test/ClientAddressFilter_test.res +++ b/packages/envio-tests/test/ClientAddressFilter_test.res @@ -6,7 +6,7 @@ open Vitest let transferSighash = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" -let parseEvm = (~eventFilters: option, ~chainId=1) => +let parseEvm = (~eventFilters: option, ~chainId=1->ChainId.fromInt) => LogSelection.parseWhereOrThrow( ~where=eventFilters, ~sighash=transferSighash, @@ -88,7 +88,7 @@ describe("clientAddressFilter — precompiled predicate", () => { ~handler=None, ~contractRegister=None, ~where=Some(eventFilters), - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ).clientAddressFilter @@ -181,7 +181,7 @@ describe("filterByClientAddress applies clientAddressFilter", () => { ~handler=None, ~contractRegister=None, ~where=Some(%raw(`({chain}) => ({params: {to: chain.ERC20.addresses}})`)), - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ~startBlock=5, ) @@ -189,7 +189,7 @@ describe("filterByClientAddress applies clientAddressFilter", () => { let onEventRegistration = (onEventRegistration :> Internal.onEventRegistration) let makeItem = (~to, ~blockNumber): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId=1), + chain: ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), blockNumber, onEventRegistration, logIndex: 0, @@ -234,7 +234,7 @@ describe("filterByClientAddress drops over-fetched non-wildcard srcAddress event ~handler=None, ~contractRegister=None, ~where=None, - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ~startBlock=5, ) @@ -242,7 +242,7 @@ describe("filterByClientAddress drops over-fetched non-wildcard srcAddress event let onEventRegistration = (onEventRegistration :> Internal.onEventRegistration) let makeItem = (~srcAddress, ~blockNumber): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId=1), + chain: ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), blockNumber, onEventRegistration, logIndex: 0, diff --git a/packages/envio-tests/test/RateLimit_test.res b/packages/envio-tests/test/RateLimit_test.res index 3638878739..12bfcdede4 100644 --- a/packages/envio-tests/test/RateLimit_test.res +++ b/packages/envio-tests/test/RateLimit_test.res @@ -1,6 +1,6 @@ open Vitest -let chain = ChainMap.Chain.makeUnsafe(~chainId=1) +let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) // Mock source that throws Source.RateLimited on the first N calls, then // returns Ok with the requested block data. Lets us exercise diff --git a/packages/envio-tests/test/ReorgDetection_test.res b/packages/envio-tests/test/ReorgDetection_test.res index 0c67c775c8..37d1d496c2 100644 --- a/packages/envio-tests/test/ReorgDetection_test.res +++ b/packages/envio-tests/test/ReorgDetection_test.res @@ -16,7 +16,7 @@ describe("Validate reorg detection functions", () => { blockNumber, blockHash, )): Internal.reorgCheckpoint => { - chainId: 0, // It's not used + chainId: 0->ChainId.fromInt, // It's not used checkpointId: 0n, // It's not used blockNumber, blockHash, diff --git a/packages/envio-tests/test/SvmHyperSyncSource_test.res b/packages/envio-tests/test/SvmHyperSyncSource_test.res index 2bed292660..49a59d011d 100644 --- a/packages/envio-tests/test/SvmHyperSyncSource_test.res +++ b/packages/envio-tests/test/SvmHyperSyncSource_test.res @@ -11,7 +11,7 @@ open Vitest // logIndex, and Rust-decoded params parsed from JSON strings. let metaplexProgramId = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" -let chain = ChainMap.Chain.makeUnsafe(~chainId=0) +let chain = ChainMap.Chain.makeUnsafe(~chainId=0->ChainId.fromInt) let blockTime = 1778064393 let slot = 417950033 diff --git a/packages/envio-tests/test/UserApiValidation_test.res b/packages/envio-tests/test/UserApiValidation_test.res index 025af89627..3baa0ddfde 100644 --- a/packages/envio-tests/test/UserApiValidation_test.res +++ b/packages/envio-tests/test/UserApiValidation_test.res @@ -955,7 +955,7 @@ chains: `) let chain = config.chainMap->ChainMap.values->Array.getUnsafe(0) t.expect(config.ecosystem.name).toEqual(Ecosystem.Fuel) - t.expect((chain.id, chain.startBlock)).toEqual((0, 7)) + t.expect((chain.id->ChainId.toString, chain.startBlock)).toEqual(("0", 7)) }) it("parses a minimal SVM config through the public boundary", t => { @@ -968,7 +968,7 @@ chains: `) let chain = config.chainMap->ChainMap.values->Array.getUnsafe(0) t.expect(config.ecosystem.name).toEqual(Ecosystem.Svm) - t.expect((chain.id, chain.startBlock)).toEqual((0, 8)) + t.expect((chain.id->ChainId.toString, chain.startBlock)).toEqual(("0", 8)) }) it("validates event field selections against only the chain that uses them", t => { @@ -1031,7 +1031,7 @@ chains: `) let chain = config.chainMap->ChainMap.values->Array.getUnsafe(0) t.expect(config.chainMap->ChainMap.values->Array.length).toBe(1) - t.expect(chain.id).toBe(137) + t.expect(chain.id->ChainId.toString).toBe("137") t.expect(chain.startBlock).toBe(2000) }) diff --git a/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res b/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res index 2f42531e6b..81fe9cedca 100644 --- a/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res +++ b/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res @@ -5,7 +5,7 @@ open Vitest // omitted, the payload is store-backed for that dimension. // `transactionMask`/`blockMask` mirror the per-event `onEventRegistration.eventConfig` // masks that `ChainState.groupBatchItems` reads for each dimension. -let materializeChainId = 987 +let materializeChainId = 987->ChainId.fromInt let makeItem = ( ~blockNumber, ~transactionIndex=0, diff --git a/packages/envio-tests/test/lib_tests/EffectCache_test.res b/packages/envio-tests/test/lib_tests/EffectCache_test.res index 69849b27a2..6e5af7461e 100644 --- a/packages/envio-tests/test/lib_tests/EffectCache_test.res +++ b/packages/envio-tests/test/lib_tests/EffectCache_test.res @@ -12,15 +12,15 @@ describe("Internal.EffectCache address mapping", () => { it("Maps chain scope to the chain-prefixed table name and nested file path", t => { t.expect(( - Internal.EffectCache.toTableName(~effectName="foo", ~scope=Chain(1)), - Internal.EffectCache.toCachePath(~effectName="foo", ~scope=Chain(1)), - Internal.EffectCache.toTableName(~effectName="foo", ~scope=Chain(137)), - Internal.EffectCache.toCachePath(~effectName="foo", ~scope=Chain(137)), + Internal.EffectCache.toTableName(~effectName="foo", ~scope=Chain(1->ChainId.fromInt)), + Internal.EffectCache.toCachePath(~effectName="foo", ~scope=Chain(1->ChainId.fromInt)), + Internal.EffectCache.toTableName(~effectName="foo", ~scope=Chain(137->ChainId.fromInt)), + Internal.EffectCache.toCachePath(~effectName="foo", ~scope=Chain(137->ChainId.fromInt)), )).toEqual(("envio_1_effect_foo", "1/foo.tsv", "envio_137_effect_foo", "137/foo.tsv")) }) it("Round trips every scope through table name and back", t => { - let cases = [("foo", Internal.CrossChain), ("foo", Chain(1)), ("bar_baz", Chain(137))] + let cases = [("foo", Internal.CrossChain), ("foo", Chain(1->ChainId.fromInt)), ("bar_baz", Chain(137->ChainId.fromInt))] t.expect( cases->Array.map(((effectName, scope)) => Internal.EffectCache.fromTableName(Internal.EffectCache.toTableName(~effectName, ~scope)) @@ -39,7 +39,7 @@ describe("Internal.EffectCache address mapping", () => { Internal.EffectCache.fromTableName("envio_effect_foo"), Internal.EffectCache.fromTableName("envio_1_effect_foo"), Internal.EffectCache.fromTableName("envio_137_effect_foo"), - )).toEqual((Some(("foo", CrossChain)), Some(("foo", Chain(1))), Some(("foo", Chain(137))))) + )).toEqual((Some(("foo", CrossChain)), Some(("foo", Chain(1->ChainId.fromInt))), Some(("foo", Chain(137->ChainId.fromInt))))) }) it("Keeps effect names that themselves contain _effect_ unambiguous", t => { @@ -49,13 +49,13 @@ describe("Internal.EffectCache address mapping", () => { t.expect(( Internal.EffectCache.fromTableName("envio_effect_1_effect_x"), Internal.EffectCache.fromTableName("envio_1_effect_x"), - )).toEqual((Some(("1_effect_x", CrossChain)), Some(("x", Chain(1))))) + )).toEqual((Some(("1_effect_x", CrossChain)), Some(("x", Chain(1->ChainId.fromInt))))) }) it("Parses only canonical decimal chain ids", t => { t.expect( ["1", "137", "007", "1foo", "", "-1", "1.5"]->Array.map(Internal.EffectCache.parseChainId), - ).toEqual([Some(1), Some(137), None, None, None, None, None]) + ).toEqual([Some(1->ChainId.fromInt), Some(137->ChainId.fromInt), None, None, None, None, None]) }) it("Rejects table names with a non-canonical chain id", t => { @@ -65,7 +65,7 @@ describe("Internal.EffectCache address mapping", () => { it("Maps scope to its Prometheus label value", t => { t.expect(( Internal.EffectCache.scopeToString(CrossChain), - Internal.EffectCache.scopeToString(Chain(137)), + Internal.EffectCache.scopeToString(Chain(137->ChainId.fromInt)), )).toEqual(("crossChain", "137")) }) diff --git a/packages/envio-tests/test/lib_tests/Metrics_test.res b/packages/envio-tests/test/lib_tests/Metrics_test.res index 8a2fb5d9d4..a3a80095f9 100644 --- a/packages/envio-tests/test/lib_tests/Metrics_test.res +++ b/packages/envio-tests/test/lib_tests/Metrics_test.res @@ -130,7 +130,7 @@ envio_info{version="${Utils.EnvioPackage.value.version}"} 1 rollbackEventsCount: 42., chains: [ { - chainId: 1., + chainId: 1->ChainId.fromInt, poweredByHyperSync: true, firstEventBlockNumber: Some(100), latestProcessedBlock: Some(200), @@ -216,7 +216,7 @@ envio_info{version="${Utils.EnvioPackage.value.version}"} 1 sourceRequests: [ { source: "HyperSync", - chainId: 1, + chainId: 1->ChainId.fromInt, method: "getLogs", count: 42, seconds: 33.75, @@ -225,7 +225,7 @@ envio_info{version="${Utils.EnvioPackage.value.version}"} 1 sourceHeights: [ { source: "HyperSync", - chainId: 1, + chainId: 1->ChainId.fromInt, height: 305, }, ], diff --git a/packages/envio/src/Batch.res b/packages/envio/src/Batch.res index 850b3f4de4..96ac2e6ec2 100644 --- a/packages/envio/src/Batch.res +++ b/packages/envio/src/Batch.res @@ -28,7 +28,7 @@ type t = { isInReorgThreshold: bool, // Unnest-like checkpoint fields: checkpointIds: array, - checkpointChainIds: array, + checkpointChainIds: array, checkpointBlockNumbers: array, checkpointBlockHashes: array>, checkpointEventsProcessed: array, @@ -80,14 +80,14 @@ let getProgressedChainsById = { let fetchState = chainBeforeBatch.fetchState let progressBlockNumberAfterBatch = switch progressBlockNumberPerChain->Utils.Dict.dangerouslyGetNonOption( - fetchState.chainId->Int.toString, + fetchState.chainId->ChainId.toString, ) { | Some(progressBlockNumber) => progressBlockNumber | None => chainBeforeBatch.progressBlockNumber } switch switch batchSizePerChain->Utils.Dict.dangerouslyGetNonOption( - fetchState.chainId->Int.toString, + fetchState.chainId->ChainId.toString, ) { | Some(batchSize) => let leftItems = fetchState.buffer->Array.slice(~start=batchSize) @@ -107,7 +107,7 @@ let getProgressedChainsById = { ) } { | Some(progressedChain) => - progressedChainsById->Utils.Dict.setByInt( + progressedChainsById->ChainId.Dict.set( chainBeforeBatch.fetchState.chainId, progressedChain, ) @@ -195,7 +195,7 @@ let prepareBatch = ( ) let chainBeforeBatch = chainsBeforeBatch - ->Utils.Dict.dangerouslyGetByIntNonOption(fetchState.chainId) + ->ChainId.Dict.dangerouslyGetNonOption(fetchState.chainId) ->Option.getUnsafe let prevBlockNumber = ref(chainBeforeBatch.progressBlockNumber) @@ -245,7 +245,7 @@ let prepareBatch = ( } totalBatchSize := totalBatchSize.contents + chainBatchSize - mutBatchSizePerChain->Utils.Dict.setByInt(fetchState.chainId, chainBatchSize) + mutBatchSizePerChain->ChainId.Dict.set(fetchState.chainId, chainBatchSize) } let progressBlockNumberAfterBatch = @@ -265,7 +265,7 @@ let prepareBatch = ( ~mutCheckpointEventsProcessed=checkpointEventsProcessed, ) - mutProgressBlockNumberPerChain->Utils.Dict.setByInt( + mutProgressBlockNumberPerChain->ChainId.Dict.set( fetchState.chainId, progressBlockNumberAfterBatch, ) diff --git a/packages/envio/src/ChainFetching.res b/packages/envio/src/ChainFetching.res index 40e0c49c55..28e31f4b3a 100644 --- a/packages/envio/src/ChainFetching.res +++ b/packages/envio/src/ChainFetching.res @@ -199,7 +199,7 @@ let rec onQueryResponse = async ( cs->ChainState.prepareReorg( ~eventsProcessedDiff=switch eventsProcessedDiffByChain { | Some(byChain) => - byChain->Utils.Dict.dangerouslyGetByIntNonOption((cs->ChainState.chainConfig).id) + byChain->ChainId.Dict.dangerouslyGetNonOption((cs->ChainState.chainConfig).id) | None => None }, ) diff --git a/packages/envio/src/ChainId.res b/packages/envio/src/ChainId.res index 65cbb4ef54..eb713c9f7f 100644 --- a/packages/envio/src/ChainId.res +++ b/packages/envio/src/ChainId.res @@ -17,6 +17,9 @@ let maxSafe = 9007199254740991. @scope("Number") @val external isSafeInteger: float => bool = "isSafeInteger" +// Escapes for the boundaries that stay `int`: the handler-facing +// `context.chain.id` / `Envio.effectChain.id`, and int literals in configs and +// tests. Both are the identity at runtime — an `int` is already a JS number. external fromInt: int => t = "%identity" external toInt: t => int = "%identity" external toFloat: t => float = "%identity" @@ -45,10 +48,12 @@ let schema: S.t = S.float->S.preprocess(s => { }, }) -// The same runtime schema, typed for the modules that still annotate chain ids -// as `int`. Safe because ReScript's `int` is a JS number at runtime — chain ids -// are only ever compared and stringified, never used in int32 arithmetic. -let intSchema = schema->(Utils.magic: S.t => S.t) +// Dicts keyed by chain id. JS coerces the number key to its decimal string, +// which is exactly what `toString` produces, so the two key forms interoperate. +module Dict = { + @get_index external dangerouslyGetNonOption: (dict<'a>, t) => option<'a> = "" + @set_index external set: (dict<'a>, t, 'a) => unit = "" +} // Postgres returns BIGINT columns as strings, so raw (schema-less) reads of a // chain-id column go through this instead of trusting the driver's type. diff --git a/packages/envio/src/ChainId.resi b/packages/envio/src/ChainId.resi index a34a1a43c9..95ab125cd3 100644 --- a/packages/envio/src/ChainId.resi +++ b/packages/envio/src/ChainId.resi @@ -6,6 +6,9 @@ let modeSchema: S.t let maxSafe: float +// Escapes for the boundaries that stay `int`: the handler-facing +// `context.chain.id` / `Envio.effectChain.id`, and int literals in configs and +// tests. Both are the identity at runtime — an `int` is already a JS number. external fromInt: int => t = "%identity" external toInt: t => int = "%identity" external toFloat: t => float = "%identity" @@ -16,10 +19,12 @@ let equal: (t, t) => bool let schema: S.t -// The same runtime schema, typed for the modules that still annotate chain ids -// as `int`. Safe because ReScript's `int` is a JS number at runtime — chain ids -// are only ever compared and stringified, never used in int32 arithmetic. -let intSchema: S.t +// Dicts keyed by chain id. JS coerces the number key to its decimal string, +// which is exactly what `toString` produces, so the two key forms interoperate. +module Dict: { + @get_index external dangerouslyGetNonOption: (dict<'a>, t) => option<'a> = "" + @set_index external set: (dict<'a>, t, 'a) => unit = "" +} // Postgres returns BIGINT columns as strings, so raw (schema-less) reads of a // chain-id column go through this instead of trusting the driver's type. diff --git a/packages/envio/src/ChainMap.res b/packages/envio/src/ChainMap.res index 449c14c5dc..d75903455a 100644 --- a/packages/envio/src/ChainMap.res +++ b/packages/envio/src/ChainMap.res @@ -1,11 +1,11 @@ module Chain = { type t = ChainId.t - external toChainId: t => int = "%identity" + external toChainId: t => ChainId.t = "%identity" let toString = chainId => chainId->ChainId.toString - let makeUnsafe = (~chainId) => chainId->ChainId.fromInt + external makeUnsafe: (~chainId: ChainId.t) => t = "%identity" } module ChainIdCmp = Belt.Id.MakeComparable({ diff --git a/packages/envio/src/ChainMap.resi b/packages/envio/src/ChainMap.resi index c770344a0a..f2d4022a0c 100644 --- a/packages/envio/src/ChainMap.resi +++ b/packages/envio/src/ChainMap.resi @@ -1,11 +1,11 @@ module Chain: { type t - external toChainId: t => int = "%identity" + external toChainId: t => ChainId.t = "%identity" let toString: t => string - let makeUnsafe: (~chainId: int) => t + external makeUnsafe: (~chainId: ChainId.t) => t = "%identity" } type t<'a> diff --git a/packages/envio/src/ChainMetadata.res b/packages/envio/src/ChainMetadata.res index 8c3c58a078..b64a65c92d 100644 --- a/packages/envio/src/ChainMetadata.res +++ b/packages/envio/src/ChainMetadata.res @@ -8,7 +8,7 @@ let stage = (state: IndexerState.t) => { ->Dict.valuesToArray ->Array.forEach(cs => { chainsData->Dict.set( - (cs->ChainState.chainConfig).id->Int.toString, + (cs->ChainState.chainConfig).id->ChainId.toString, cs->ChainState.toChainMetadata, ) }) diff --git a/packages/envio/src/ChainState.res b/packages/envio/src/ChainState.res index fddbefa483..f855999d83 100644 --- a/packages/envio/src/ChainState.res +++ b/packages/envio/src/ChainState.res @@ -68,13 +68,13 @@ let configAddresses = (chainConfig: Config.chain): array, ) => registrations->Array.forEachWithIndex((registration, expectedIndex) => { if registration.index !== expectedIndex { JsError.throwWithMessage( - `Invalid onEvent registration index for chain ${chainId->Int.toString}: ${registration.eventConfig.contractName}.${registration.eventConfig.name} has index ${registration.index->Int.toString}, but its ChainState position is ${expectedIndex->Int.toString}.`, + `Invalid onEvent registration index for chain ${chainId->ChainId.toString}: ${registration.eventConfig.contractName}.${registration.eventConfig.name} has index ${registration.index->Int.toString}, but its ChainState position is ${expectedIndex->Int.toString}.`, ) } }) @@ -153,7 +153,7 @@ let makeInternal = ( // chain - this just looks up this chain's slice. let {onEventRegistrations, onBlockRegistrations} = registrationsByChainId - ->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->Int.toString) + ->Utils.Dict.dangerouslyGetNonOption(chainConfig.id->ChainId.toString) ->Option.getOr({onEventRegistrations: [], onBlockRegistrations: []}) chainConfig.contracts->Array.forEach(contract => { @@ -241,7 +241,7 @@ let makeInternal = ( switch (hypersync, rpc) { | (None, None) => JsError.throwWithMessage( - `Chain ${chain->ChainMap.Chain.toChainId->Int.toString} has no SVM data source`, + `Chain ${chain->ChainMap.Chain.toString} has no SVM data source`, ) | (None, Some(rpc)) => [Svm.makeRPCSource(~chain, ~rpc)] | (Some(hypersyncUrl), _) => @@ -891,7 +891,7 @@ let toChainMetadata = (cs: t): InternalTable.Chains.metaFields => { } let toMetrics = (cs: t): Metrics.chainMetrics => { - chainId: cs.chainConfig.id->Int.toFloat, + chainId: cs.chainConfig.id, poweredByHyperSync: (cs.sourceManager->SourceManager.getActiveSource).poweredByHyperSync, firstEventBlockNumber: cs.fetchState.firstEventBlock, latestProcessedBlock: cs.committedProgressBlockNumber === -1 @@ -941,7 +941,7 @@ let toChainBeforeBatch = (cs: t): Batch.chainBeforeBatch => { // Whether the chain's post-batch fetch frontier is ready to cross into the reorg // threshold, using the batch's progressed frontier when this chain advanced. let isReadyToEnterReorgThresholdAfterBatch = (cs: t, ~batch: Batch.t) => { - let fetchState = switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption( + let fetchState = switch batch.progressedChainsById->ChainId.Dict.dangerouslyGetNonOption( cs.fetchState.chainId, ) { | Some(chainAfterBatch) => chainAfterBatch.fetchState @@ -953,7 +953,7 @@ let isReadyToEnterReorgThresholdAfterBatch = (cs: t, ~batch: Batch.t) => { // Commit the post-batch fetch frontier for a chain that progressed in the batch, // applying blockLag when this batch also crosses into the reorg threshold. let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) => - switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption( + switch batch.progressedChainsById->ChainId.Dict.dangerouslyGetNonOption( cs.fetchState.chainId, ) { | Some(chainAfterBatch) => @@ -975,7 +975,7 @@ let advanceAfterBatch = (cs: t, ~batch: Batch.t, ~enteringReorgThreshold) => let applyBatchProgress = (cs: t, ~batch: Batch.t, ~blockTimestampName: string) => { let chainId = cs.chainConfig.id - switch batch.progressedChainsById->Utils.Dict.dangerouslyGetByIntNonOption(chainId) { + switch batch.progressedChainsById->ChainId.Dict.dangerouslyGetNonOption(chainId) { | Some(chainAfterBatch) => { // Calculate and set latency metrics. The payload block is materialised or // inline by processing time; its timestamp may still be absent (e.g. an diff --git a/packages/envio/src/Config.res b/packages/envio/src/Config.res index fcf28281ba..f9f6f20dcf 100644 --- a/packages/envio/src/Config.res +++ b/packages/envio/src/Config.res @@ -35,7 +35,7 @@ type sourceConfig = type chain = { name: string, - id: int, + id: ChainId.t, startBlock: int, endBlock?: int, maxReorgDepth: int, @@ -102,13 +102,13 @@ module EnvioAddresses = { let name = "envio_addresses" let index = -1 - let makeId = (~chainId, ~address) => { - chainId->Int.toString ++ "-" ++ address->Address.toString + let makeId = (~chainId: ChainId.t, ~address) => { + chainId->ChainId.toString ++ "-" ++ address->Address.toString } type t = { id: string, - @as("chain_id") chainId: int, + @as("chain_id") chainId: ChainId.t, @as("registration_block") registrationBlock: int, // -1 when the address was registered from a block handler (no log index) @as("registration_log_index") registrationLogIndex: int, @@ -127,7 +127,7 @@ module EnvioAddresses = { let schema = S.schema(s => { id: s.matches(S.string), - chainId: s.matches(ChainId.intSchema), + chainId: s.matches(ChainId.schema), registrationBlock: s.matches(S.int), registrationLogIndex: s.matches(S.int), contractName: s.matches(S.string), @@ -137,7 +137,7 @@ module EnvioAddresses = { name, ~fields=[ Table.mkField("id", String, ~isPrimaryKey=true, ~fieldSchema=S.string), - Table.mkField("chain_id", ChainId, ~fieldSchema=ChainId.intSchema), + Table.mkField("chain_id", ChainId, ~fieldSchema=ChainId.schema), Table.mkField("registration_block", Int32, ~fieldSchema=S.int), // -1 sentinel when registered from a block handler (no log index) Table.mkField("registration_log_index", Int32, ~fieldSchema=S.int), @@ -189,7 +189,7 @@ let chainContractSchema = S.schema(s => let publicConfigChainSchema = S.schema(s => { - "id": s.matches(ChainId.intSchema), + "id": s.matches(ChainId.schema), "startBlock": s.matches(S.int), "endBlock": s.matches(S.option(S.int)), "maxReorgDepth": s.matches(S.option(S.int)), @@ -695,7 +695,7 @@ let fromPublic = (publicConfigJson: JSON.t) => { ~contractName, ~events: option>, ~abi, - ~chainId: int, + ~chainId: ChainId.t, ~addresses: array, ~svmDefinedTypes: JSON.t=JSON.Null, ) => { @@ -729,11 +729,11 @@ let fromPublic = (publicConfigJson: JSON.t) => { | [pid] => pid->SvmTypes.Pubkey.fromStringUnsafe | [] => JsError.throwWithMessage( - `SVM program ${contractName} on chain ${chainId->Int.toString} is missing a program_id`, + `SVM program ${contractName} on chain ${chainId->ChainId.toString} is missing a program_id`, ) | _ => JsError.throwWithMessage( - `SVM program ${contractName} on chain ${chainId->Int.toString} has multiple addresses; a program is uniquely identified by a single program_id`, + `SVM program ${contractName} on chain ${chainId->ChainId.toString} has multiple addresses; a program is uniquely identified by a single program_id`, ) } let widenedEventItem = @@ -883,8 +883,8 @@ let fromPublic = (publicConfigJson: JSON.t) => { | Some(existingContractName) => JsError.throwWithMessage( existingContractName === contract.name - ? `Address ${addressString} is listed multiple times for the contract ${contract.name} on chain ${chainId->Int.toString}. Please remove the duplicate from your config.` - : `Address ${addressString} on chain ${chainId->Int.toString} is configured for multiple contracts: ${existingContractName} and ${contract.name}. Indexing the same address with multiple contract definitions is not supported. Please define the events on a single contract definition instead.`, + ? `Address ${addressString} is listed multiple times for the contract ${contract.name} on chain ${chainId->ChainId.toString}. Please remove the duplicate from your config.` + : `Address ${addressString} on chain ${chainId->ChainId.toString} is configured for multiple contracts: ${existingContractName} and ${contract.name}. Indexing the same address with multiple contract definitions is not supported. Please define the events on a single contract definition instead.`, ) | None => contractNameByAddress->Dict.set(addressString, contract.name) } @@ -1098,7 +1098,7 @@ let normalizeSimulateAddress = (config: t, address: Address.t): Address.t => // returns that chain's per-chain event config (matters for where-callback // probe detection, which runs with the chain's real id). Without `chainId`, // falls back to the first chain that declares this event. -let getEventConfig = (config: t, ~contractName, ~eventName, ~chainId: option=?) => { +let getEventConfig = (config: t, ~contractName, ~eventName, ~chainId: option=?) => { let chains = switch chainId { | Some(chainId) => let chain = ChainMap.Chain.makeUnsafe(~chainId) @@ -1106,7 +1106,7 @@ let getEventConfig = (config: t, ~contractName, ~eventName, ~chainId: option [chainConfig] | exception _ => JsError.throwWithMessage( - `Chain ${chainId->Int.toString} is not configured. Add it to config.yaml or pass a configured chain.`, + `Chain ${chainId->ChainId.toString} is not configured. Add it to config.yaml or pass a configured chain.`, ) } | None => config.chainMap->ChainMap.values diff --git a/packages/envio/src/ContractRegisterContext.res b/packages/envio/src/ContractRegisterContext.res index c50de28887..6fa21a42fe 100644 --- a/packages/envio/src/ContractRegisterContext.res +++ b/packages/envio/src/ContractRegisterContext.res @@ -36,7 +36,7 @@ let contractRegisterChainTraps: Utils.Proxy.traps = { switch prop { | "id" => let eventItem = params.item->Internal.castUnsafeEventItem - eventItem.chain->ChainMap.Chain.toChainId->(Utils.magic: int => unknown) + eventItem.chain->ChainMap.Chain.toChainId->(Utils.magic: ChainId.t => unknown) | _ => // Look up the contract name directly in config contracts across all chains. let contractName = prop diff --git a/packages/envio/src/CrossChainState.res b/packages/envio/src/CrossChainState.res index a2ba58d6a5..1937cfe837 100644 --- a/packages/envio/src/CrossChainState.res +++ b/packages/envio/src/CrossChainState.res @@ -7,7 +7,7 @@ type t = { chainStates: dict, // Chain ids in a stable order, so the cross-chain loops iterate the chains // without allocating a values array on every tick. - chainIds: array, + chainIds: array, // True once every chain has caught up to head/endBlock. Monotonic during a run. mutable isRealtime: bool, mutable isInReorgThreshold: bool, @@ -40,7 +40,7 @@ let make = ( // Resolve a chain's state by id. The id always comes from `chainIds`, which is // derived from `chainStates`, so the entry is guaranteed present. let getChainState = (crossChainState: t, chainId) => - crossChainState.chainStates->Utils.Dict.dangerouslyGetByIntNonOption(chainId)->Option.getUnsafe + crossChainState.chainStates->ChainId.Dict.dangerouslyGetNonOption(chainId)->Option.getUnsafe // --- Accessors. --- @@ -274,14 +274,14 @@ let checkAndFetch = async ( // hold the whole pool. (The general can't-fetch-yet rule, including // blockLag, lives in FetchState.getNextQuery — this branch only // short-circuits the unambiguous no-height case.) - actionByChain->Utils.Dict.setByInt(chainId, FetchState.WaitingForNewBlock) + actionByChain->ChainId.Dict.set(chainId, FetchState.WaitingForNewBlock) } else if remaining.contents < minimumAdmissionBudget { // More than 90% of the target pool is ready or reserved. Don't admit new // queries until a full admission unit becomes free. No wake-up poll is // needed: a saturated pool means some chain holds ready items or // in-flight reservations, so a batch completion or landing response is // guaranteed to schedule another tick that revisits this chain. - actionByChain->Utils.Dict.setByInt(chainId, FetchState.NothingToQuery) + actionByChain->ChainId.Dict.set(chainId, FetchState.NothingToQuery) } else { let isCold = cs->ChainState.effectiveDensity === None let chainTargetItems = @@ -296,12 +296,12 @@ let checkAndFetch = async ( | _ => None } switch cs->ChainState.getNextQuery(~chainTargetItems, ~maxTargetBlock?) { - | WaitingForNewBlock as action => actionByChain->Utils.Dict.setByInt(chainId, action) + | WaitingForNewBlock as action => actionByChain->ChainId.Dict.set(chainId, action) | NothingToQuery => // A chain below its head can emit no query when its budget went to // more-behind chains or the cross-chain alignment clamped its range to // nothing — idleOrWaitAction keeps it polling for new blocks. - actionByChain->Utils.Dict.setByInt(chainId, idleOrWaitAction(cs)) + actionByChain->ChainId.Dict.set(chainId, idleOrWaitAction(cs)) | Ready(queries) => { let consumed = queries->Array.reduce(0., (acc, query: FetchState.query) => @@ -325,7 +325,7 @@ let checkAndFetch = async ( "partitions": partitions, }) - actionByChain->Utils.Dict.setByInt(chainId, FetchState.Ready(queries)) + actionByChain->ChainId.Dict.set(chainId, FetchState.Ready(queries)) // Mark the queries in flight and reserve their size against the // shared budget; released as each response lands in // handleQueryResult. @@ -339,7 +339,7 @@ let checkAndFetch = async ( let promises = [] for i in 0 to crossChainState.chainIds->Array.length - 1 { let chainId = crossChainState.chainIds->Array.getUnsafe(i) - switch actionByChain->Utils.Dict.dangerouslyGetByIntNonOption(chainId) { + switch actionByChain->ChainId.Dict.dangerouslyGetNonOption(chainId) { | Some(NothingToQuery) | None => () | Some(action) => diff --git a/packages/envio/src/EventConfigBuilder.res b/packages/envio/src/EventConfigBuilder.res index 9d42f7d88f..96fc8a1ec1 100644 --- a/packages/envio/src/EventConfigBuilder.res +++ b/packages/envio/src/EventConfigBuilder.res @@ -397,7 +397,7 @@ let buildEvmOnEventRegistration = ( ~handler: option, ~contractRegister: option, ~where: option, - ~chainId: int, + ~chainId: ChainId.t, ~onEventBlockFilterSchema: S.t>, ~startBlock: option=?, ): Internal.evmOnEventRegistration => { diff --git a/packages/envio/src/EventProcessing.res b/packages/envio/src/EventProcessing.res index caa0186e09..b6f47ba7e7 100644 --- a/packages/envio/src/EventProcessing.res +++ b/packages/envio/src/EventProcessing.res @@ -13,9 +13,9 @@ let computeChainsState = (chainStates: dict): Internal.chains => { values->Array.forEach(cs => { let chainId = (cs->ChainState.chainConfig).id chains->Dict.set( - chainId->Int.toString, + chainId->ChainId.toString, { - Internal.id: chainId, + Internal.id: chainId->ChainId.toInt, isRealtime, }, ) @@ -280,7 +280,7 @@ let registerProcessEventBatchMetrics = ( batch.progressedChainsById->Dict.forEachWithKey((chainAfterBatch, chainId) => { logger->Logging.childTrace({ "msg": "Finished processing", - "chainId": chainId->Int.fromString->Option.getUnsafe, + "chainId": chainId, "batchSize": chainAfterBatch.batchSize, "progress": chainAfterBatch.progressBlockNumber, }) @@ -312,7 +312,7 @@ let materializeBatchEvents = async ( | _ => let itemsByChain: dict> = Dict.make() batch.items->Array.forEach(item => { - let chainId = item->Internal.getItemChainId->Int.toString + let chainId = item->Internal.getItemChainId->ChainId.toString switch itemsByChain->Utils.Dict.dangerouslyGetNonOption(chainId) { | Some(items) => items->Array.push(item) | None => itemsByChain->Dict.set(chainId, [item]) @@ -345,7 +345,7 @@ let processEventBatch = async ( batch.progressedChainsById->Dict.forEachWithKey((chainAfterBatch, chainId) => { logger->Logging.childTrace({ "msg": "Started processing", - "chainId": chainId->Int.fromString->Option.getUnsafe, + "chainId": chainId, "batchSize": chainAfterBatch.batchSize, }) }) diff --git a/packages/envio/src/FetchState.res b/packages/envio/src/FetchState.res index 9c1ca0dd76..a423c6c5d6 100644 --- a/packages/envio/src/FetchState.res +++ b/packages/envio/src/FetchState.res @@ -667,7 +667,7 @@ type t = { // By contract name contractConfigs: dict, // Not used for logic - only metadata - chainId: int, + chainId: ChainId.t, // The block number of the latest block which was added to the queue // by the onBlock configs // Need a separate pointer for this @@ -2459,7 +2459,7 @@ let make = ( ~contractConfigs: dict, ~addresses: array, ~maxAddrInPartition, - ~chainId, + ~chainId: ChainId.t, ~maxOnBlockBufferSize, ~knownHeight, ~progressBlockNumber=startBlock - 1, @@ -2571,7 +2571,7 @@ let make = ( onBlockRegistrations->Utils.Array.isEmpty ) { JsError.throwWithMessage( - `Invalid configuration: Nothing to fetch on chain ${chainId->Int.toString}. ` ++ + `Invalid configuration: Nothing to fetch on chain ${chainId->ChainId.toString}. ` ++ `addresses=${addresses->Array.length->Int.toString}, ` ++ `onEventRegistrations=${onEventRegistrations->Array.length->Int.toString}, ` ++ `normalRegistrations=${normalRegistrations diff --git a/packages/envio/src/HandlerRegister.res b/packages/envio/src/HandlerRegister.res index 28db369576..7005f4db95 100644 --- a/packages/envio/src/HandlerRegister.res +++ b/packages/envio/src/HandlerRegister.res @@ -82,8 +82,8 @@ let startRegistration = (~config: Config.t) => { } } -let getChainRegistrations = (r: activeRegistration, ~chainId: int): chainRegistrations => { - let key = chainId->Int.toString +let getChainRegistrations = (r: activeRegistration, ~chainId: ChainId.t): chainRegistrations => { + let key = chainId->ChainId.toString switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(key) { | Some(existing) => existing | None => @@ -98,7 +98,7 @@ let getChainRegistrations = (r: activeRegistration, ~chainId: int): chainRegistr let buildOnEventRegistrationWith = ( ~config: Config.t, - ~chainId: int, + ~chainId: ChainId.t, ~eventConfig: Internal.eventConfig, ~isWildcard: bool, ~handler: option, @@ -342,10 +342,10 @@ let setContractRegister = (~contractName, ~eventName, contractRegister, ~eventOp } // Raw onEvent registrations stored for a chain (empty if the chain has none). -let storedOnEventRegistrations = (r: activeRegistration, ~chainId: int): array< +let storedOnEventRegistrations = (r: activeRegistration, ~chainId: ChainId.t): array< Internal.onEventRegistration, > => - switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(chainId->Int.toString) { + switch r.registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(chainId->ChainId.toString) { | Some(chainRegs) => chainRegs.onEventRegistrations | None => [] } @@ -373,7 +373,7 @@ let isWildcard = (~contractName, ~eventName) => // item still produces an item to run. let getSimulateOnEventRegistrations = ( ~config: Config.t, - ~chainId: int, + ~chainId: ChainId.t, ~eventConfig: Internal.eventConfig, ): array => { let stored = switch getActiveRegistration() { @@ -412,7 +412,7 @@ let finishRegistration = (~config: Config.t): registrationsByChainId => { ->ChainMap.values ->Array.forEach(chainConfig => { let chainId = chainConfig.id - let key = chainId->Int.toString + let key = chainId->ChainId.toString let builtRegs = mergeRegistrations(r->storedOnEventRegistrations(~chainId), ~config) let registeredKeys = Utils.Set.make() @@ -638,7 +638,7 @@ let registerOnBlock = ( ->ChainMap.values ->Array.forEach(chainConfig => { let chainId = chainConfig.id - let chainObj = chainsDict->Dict.getUnsafe(chainId->Int.toString) + let chainObj = chainsDict->Dict.getUnsafe(chainId->ChainId.toString) // Predicate returns `true` → match with no filter; `false` → skip; // any plain object → structured filter. `undefined`/`null` returns diff --git a/packages/envio/src/HandlerRegister.resi b/packages/envio/src/HandlerRegister.resi index 632a19b0a9..93caf0758e 100644 --- a/packages/envio/src/HandlerRegister.resi +++ b/packages/envio/src/HandlerRegister.resi @@ -26,7 +26,7 @@ let isWildcard: (~contractName: string, ~eventName: string) => bool let isDroppedByWhere: (~config: Config.t, Internal.onEventRegistration) => bool let getSimulateOnEventRegistrations: ( ~config: Config.t, - ~chainId: int, + ~chainId: ChainId.t, ~eventConfig: Internal.eventConfig, ) => array diff --git a/packages/envio/src/IndexerState.res b/packages/envio/src/IndexerState.res index e3727a3953..97e74485df 100644 --- a/packages/envio/src/IndexerState.res +++ b/packages/envio/src/IndexerState.res @@ -276,7 +276,7 @@ let makeFromDbState = ( initialState.chains->Array.forEach((resumedChainState: Persistence.initialChainState) => { let chain = Config.getChain(config, ~chainId=resumedChainState.id) let chainConfig = config.chainMap->ChainMap.get(chain) - chainStates->Utils.Dict.setByInt( + chainStates->ChainId.Dict.set( resumedChainState.id, chainConfig->ChainState.makeFromDbState( ~resumedChainState, @@ -361,7 +361,7 @@ let stop = (state: t) => { let getChainState = (state: t, ~chain: chain): ChainState.t => switch state.crossChainState ->CrossChainState.chainStates - ->Utils.Dict.dangerouslyGetByIntNonOption(chain->ChainMap.Chain.toChainId) { + ->ChainId.Dict.dangerouslyGetNonOption(chain->ChainMap.Chain.toChainId) { | Some(cs) => cs | None => // Should be unreachable, since we validate on Chain.t creation diff --git a/packages/envio/src/Internal.res b/packages/envio/src/Internal.res index 644e75c58b..591f18af11 100644 --- a/packages/envio/src/Internal.res +++ b/packages/envio/src/Internal.res @@ -301,7 +301,7 @@ type genericEvent<'params, 'block, 'transaction> = { contractName: string, eventName: string, params: 'params, - chainId: int, + chainId: ChainId.t, srcAddress: Address.t, logIndex: int, transaction: 'transaction, @@ -364,6 +364,7 @@ type entityHandlerContext<'entity> = { } type chainInfo = { + // `int` rather than `ChainId.t`: this is the handler-facing `context.chain`. id: int, // True once every chain has caught up to head/endBlock and entered real-time // indexing mode. False while any chain is still backfilling. @@ -628,7 +629,7 @@ type eventItem = private { // `InternalTable`) so the ecosystem's `toRawEvent` can reference it without // pulling in `InternalTable`'s dependency on `Config`. type rawEvent = { - chain_id: int, + chain_id: ChainId.t, event_id: bigint, event_name: string, contract_name: string, @@ -656,7 +657,7 @@ type onBlockRegistration = { // we want to use the order they are defined for sorting index: int, name: string, - chainId: int, + chainId: ChainId.t, startBlock: option, endBlock: option, interval: int, @@ -787,7 +788,7 @@ type effect = { @unboxed type chainScope = | @as("crossChain") CrossChain - | Chain(int) + | Chain(ChainId.t) let cacheTablePrefix = "envio_effect_" @@ -795,27 +796,29 @@ let cacheTablePrefix = "envio_effect_" // canonical Postgres cache-table name and .envio/cache file path. Everything // that needs a cache address goes through here instead of slicing prefixes. // CrossChain -> envio_effect_ .tsv -// Chain(1) -> envio_1_effect_ 1/.tsv -// Chain(137) -> envio_137_effect_ 137/.tsv +// Chain(1->ChainId.fromInt) -> envio_1_effect_ 1/.tsv +// Chain(137->ChainId.fromInt) -> envio_137_effect_ 137/.tsv module EffectCache = { let toTableName = (~effectName, ~scope) => switch scope { | CrossChain => cacheTablePrefix ++ effectName - | Chain(chainId) => `envio_${chainId->Int.toString}_effect_${effectName}` + | Chain(chainId) => `envio_${chainId->ChainId.toString}_effect_${effectName}` } // "crossChain" or the decimal chain id. Used as the `scope` Prometheus label. let scopeToString = scope => switch scope { | CrossChain => "crossChain" - | Chain(chainId) => chainId->Int.toString + | Chain(chainId) => chainId->ChainId.toString } // Only accepts a canonical decimal chain id ("7", not "007" or "1foo") — - // Int.fromString alone follows parseInt semantics and accepts both. + // the schema's parser follows parseFloat semantics and accepts both. let parseChainId = str => - switch Int.fromString(str) { - | Some(chainId) if chainId >= 0 && chainId->Int.toString === str => Some(chainId) + switch try Some(str->ChainId.normalizeOrThrow) catch { + | _ => None + } { + | Some(chainId) if chainId->ChainId.toString === str => Some(chainId) | _ => None } @@ -855,7 +858,7 @@ module EffectCache = { let toCachePath = (~effectName, ~scope) => switch scope { | CrossChain => effectName ++ ".tsv" - | Chain(chainId) => `${chainId->Int.toString}/${effectName}.tsv` + | Chain(chainId) => `${chainId->ChainId.toString}/${effectName}.tsv` } } @@ -884,7 +887,7 @@ type reorgCheckpoint = { @as("id") checkpointId: bigint, @as("chain_id") - chainId: int, + chainId: ChainId.t, @as("block_number") blockNumber: int, @as("block_hash") diff --git a/packages/envio/src/LoadLayer.res b/packages/envio/src/LoadLayer.res index 5b1376fba7..66bb2edd16 100644 --- a/packages/envio/src/LoadLayer.res +++ b/packages/envio/src/LoadLayer.res @@ -226,7 +226,7 @@ let loadEffect = ( // the storage-load metric stays stable. let key = switch scope { | CrossChain => `${effectName}.effect` - | Chain(chainId) => `${effectName}.effect.${chainId->Int.toString}` + | Chain(chainId) => `${effectName}.effect.${chainId->ChainId.toString}` } let load = async (args, ~onError) => { diff --git a/packages/envio/src/LogSelection.res b/packages/envio/src/LogSelection.res index 8a2fd7c8d7..33ff2fdc51 100644 --- a/packages/envio/src/LogSelection.res +++ b/packages/envio/src/LogSelection.res @@ -84,7 +84,7 @@ let extractStartBlock = ( // getter — the enclosing chainObj is a plain JS object. let makeChainArg = ( ~contractName: string, - ~chainId: int, + ~chainId: ChainId.t, ~getAddresses: unit => array, ) => { let contractObj = Utils.Object.createNullObject() @@ -124,7 +124,7 @@ let parseWhereOrThrow = { ~sighash, ~params: array, ~contractName: string, - ~chainId: int, + ~chainId: ChainId.t, ~onEventBlockFilterSchema: S.t>, ~topic1=noopGetter, ~topic2=noopGetter, diff --git a/packages/envio/src/Main.res b/packages/envio/src/Main.res index 4d413ff51d..7da20074a0 100644 --- a/packages/envio/src/Main.res +++ b/packages/envio/src/Main.res @@ -2,7 +2,7 @@ // backward compatibility with consumers like RACE — new metric fields stay off // the HTTP response. type chainData = { - chainId: float, + chainId: ChainId.t, poweredByHyperSync: bool, firstEventBlockNumber: option, latestProcessedBlock: option, @@ -46,7 +46,7 @@ let toChainData = (m: Metrics.chainMetrics): chainData => { } let chainDataSchema = S.schema((s): chainData => { - chainId: s.matches(S.float), + chainId: s.matches(ChainId.schema), poweredByHyperSync: s.matches(S.bool), firstEventBlockNumber: s.matches(S.option(S.int)), latestProcessedBlock: s.matches(S.option(S.int)), @@ -88,7 +88,7 @@ let getGlobalPersistence = () => let setGlobalPersistence = (persistence: Persistence.t) => EnvioGlobal.value.persistence = Some(persistence->(Utils.magic: Persistence.t => unknown)) -let getInitialChainState = (~chainId: int): option => { +let getInitialChainState = (~chainId: ChainId.t): option => { switch getGlobalPersistence() { | Some(persistence) => switch persistence.storageStatus { @@ -107,7 +107,7 @@ let buildChainsObject = (~config: Config.t) => { config.chainMap ->ChainMap.values ->Array.forEach(chainConfig => { - let chainIdStr = chainConfig.id->Int.toString + let chainIdStr = chainConfig.id->ChainId.toString chainIds->Array.push(chainConfig.id)->ignore @@ -423,7 +423,7 @@ let getGlobalIndexer = (): 'indexer => { | "description" => Config.load().description->(Utils.magic: option => unknown) | "chainIds" => { let (_, chainIds) = buildChainsObject(~config=Config.load()) - chainIds->(Utils.magic: array => unknown) + chainIds->(Utils.magic: array => unknown) } | "chains" => { let (chains, _) = buildChainsObject(~config=Config.load()) diff --git a/packages/envio/src/Metrics.res b/packages/envio/src/Metrics.res index fc8f539aad..8ed044a76a 100644 --- a/packages/envio/src/Metrics.res +++ b/packages/envio/src/Metrics.res @@ -1,5 +1,5 @@ type chainMetrics = { - chainId: float, + chainId: ChainId.t, poweredByHyperSync: bool, firstEventBlockNumber: option, latestProcessedBlock: option, @@ -84,7 +84,7 @@ type historyPruneMetrics = { type sourceRequestMetrics = { source: string, - chainId: int, + chainId: ChainId.t, method: string, count: int, seconds: float, @@ -92,7 +92,7 @@ type sourceRequestMetrics = { type sourceHeightMetrics = { source: string, - chainId: int, + chainId: ChainId.t, height: int, } @@ -198,7 +198,7 @@ let single = (b: builder, ~name, ~help, ~kind, ~value) => { } let renderMetrics = (b: builder, metrics: t) => { - let chains = metrics.chains->Array.map(m => (`{chainId="${m.chainId->Float.toString}"}`, m)) + let chains = metrics.chains->Array.map(m => (`{chainId="${m.chainId->ChainId.toString}"}`, m)) let handlers = metrics.handlers->Array.map(s => ( `{contract="${s.contract->escapeLabelValue}",event="${s.event->escapeLabelValue}"}`, @@ -224,7 +224,7 @@ let renderMetrics = (b: builder, metrics: t) => { let sourceRequests = { let byLabels: dict = Dict.make() metrics.sourceRequests->Array.forEach(s => { - let labels = `{source="${s.source->escapeLabelValue}",chainId="${s.chainId->Int.toString}",method="${s.method->escapeLabelValue}"}` + let labels = `{source="${s.source->escapeLabelValue}",chainId="${s.chainId->ChainId.toString}",method="${s.method->escapeLabelValue}"}` switch byLabels->Utils.Dict.dangerouslyGetNonOption(labels) { | Some(existing) => byLabels->Dict.set( @@ -239,7 +239,7 @@ let renderMetrics = (b: builder, metrics: t) => { let sources = { let byLabels: dict = Dict.make() metrics.sourceHeights->Array.forEach(s => { - let labels = `{source="${s.source->escapeLabelValue}",chainId="${s.chainId->Int.toString}"}` + let labels = `{source="${s.source->escapeLabelValue}",chainId="${s.chainId->ChainId.toString}"}` switch byLabels->Utils.Dict.dangerouslyGetNonOption(labels) { | Some(existing) if existing >= s.height => () | _ => byLabels->Dict.set(labels, s.height) diff --git a/packages/envio/src/Persistence.res b/packages/envio/src/Persistence.res index 99a9899b31..2af423863f 100644 --- a/packages/envio/src/Persistence.res +++ b/packages/envio/src/Persistence.res @@ -19,7 +19,7 @@ type effectCacheRecord = { } type initialChainState = { - id: int, + id: ChainId.t, startBlock: int, endBlock: option, maxReorgDepth: int, @@ -104,7 +104,7 @@ type storage = { ) => promise, // Get rollback target checkpoint getRollbackTargetCheckpoint: ( - ~reorgChainId: int, + ~reorgChainId: ChainId.t, ~lastKnownValidBlockNumber: int, ) => promise>, // Get rollback progress diff @@ -112,7 +112,7 @@ type storage = { ~rollbackTargetCheckpointId: Internal.checkpointId, ) => promise< array<{ - "chain_id": int, + "chain_id": ChainId.t, "events_processed_diff": string, "new_progress_block_number": int, }>, @@ -240,7 +240,7 @@ let init = { persistence.storageStatus = Ready(initialState) let progress = Dict.make() initialState.chains->Array.forEach(c => { - progress->Utils.Dict.setByInt(c.id, c.progressBlockNumber) + progress->ChainId.Dict.set(c.id, c.progressBlockNumber) }) Logging.info({ "msg": `Successfully resumed indexing state! Continuing from the last checkpoint.`, diff --git a/packages/envio/src/PgStorage.res b/packages/envio/src/PgStorage.res index b8d5cdd4af..88e9f58b1f 100644 --- a/packages/envio/src/PgStorage.res +++ b/packages/envio/src/PgStorage.res @@ -912,7 +912,7 @@ let rec writeBatch = async ( | Internal.Event(_) => let coordinate = `${item ->Internal.getItemChainId - ->Int.toString}-${item + ->ChainId.toString}-${item ->Internal.getItemBlockNumber ->Int.toString}-${item->Internal.getItemLogIndex->Int.toString}` if seenLogCoordinates->Utils.Set.has(coordinate) { @@ -1780,7 +1780,7 @@ SELECT id, chain_id, -1, -1, contract_name FROM unnest($1::text[],$2::${addrChai // Convert string checkpoint IDs from DB to bigint let reorgCheckpoints = Array.map(reorgCheckpoints, (raw): Internal.reorgCheckpoint => { checkpointId: raw["id"]->BigInt.fromStringOrThrow, - chainId: raw["chain_id"]->ChainId.normalizeOrThrow->ChainId.toInt, + chainId: raw["chain_id"]->ChainId.normalizeOrThrow, blockNumber: raw["block_number"], blockHash: raw["block_hash"], }) diff --git a/packages/envio/src/Rollback.res b/packages/envio/src/Rollback.res index fde853eb29..3c18b68de4 100644 --- a/packages/envio/src/Rollback.res +++ b/packages/envio/src/Rollback.res @@ -140,7 +140,7 @@ and executeRollback = async ( ).storage.getRollbackProgressDiff(~rollbackTargetCheckpointId) for idx in 0 to rollbackProgressDiff->Array.length - 1 { let diff = rollbackProgressDiff->Array.getUnsafe(idx) - eventsProcessedDiffByChain->Utils.Dict.setByInt( + eventsProcessedDiffByChain->ChainId.Dict.set( diff["chain_id"], { let eventsProcessedDiff = @@ -149,7 +149,7 @@ and executeRollback = async ( eventsProcessedDiff }, ) - newProgressBlockNumberPerChain->Utils.Dict.setByInt( + newProgressBlockNumberPerChain->ChainId.Dict.set( diff["chain_id"], if rollbackTargetCheckpointId === 0n && diff["chain_id"] === reorgChainId { Pervasives.min(diff["new_progress_block_number"], rollbackTargetBlockNumber) @@ -167,10 +167,10 @@ and executeRollback = async ( let chainId = (cs->ChainState.chainConfig).id let fromBlock = cs->ChainState.committedProgressBlockNumber cs->ChainState.rollback( - ~newProgressBlockNumber=newProgressBlockNumberPerChain->Utils.Dict.dangerouslyGetByIntNonOption( + ~newProgressBlockNumber=newProgressBlockNumberPerChain->ChainId.Dict.dangerouslyGetNonOption( chainId, ), - ~eventsProcessedDiff=eventsProcessedDiffByChain->Utils.Dict.dangerouslyGetByIntNonOption( + ~eventsProcessedDiff=eventsProcessedDiffByChain->ChainId.Dict.dangerouslyGetNonOption( chainId, ), ~rollbackTargetBlockNumber, @@ -184,7 +184,7 @@ and executeRollback = async ( "fromBlock": fromBlock, "toBlock": toBlock, "rollbackedEvents": eventsProcessedDiffByChain - ->Utils.Dict.dangerouslyGetByIntNonOption(chainId) + ->ChainId.Dict.dangerouslyGetNonOption(chainId) ->Option.getOr(0.), }) ->ignore diff --git a/packages/envio/src/RollbackCommit.res b/packages/envio/src/RollbackCommit.res index fda3faf159..0660908735 100644 --- a/packages/envio/src/RollbackCommit.res +++ b/packages/envio/src/RollbackCommit.res @@ -3,7 +3,7 @@ // feature lives here plus two call sites: registration in `Main.res` and the // fire on a successful rollback write in `InMemoryStore.res`. Delete those // together with this module. -type args = {chainId: int, rollbackToBlock: int} +type args = {chainId: ChainId.t, rollbackToBlock: int} type callback = args => promise // Lives in the process-wide `EnvioGlobal` record so callbacks registered @@ -28,7 +28,7 @@ let fire = async (~progressBlockNumberByChainId: dict) => { let _ = await progressBlockNumberByChainId ->Dict.toArray ->Array.flatMap(((chainIdKey, rollbackToBlock)) => { - let args = {chainId: chainIdKey->Int.fromString->Option.getUnsafe, rollbackToBlock} + let args = {chainId: chainIdKey->ChainId.normalizeOrThrow, rollbackToBlock} callbacks->Array.map(callback => callback(args)) }) ->Promise.all diff --git a/packages/envio/src/SafeCheckpointTracking.res b/packages/envio/src/SafeCheckpointTracking.res index 03319e033b..64db9c9d5e 100644 --- a/packages/envio/src/SafeCheckpointTracking.res +++ b/packages/envio/src/SafeCheckpointTracking.res @@ -70,10 +70,10 @@ let getSafeCheckpointId = (safeCheckpointTracking: t, ~sourceBlockNumber: int) = let updateOnNewBatch = ( safeCheckpointTracking: t, ~sourceBlockNumber: int, - ~chainId: int, + ~chainId: ChainId.t, ~batchCheckpointIds: array, ~batchCheckpointBlockNumbers: array, - ~batchCheckpointChainIds: array, + ~batchCheckpointChainIds: array, ) => { let safeCheckpointId = getSafeCheckpointId(safeCheckpointTracking, ~sourceBlockNumber) diff --git a/packages/envio/src/SimulateDeadInputTracker.res b/packages/envio/src/SimulateDeadInputTracker.res index 1081448163..de20866320 100644 --- a/packages/envio/src/SimulateDeadInputTracker.res +++ b/packages/envio/src/SimulateDeadInputTracker.res @@ -8,14 +8,13 @@ let itemKey = (item: Internal.item): string => switch item { | Internal.Event({chain, blockNumber, logIndex}) => `${chain - ->ChainMap.Chain.toChainId - ->Int.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}` + ->ChainMap.Chain.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}` | Internal.Block(_) => "" } // `index` is the item's position in its chain's `simulate` array, reported back // so a user finds it without echoing its fields. -type entry = {chainId: int, index: int, key: string} +type entry = {chainId: ChainId.t, index: int, key: string} type t = {mutable unprocessed: array} @@ -52,17 +51,20 @@ let recordProcessed = (t: t, ~batch: Batch.t) => { } // Unrouted item indices grouped by chain, in the order chains were first seen. -let unroutedByChain = (t: t): array<(int, array)> => { +let unroutedByChain = (t: t): array<(ChainId.t, array)> => { let indicesByChain = Dict.make() let chainOrder = [] t.unprocessed->Array.forEach(entry => { - let key = entry.chainId->Int.toString + let key = entry.chainId->ChainId.toString if indicesByChain->Dict.get(key)->Option.isNone { chainOrder->Array.push(entry.chainId)->ignore } indicesByChain->Utils.Dict.push(key, entry.index) }) - chainOrder->Array.map(chainId => (chainId, indicesByChain->Dict.getUnsafe(chainId->Int.toString))) + chainOrder->Array.map(chainId => ( + chainId, + indicesByChain->Dict.getUnsafe(chainId->ChainId.toString), + )) } let failureMessage = (t: t): option => @@ -74,7 +76,7 @@ let failureMessage = (t: t): option => let lines = byChain ->Array.map(((chainId, indices)) => - ` - chain ${chainId->Int.toString}: ${indices + ` - chain ${chainId->ChainId.toString}: ${indices ->Array.map(index => index->Int.toString) ->Array.join(", ")}` ) diff --git a/packages/envio/src/SimulateItems.res b/packages/envio/src/SimulateItems.res index fc4a3bb054..373ab518dc 100644 --- a/packages/envio/src/SimulateItems.res +++ b/packages/envio/src/SimulateItems.res @@ -334,7 +334,7 @@ let parse = ( switch seenCoordinates->Dict.get(coordinate) { | Some(firstIndex) => JsError.throwWithMessage( - `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chainId->Int.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`, + `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chainId->ChainId.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`, ) | None => seenCoordinates->Dict.set(coordinate, itemIndex) } @@ -363,7 +363,7 @@ let parse = ( ->ChainMap.values ->Array.length { | 1 => "" - | _ => ` on chain ${chainId->Int.toString}` + | _ => ` on chain ${chainId->ChainId.toString}` }}. Register a handler with indexer.onEvent (and check any \`where\` filter isn't excluding this chain) before simulating it.`, ) } @@ -421,7 +421,7 @@ let patchConfig = ( switch processChains { | Some(chainsDict) => let newChainMap = config.chainMap->ChainMap.mapWithKey((chain, chainConfig) => { - let chainIdStr = chain->ChainMap.Chain.toChainId->Int.toString + let chainIdStr = chain->ChainMap.Chain.toString switch chainsDict->Dict.get(chainIdStr) { | Some(processChainJson) => let raw = processChainJson->(Utils.magic: JSON.t => {..}) diff --git a/packages/envio/src/TestIndexer.res b/packages/envio/src/TestIndexer.res index 71362679ea..422c26a848 100644 --- a/packages/envio/src/TestIndexer.res +++ b/packages/envio/src/TestIndexer.res @@ -55,7 +55,7 @@ let getIndexingAddressesByChain = (state: testIndexerState): dict< ->Dict.valuesToArray ->Array.forEach(entity => { let dc = entity->castToEnvioAddresses - let chainIdStr = dc.chainId->Int.toString + let chainIdStr = dc.chainId->ChainId.toString let contracts = switch byChain->Dict.get(chainIdStr) { | Some(arr) => arr | None => @@ -96,7 +96,7 @@ let handleWriteBatch = ( state: testIndexerState, ~updatedEntities: array, ~checkpointIds: array, - ~checkpointChainIds: array, + ~checkpointChainIds: array, ~checkpointBlockNumbers: array, ~checkpointEventsProcessed: array, ): unit => { @@ -158,7 +158,7 @@ let handleWriteBatch = ( // Update progress tracking from checkpoint data state.progressBlockByChain->Dict.set( - checkpointChainIds->Array.getUnsafe(i)->Int.toString, + checkpointChainIds->Array.getUnsafe(i)->ChainId.toString, checkpointBlockNumbers->Array.getUnsafe(i), ) @@ -223,7 +223,7 @@ let makeInitialState = ( ): Persistence.initialState => { let chainKeys = processConfigChains->Dict.keysToArray let chains = chainKeys->Array.map(chainIdStr => { - let chainId = chainIdStr->Int.fromString->Option.getOr(0) + let chainId = chainIdStr->ChainId.normalizeOrThrow let chain = ChainMap.Chain.makeUnsafe(~chainId) if !(config.chainMap->ChainMap.has(chain)) { @@ -310,10 +310,8 @@ let parseBlockRange = ( ~rawChainConfig: rawChainConfig, ~progressBlock: option, ): chainConfig => { - let chainId = switch chainIdStr->Int.fromString { - | Some(id) => id - | None => - JsError.throwWithMessage(`Invalid chain ID "${chainIdStr}": expected a numeric chain ID`) + let chainId = try chainIdStr->ChainId.normalizeOrThrow catch { + | _ => JsError.throwWithMessage(`Invalid chain ID "${chainIdStr}": expected a numeric chain ID`) } let chain = ChainMap.Chain.makeUnsafe(~chainId) if !(config.chainMap->ChainMap.has(chain)) { @@ -638,7 +636,7 @@ let createTestIndexer = (): t<'processConfig> => { config.chainMap ->ChainMap.values ->Array.forEach(chainConfig => { - let chainIdStr = chainConfig.id->Int.toString + let chainIdStr = chainConfig.id->ChainId.toString chainIds->Array.push(chainConfig.id)->ignore let chainObj = Utils.Object.createNullObject() @@ -673,7 +671,7 @@ let createTestIndexer = (): t<'processConfig> => { ) } getIndexingAddressesByChain(state) - ->Dict.get(chainConfig.id->Int.toString) + ->Dict.get(chainConfig.id->ChainId.toString) ->Option.getOr([]) ->Array.filterMap(ia => ia.contractName === contract.name ? Some(ia.address) : None) }, @@ -699,7 +697,7 @@ let createTestIndexer = (): t<'processConfig> => { // Build the result object with process + entity operations + chain info let result: dict = Dict.make() - result->Dict.set("chainIds", chainIds->(Utils.magic: array => unknown)) + result->Dict.set("chainIds", chainIds->(Utils.magic: array => unknown)) result->Dict.set("chains", chains->(Utils.magic: {..} => unknown)) entityOpsDict ->Dict.toArray diff --git a/packages/envio/src/UserContext.res b/packages/envio/src/UserContext.res index 2a24dea377..362da3d1ec 100644 --- a/packages/envio/src/UserContext.res +++ b/packages/envio/src/UserContext.res @@ -52,7 +52,7 @@ EffectContext.prototype = effectContextPrototype; @new external makeEffectContext: ( contextParams, - ~chainId: option, + ~chainId: option, ~effectName: string, ~defaultShouldCache: bool, ~callEffect: (Internal.effect, Internal.effectInput) => promise, @@ -318,7 +318,7 @@ let handlerTraps: Utils.Proxy.traps = { | "chain" => let chainId = params.item->Internal.getItemChainId params.chains - ->Utils.Dict.dangerouslyGetByIntNonOption(chainId) + ->ChainId.Dict.dangerouslyGetNonOption(chainId) ->(Utils.magic: option => unknown) | _ => switch params.config.userEntitiesByName->Utils.Dict.dangerouslyGetNonOption(prop) { diff --git a/packages/envio/src/bindings/ClickHouse.res b/packages/envio/src/bindings/ClickHouse.res index 52a8cbabd9..bb1a69f389 100644 --- a/packages/envio/src/bindings/ClickHouse.res +++ b/packages/envio/src/bindings/ClickHouse.res @@ -129,7 +129,7 @@ let makeClickHouseEntitySchema = (table: Table.table): S.t => { dateSchema } } - | ChainId => ChainId.intSchema->S.toUnknown + | ChainId => ChainId.schema->S.toUnknown // ClickHouse returns UInt64 values as strings, need to parse to float | UInt52 => { let uint52Schema = diff --git a/packages/envio/src/db/EntityHistory.res b/packages/envio/src/db/EntityHistory.res index acba42ddb5..aa20a87cea 100644 --- a/packages/envio/src/db/EntityHistory.res +++ b/packages/envio/src/db/EntityHistory.res @@ -64,7 +64,7 @@ let historyTableName = (~entityName, ~entityIndex) => { } type safeReorgBlocks = { - chainIds: array, + chainIds: array, blockNumbers: array, } diff --git a/packages/envio/src/db/InternalTable.res b/packages/envio/src/db/InternalTable.res index 91e48e1654..8e2c114e79 100644 --- a/packages/envio/src/db/InternalTable.res +++ b/packages/envio/src/db/InternalTable.res @@ -56,7 +56,7 @@ module Chains = { } type t = { - @as("id") id: int, + @as("id") id: ChainId.t, @as("start_block") startBlock: int, @as("end_block") endBlock: Null.t, @as("max_reorg_depth") maxReorgDepth: int, @@ -69,7 +69,7 @@ module Chains = { let table = mkTable( "envio_chains", ~fields=[ - mkField((#id: field :> string), ChainId, ~fieldSchema=ChainId.intSchema, ~isPrimaryKey), + mkField((#id: field :> string), ChainId, ~fieldSchema=ChainId.schema, ~isPrimaryKey), // Values populated from config mkField((#start_block: field :> string), Int32, ~fieldSchema=S.int), mkField((#end_block: field :> string), Int32, ~fieldSchema=S.null(S.int), ~isNullable), @@ -165,7 +165,7 @@ WHERE "${(#id: field :> string)}" = $1;` } type rawInitialState = { - id: int, + id: ChainId.t, startBlock: int, endBlock: Null.t, maxReorgDepth: int, @@ -191,7 +191,7 @@ FROM "${pgSchema}"."${table.tableName}";` } type rawIndexingAddress = { - chainId: int, + chainId: ChainId.t, address: Address.t, contractName: string, registrationBlock: int, @@ -247,7 +247,7 @@ FROM "${pgSchema}"."${EnvioAddresses.table.tableName}";` let id = rawInitialState.id->ChainId.normalizeOrThrow { ...rawInitialState, - id: id->ChainId.toInt, + id, indexingAddresses: indexingAddressesByChainId ->Dict.get(id->ChainId.toString) ->Option.getOr([]), @@ -294,7 +294,7 @@ WHERE "id" = $1;` } type progressedChain = { - chainId: int, + chainId: ChainId.t, progressBlockNumber: int, sourceBlockNumber: int, totalEventsProcessed: float, @@ -308,7 +308,7 @@ WHERE "id" = $1;` progressedChains->Array.forEach(data => { let params = [] - params->Array.push(data.chainId->(Utils.magic: int => unknown))->ignore + params->Array.push(data.chainId->(Utils.magic: ChainId.t => unknown))->ignore progressFields->Array.forEach(field => { params @@ -392,7 +392,7 @@ module Checkpoints = { type t = { id: bigint, @as("chain_id") - chainId: int, + chainId: ChainId.t, @as("block_number") blockNumber: int, @as("block_hash") @@ -404,7 +404,7 @@ module Checkpoints = { // Schema for parsing DB results where BIGINT columns come back as strings let dbSchema = S.object(s => { id: s.field("id", Utils.BigInt.schema), - chainId: s.field("chain_id", ChainId.intSchema), + chainId: s.field("chain_id", ChainId.schema), blockNumber: s.field("block_number", S.int), blockHash: s.field( "block_hash", @@ -422,7 +422,7 @@ module Checkpoints = { "envio_checkpoints", ~fields=[ mkField((#id: field :> string), UInt64, ~fieldSchema=S.bigint, ~isPrimaryKey), - mkField((#chain_id: field :> string), ChainId, ~fieldSchema=ChainId.intSchema), + mkField((#chain_id: field :> string), ChainId, ~fieldSchema=ChainId.schema), mkField((#block_number: field :> string), Int32, ~fieldSchema=S.int), mkField((#block_hash: field :> string), String, ~fieldSchema=S.null(S.string), ~isNullable), mkField((#events_processed: field :> string), Int32, ~fieldSchema=S.int), @@ -497,7 +497,7 @@ SELECT * FROM unnest($1::${(BigInt: Postgres.columnType :> string)}[],$2::${chai checkpointEventsProcessed, )->( Utils.magic: ( - (array, array, array, array>, array) + (array, array, array, array>, array) ) => unknown ), ) @@ -538,7 +538,7 @@ LIMIT 1;` let getRollbackTargetCheckpoint = ( sql, ~pgSchema, - ~reorgChainId: int, + ~reorgChainId: ChainId.t, ~lastKnownValidBlockNumber: int, ) => { let rawResult: promise> = @@ -576,7 +576,7 @@ GROUP BY "${(#chain_id: field :> string)}";` ->( Utils.magic: promise => promise< array<{ - "chain_id": int, + "chain_id": ChainId.t, "events_processed_diff": string, "new_progress_block_number": int, }>, @@ -589,7 +589,7 @@ module RawEvents = { type t = Internal.rawEvent let schema = S.schema((s): t => { - chain_id: s.matches(ChainId.intSchema), + chain_id: s.matches(ChainId.schema), event_id: s.matches(S.bigint), event_name: s.matches(S.string), contract_name: s.matches(S.string), @@ -606,7 +606,7 @@ module RawEvents = { let table = mkTable( "raw_events", ~fields=[ - mkField("chain_id", ChainId, ~fieldSchema=ChainId.intSchema), + mkField("chain_id", ChainId, ~fieldSchema=ChainId.schema), mkField("event_id", UInt64, ~fieldSchema=S.bigint), mkField("event_name", String, ~fieldSchema=S.string), mkField("contract_name", String, ~fieldSchema=S.string), diff --git a/packages/envio/src/sources/Evm.res b/packages/envio/src/sources/Evm.res index e8ef5099dc..055153ce0a 100644 --- a/packages/envio/src/sources/Evm.res +++ b/packages/envio/src/sources/Evm.res @@ -6,7 +6,7 @@ type payload = { contractName: string, eventName: string, params: Internal.eventParams, - chainId: int, + chainId: ChainId.t, srcAddress: Address.t, logIndex: int, transaction?: Internal.eventTransaction, diff --git a/packages/envio/src/sources/Fuel.res b/packages/envio/src/sources/Fuel.res index 7055975233..77094e54f3 100644 --- a/packages/envio/src/sources/Fuel.res +++ b/packages/envio/src/sources/Fuel.res @@ -4,7 +4,7 @@ type payload = { contractName: string, eventName: string, params: Internal.eventParams, - chainId: int, + chainId: ChainId.t, srcAddress: Address.t, logIndex: int, transaction: Internal.eventTransaction, diff --git a/packages/envio/src/sources/HyperSync.resi b/packages/envio/src/sources/HyperSync.resi index e720b43c79..3ce874320b 100644 --- a/packages/envio/src/sources/HyperSync.resi +++ b/packages/envio/src/sources/HyperSync.resi @@ -43,7 +43,7 @@ let queryBlockData: ( ~client: HyperSyncClient.t, ~blockNumber: int, ~sourceName: string, - ~chainId: int, + ~chainId: ChainId.t, ~logger: Pino.t, ) => promise<( queryResponse>, @@ -54,7 +54,7 @@ let queryBlockDataMulti: ( ~client: HyperSyncClient.t, ~blockNumbers: array, ~sourceName: string, - ~chainId: int, + ~chainId: ChainId.t, ~logger: Pino.t, ) => promise<( queryResponse>, diff --git a/packages/envio/src/sources/RpcSource.res b/packages/envio/src/sources/RpcSource.res index 5fdeac73d9..bcf7650db0 100644 --- a/packages/envio/src/sources/RpcSource.res +++ b/packages/envio/src/sources/RpcSource.res @@ -637,7 +637,7 @@ let make = ( let urlHost = switch Utils.Url.getHostFromUrl(url) { | None => JsError.throwWithMessage( - `The RPC url for chain ${chainId->Int.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, + `The RPC url for chain ${chainId->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, ) | Some(host) => host } diff --git a/packages/envio/src/sources/SourceManager.res b/packages/envio/src/sources/SourceManager.res index f1123dfa02..1e84d5ca92 100644 --- a/packages/envio/src/sources/SourceManager.res +++ b/packages/envio/src/sources/SourceManager.res @@ -32,7 +32,7 @@ let recordRequestStats = (sourceState: sourceState, requestStats: array => { // observed height yet are skipped. type sourceHeightSample = { sourceName: string, - chainId: int, + chainId: ChainId.t, height: int, } diff --git a/packages/envio/src/sources/SourceManager.resi b/packages/envio/src/sources/SourceManager.resi index 264fae86e2..fe64abd18d 100644 --- a/packages/envio/src/sources/SourceManager.resi +++ b/packages/envio/src/sources/SourceManager.resi @@ -23,7 +23,7 @@ let getActiveSource: t => Source.t type requestStatSample = { sourceName: string, - chainId: int, + chainId: ChainId.t, method: string, count: int, seconds: float, @@ -33,7 +33,7 @@ let getRequestStatSamples: t => array type sourceHeightSample = { sourceName: string, - chainId: int, + chainId: ChainId.t, height: int, } diff --git a/packages/envio/src/sources/Svm.res b/packages/envio/src/sources/Svm.res index feea32b12f..0e7a1711a0 100644 --- a/packages/envio/src/sources/Svm.res +++ b/packages/envio/src/sources/Svm.res @@ -77,7 +77,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S let urlHost = switch Utils.Url.getHostFromUrl(rpc) { | None => JsError.throwWithMessage( - `The RPC url for chain ${chainId->Int.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, + `The RPC url for chain ${chainId->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, ) | Some(host) => host } diff --git a/packages/envio/src/tui/Tui.res b/packages/envio/src/tui/Tui.res index ce791524e7..6a26e41aad 100644 --- a/packages/envio/src/tui/Tui.res +++ b/packages/envio/src/tui/Tui.res @@ -204,7 +204,7 @@ module App = { knownHeight: data.knownHeight, latestFetchedBlockNumber, eventsProcessed: numEventsProcessed, - chainId: (cs->ChainState.chainConfig).id->Int.toString, + chainId: (cs->ChainState.chainConfig).id->ChainId.toString, progressBlock: committedProgressBlockNumber < data.startBlock ? Some(data.startBlock) : Some(committedProgressBlockNumber), diff --git a/packages/envio/src/tui/components/CustomHooks.res b/packages/envio/src/tui/components/CustomHooks.res index dcee0fe442..6cff9f23b2 100644 --- a/packages/envio/src/tui/components/CustomHooks.res +++ b/packages/envio/src/tui/components/CustomHooks.res @@ -4,16 +4,16 @@ module InitApi = { envioVersion: string, envioApiToken: option, ecosystem: ecosystem, - hyperSyncNetworks: array, - rpcNetworks: array, + hyperSyncNetworks: array, + rpcNetworks: array, } let bodySchema = S.object(s => { envioVersion: s.field("envioVersion", S.string), envioApiToken: s.field("envioApiToken", S.option(S.string)), ecosystem: s.field("ecosystem", S.enum([Evm, Fuel, Svm])), - hyperSyncNetworks: s.field("hyperSyncNetworks", S.array(S.int)), - rpcNetworks: s.field("rpcNetworks", S.array(S.int)), + hyperSyncNetworks: s.field("hyperSyncNetworks", S.array(ChainId.schema)), + rpcNetworks: s.field("rpcNetworks", S.array(ChainId.schema)), }) let makeBody = (~envioVersion, ~envioApiToken, ~config: Config.t) => { diff --git a/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res b/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res index f83df12553..e22d420d94 100644 --- a/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res +++ b/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res @@ -38,7 +38,7 @@ let withServer = async (handler, body) => { } describe("FuelHyperSyncSource - getHeightOrThrow", () => { - let chain = ChainMap.Chain.makeUnsafe(~chainId=0) + let chain = ChainMap.Chain.makeUnsafe(~chainId=0->ChainId.fromInt) // The native client validates that the token is a UUID before sending requests. let apiToken = "11111111-1111-1111-1111-111111111111" diff --git a/scenarios/test_codegen/test/ChainMeta_test.res b/scenarios/test_codegen/test/ChainMeta_test.res index f5d945a172..0532fb8aae 100644 --- a/scenarios/test_codegen/test/ChainMeta_test.res +++ b/scenarios/test_codegen/test/ChainMeta_test.res @@ -18,7 +18,7 @@ let emptyBatch = (~checkpointId): Batch.t => { progressedChainsById: Dict.make(), isInReorgThreshold: false, checkpointIds: [checkpointId], - checkpointChainIds: [1], + checkpointChainIds: [1->ChainId.fromInt], checkpointBlockNumbers: [checkpointId->BigInt.toInt], checkpointBlockHashes: [`0x${checkpointId->BigInt.toString}`->Null.make], checkpointEventsProcessed: [0], diff --git a/scenarios/test_codegen/test/E2E_test.res b/scenarios/test_codegen/test/E2E_test.res index 644040ae00..aedbfac116 100644 --- a/scenarios/test_codegen/test/E2E_test.res +++ b/scenarios/test_codegen/test/E2E_test.res @@ -28,14 +28,14 @@ describe("E2E tests", () => { ] t.expect( - await getChainAddresses(indexerMock, ~chainId=1337), + await getChainAddresses(indexerMock, ~chainId=1337->ChainId.fromInt), ~message="Config addresses should be inserted with registrationBlock=-1 on init", ).toEqual(expected) let restarted = await indexerMock.restart() t.expect( - await getChainAddresses(restarted, ~chainId=1337), + await getChainAddresses(restarted, ~chainId=1337->ChainId.fromInt), ~message="Config addresses should survive restart from DB", ).toEqual(expected) }, @@ -653,7 +653,7 @@ describe("E2E tests", () => { // The chain-scoped output landed in the per-chain cache table, not the // flat cross-chain one. t.expect(( - await indexerMock.queryEffectCache(chainScopedEffect, ~scope=Chain(1337)), + await indexerMock.queryEffectCache(chainScopedEffect, ~scope=Chain(1337->ChainId.fromInt)), await indexerMock.metric("envio_effect_cache"), )).toEqual(( [{"id": `"a"`, "output": %raw(`1337`)}], diff --git a/scenarios/test_codegen/test/EventBlockFilter_test.res b/scenarios/test_codegen/test/EventBlockFilter_test.res index 9389d98634..f2bdc93181 100644 --- a/scenarios/test_codegen/test/EventBlockFilter_test.res +++ b/scenarios/test_codegen/test/EventBlockFilter_test.res @@ -26,14 +26,14 @@ let parse = (~eventFilters: option, ~probeChainId, ~onEventBlockFilterSc {startBlock: p.resolvedWhere.startBlock, filterByAddresses: p.filterByAddresses} } -let parseEvm = (~eventFilters: option, ~probeChainId=1) => +let parseEvm = (~eventFilters: option, ~probeChainId=1->ChainId.fromInt) => parse( ~eventFilters, ~probeChainId, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ) -let parseFuel = (~eventFilters: option, ~probeChainId=1) => +let parseFuel = (~eventFilters: option, ~probeChainId=1->ChainId.fromInt) => parse( ~eventFilters, ~probeChainId, @@ -154,15 +154,15 @@ describe("parseWhereOrThrow — dynamic `where` callback (EVM)", () => { })`) let {startBlock: startBlockChain137} = parseEvm( ~eventFilters=Some(whereFn), - ~probeChainId=137, + ~probeChainId=137->ChainId.fromInt, ) - let {startBlock: startBlockChain1} = parseEvm(~eventFilters=Some(whereFn), ~probeChainId=1) + let {startBlock: startBlockChain1} = parseEvm(~eventFilters=Some(whereFn), ~probeChainId=1->ChainId.fromInt) t.expect((startBlockChain137, startBlockChain1)).toEqual((Some(5000), Some(1000))) }) it("returns None when the callback returns `false` for this chain", t => { let whereFn = %raw(`({chain}) => chain.id === 137 ? {block: {number: {_gte: 5000}}} : false`) - let {startBlock} = parseEvm(~eventFilters=Some(whereFn), ~probeChainId=1) + let {startBlock} = parseEvm(~eventFilters=Some(whereFn), ~probeChainId=1->ChainId.fromInt) t.expect(startBlock).toEqual(None) }) @@ -253,7 +253,7 @@ describe("EventConfigBuilder — where.block.number._gte overrides contract star ~handler=None, ~contractRegister=None, ~where=eventFilters, - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ~startBlock?, ) @@ -303,7 +303,7 @@ describe("EventConfigBuilder — where.block.number._gte overrides contract star ~handler=None, ~contractRegister=None, ~where=Some(whereFn), - ~chainId=137, + ~chainId=137->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ~startBlock=1, ) @@ -318,7 +318,7 @@ describe("EventConfigBuilder — where.block.number._gte overrides contract star ~handler=None, ~contractRegister=None, ~where=Some(whereFn), - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ~startBlock=1, ) @@ -352,7 +352,7 @@ describe("FetchState — where.block._gte drives the first query's fromBlock", ( ~handler=None, ~contractRegister=None, ~where=Some(%raw(`{block: {number: {_gte: 5000}}}`)), - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ~startBlock?, ) @@ -377,7 +377,7 @@ describe("FetchState — where.block._gte drives the first query's fromBlock", ( ~endBlock=None, ~maxAddrInPartition=3, ~maxOnBlockBufferSize=5000, - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=10000, ) } diff --git a/scenarios/test_codegen/test/EventFilters_test.res b/scenarios/test_codegen/test/EventFilters_test.res index 8113c7370f..6d5b2cc368 100644 --- a/scenarios/test_codegen/test/EventFilters_test.res +++ b/scenarios/test_codegen/test/EventFilters_test.res @@ -91,7 +91,7 @@ let complexTopicCases: array = [ let allTopicCases = Array.concat(scalarTopicCases, complexTopicCases) let getTopicSelection = eventName => { - let eventConfig = getEvmEventConfig(~contractName="TestEvents", ~eventName, ~chainId=1337) + let eventConfig = getEvmEventConfig(~contractName="TestEvents", ~eventName, ~chainId=1337->ChainId.fromInt) let clientRegistration = HyperSyncClient.Registration.fromOnEventRegistrations([eventConfig])->Array.getUnsafe(0) clientRegistration.topicSelections->Array.getUnsafe(0) @@ -324,7 +324,7 @@ describe("Test eventFilters", () => { let eventConfig = getEvmEventConfig( ~contractName="EventFiltersTest", ~eventName="Transfer", - ~chainId=137, + ~chainId=137->ChainId.fromInt, ) // The whitelisted addresses are checksummed (mixed-case) in the handler @@ -378,7 +378,7 @@ describe("Test eventFilters", () => { let eventConfig = getEvmEventConfig( ~contractName="EventFiltersTest", ~eventName="WildcardWithAddress", - ~chainId=137, + ~chainId=137->ChainId.fromInt, ) t.expect(eventConfig.resolvedWhere.topicSelections).toEqual([ @@ -453,7 +453,7 @@ describe("Test eventFilters", () => { let eventConfig = getEvmEventConfig( ~contractName="EventFiltersTest", ~eventName="EmptyFiltersArray", - ~chainId=137, + ~chainId=137->ChainId.fromInt, ) t.expect(eventConfig.resolvedWhere.topicSelections).toEqual([ @@ -477,7 +477,7 @@ describe("Test eventFilters", () => { let eventConfig = getEvmEventConfig( ~contractName="EventFiltersTest", ~eventName="EmptyFiltersArray", - ~chainId=137, + ~chainId=137->ChainId.fromInt, ) t.expect(eventConfig.handler->Option.isSome).toBe(true) }) @@ -500,7 +500,7 @@ describe("Test eventFilters", () => { ~config, ~contractName="EventFiltersTest", ~eventName="WithExcessField", - ~chainId=137, + ~chainId=137->ChainId.fromInt, ) t->toThrowErrorEqual(() => EventConfigBuilder.buildEvmOnEventRegistration( @@ -511,7 +511,7 @@ describe("Test eventFilters", () => { ~where=Some( %raw(`{params: {from: "0x0000000000000000000000000000000000000000", to: "0x0000000000000000000000000000000000000000"}}`), ), - ~chainId=137, + ~chainId=137->ChainId.fromInt, ~onEventBlockFilterSchema=config.ecosystem.onEventBlockFilterSchema, ) , `Invalid where configuration. The event doesn't have an indexed parameter "to" and can't use it for filtering`) @@ -521,12 +521,12 @@ describe("Test eventFilters", () => { let wildcardWithAddress = getEvmEventConfig( ~contractName="EventFiltersTest", ~eventName="WildcardWithAddress", - ~chainId=137, + ~chainId=137->ChainId.fromInt, ) let transfer = getEvmEventConfig( ~contractName="EventFiltersTest", ~eventName="Transfer", - ~chainId=137, + ~chainId=137->ChainId.fromInt, ) t.expect(( wildcardWithAddress.clientAddressFilter->Option.isSome, diff --git a/scenarios/test_codegen/test/HandlerRegisterLifecycle_test.res b/scenarios/test_codegen/test/HandlerRegisterLifecycle_test.res index 8890c0b2a3..0b12788c13 100644 --- a/scenarios/test_codegen/test/HandlerRegisterLifecycle_test.res +++ b/scenarios/test_codegen/test/HandlerRegisterLifecycle_test.res @@ -79,8 +79,8 @@ describe("HandlerRegister — onBlock validation at registration", () => { config.chainMap ->ChainMap.values ->Array.map(chainConfig => ( - chainConfig.id->Int.toString, - {"id": chainConfig.id}->(Utils.magic: {"id": int} => unknown), + chainConfig.id->ChainId.toString, + {"id": chainConfig.id}->(Utils.magic: {"id": ChainId.t} => unknown), )) ->Dict.fromArray diff --git a/scenarios/test_codegen/test/IndexerStateStall_test.res b/scenarios/test_codegen/test/IndexerStateStall_test.res index f5276511ec..7f940b88ef 100644 --- a/scenarios/test_codegen/test/IndexerStateStall_test.res +++ b/scenarios/test_codegen/test/IndexerStateStall_test.res @@ -25,7 +25,7 @@ describe("IndexerState fetch stall accounting", () => { state->IndexerState.markProcessingStalledOnFetch await Time.resolvePromiseAfterDelay(~delayMilliseconds=50) state->IndexerState.beginReorg( - ~chain=ChainMap.Chain.makeUnsafe(~chainId=1), + ~chain=ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), ~blockNumber=100, ) // Settled, not discarded: the wait before the reorg still has to land in diff --git a/scenarios/test_codegen/test/IndexerState_test.res b/scenarios/test_codegen/test/IndexerState_test.res index 7330ffd858..19b2659d78 100644 --- a/scenarios/test_codegen/test/IndexerState_test.res +++ b/scenarios/test_codegen/test/IndexerState_test.res @@ -50,7 +50,7 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) ~addresses, ~startBlock=0, ~maxOnBlockBufferSize=5000, - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=0, ) @@ -82,7 +82,7 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) onEventRegistration: {"index": 0}->( Utils.magic: {"index": int} => Internal.onEventRegistration ), - payload: `mock event (chainId)${id->Int.toString} - (blockNumber)${currentBlockNumber.contents->Int.toString} - (logIndex)${logIndex->Int.toString} - (timestamp)${currentTime.contents->Int.toString}`->( + payload: `mock event (chainId)${id->ChainId.toString} - (blockNumber)${currentBlockNumber.contents->Int.toString} - (logIndex)${logIndex->Int.toString} - (timestamp)${currentTime.contents->Int.toString}`->( Utils.magic: string => Internal.eventPayload ), }) @@ -140,7 +140,7 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) ~logger=Logging.getLogger(), ) - chainStates->Utils.Dict.setByInt(id, mockChainState) + chainStates->ChainId.Dict.set(id, mockChainState) }) let state = IndexerState.make( @@ -331,7 +331,7 @@ describe("IndexerState", () => { ~committedProgressBlockNumber=-1, ~logger=Logging.getLogger(), ) - chainStates->Utils.Dict.setByInt(chainConfig.id, chainState) + chainStates->ChainId.Dict.set(chainConfig.id, chainState) }, ) IndexerState.make( @@ -396,7 +396,7 @@ describe("IndexerState", () => { let resultCs = state->IndexerState.getChainState(~chain) let progressed = batch.progressedChainsById - ->Utils.Dict.dangerouslyGetByIntNonOption(chainId) + ->ChainId.Dict.dangerouslyGetNonOption(chainId) ->Option.getUnsafe t.expect( diff --git a/scenarios/test_codegen/test/LoadLayer_test.res b/scenarios/test_codegen/test/LoadLayer_test.res index f54977f86c..25b908e699 100644 --- a/scenarios/test_codegen/test/LoadLayer_test.res +++ b/scenarios/test_codegen/test/LoadLayer_test.res @@ -858,11 +858,11 @@ describe("LoadLayer effect scope isolation", () => { ) // Two concurrent calls, same input, same chain -> handler runs once. - let chain1 = await Promise.all([call(~scope=Chain(1), ~input="a"), call(~scope=Chain(1), ~input="a")]) + let chain1 = await Promise.all([call(~scope=Chain(1->ChainId.fromInt), ~input="a"), call(~scope=Chain(1->ChainId.fromInt), ~input="a")]) // Same input on a different chain -> handler runs again (isolated cache). - let chain2 = await call(~scope=Chain(2), ~input="a") + let chain2 = await call(~scope=Chain(2->ChainId.fromInt), ~input="a") // Repeat on chain 1 -> served from the warm in-memory cache, no new run. - let chain1Again = await call(~scope=Chain(1), ~input="a") + let chain1Again = await call(~scope=Chain(1->ChainId.fromInt), ~input="a") t.expect((callCount.contents, chain1, chain2, chain1Again)).toEqual(( 2, @@ -948,9 +948,9 @@ describe("LoadLayer effect scope isolation", () => { // chain 1 exhausts its single-call window with "a", queuing "b" until the // window resets. chain 2 has its own independent window, so "a" resolves // right away instead of waiting behind chain 1. - let a1 = track(call(~scope=Chain(1), ~input="a"), "chain1-a") - let b1 = track(call(~scope=Chain(1), ~input="b"), "chain1-b") - let a2 = track(call(~scope=Chain(2), ~input="a"), "chain2-a") + let a1 = track(call(~scope=Chain(1->ChainId.fromInt), ~input="a"), "chain1-a") + let b1 = track(call(~scope=Chain(1->ChainId.fromInt), ~input="b"), "chain1-b") + let a2 = track(call(~scope=Chain(2->ChainId.fromInt), ~input="a"), "chain2-a") let _ = await Promise.all([a1, b1, a2]) @@ -994,7 +994,7 @@ describe("LoadLayer effect scope isolation", () => { ) // Consume chain 1's single-call-per-window budget. - let _ = await call(~scope=Chain(1), ~input="a") + let _ = await call(~scope=Chain(1->ChainId.fromInt), ~input="a") // A reorg wipes the effect in-mem tables (IndexerState.beginRollbackDiff). indexerState->IndexerState.beginRollbackDiff( @@ -1005,7 +1005,7 @@ describe("LoadLayer effect scope isolation", () => { // The window hasn't elapsed, so the budget must still be spent: the next // call is queued (not run) rather than getting a fresh budget from the reset. - let pending = call(~scope=Chain(1), ~input="b") + let pending = call(~scope=Chain(1->ChainId.fromInt), ~input="b") await Utils.delay(0) await Utils.delay(0) let countWhileQueued = callCount.contents diff --git a/scenarios/test_codegen/test/RpcSourceContract_test.res b/scenarios/test_codegen/test/RpcSourceContract_test.res index 6b09cd97ab..8be03fe148 100644 --- a/scenarios/test_codegen/test/RpcSourceContract_test.res +++ b/scenarios/test_codegen/test/RpcSourceContract_test.res @@ -2,7 +2,7 @@ open Vitest type sourceFactory = RpcSource.options => Source.t -let chain = ChainMap.Chain.makeUnsafe(~chainId=1) +let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) let sighash = "0xcf16a92280c1bbb43f72d31126b724d508df2877835849e8744017ab36a9b47f" let transactionHash = "0x27e26f21f744064a4af53810d8002bbd7208a2ca4865503a99b9c529e5cff5ea" let contractAddress = "0x00000000000000000000000000000000000000AA" diff --git a/scenarios/test_codegen/test/RpcSource_test.res b/scenarios/test_codegen/test/RpcSource_test.res index 61d35af7cf..422f39853c 100644 --- a/scenarios/test_codegen/test/RpcSource_test.res +++ b/scenarios/test_codegen/test/RpcSource_test.res @@ -727,7 +727,7 @@ describe("RpcSource - fieldRegistry completeness", () => { }) }) -let chain = ChainMap.Chain.makeUnsafe(~chainId=1) +let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) describe("RpcSource - empty selection", () => { Async.it("Throws UnsupportedSelection when the selection has no event configs", async t => { let source = RpcSource.make({ diff --git a/scenarios/test_codegen/test/SourceBlockHashes_test.res b/scenarios/test_codegen/test/SourceBlockHashes_test.res index c0835ea4dc..605e41234e 100644 --- a/scenarios/test_codegen/test/SourceBlockHashes_test.res +++ b/scenarios/test_codegen/test/SourceBlockHashes_test.res @@ -6,7 +6,7 @@ let testApiToken = ) // Ethereum mainnet. -let chain = ChainMap.Chain.makeUnsafe(~chainId=1) +let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) // Uniswap V2 Factory's PairCreated event (topic0 = keccak("PairCreated(address,address,address,uint256)")) // 2 indexed args (token0, token1) ⇒ topicCount = 3. diff --git a/scenarios/test_codegen/test/__mocks__/MockConfig.res b/scenarios/test_codegen/test/__mocks__/MockConfig.res index 838c37aceb..1c4d1a6cd3 100644 --- a/scenarios/test_codegen/test/__mocks__/MockConfig.res +++ b/scenarios/test_codegen/test/__mocks__/MockConfig.res @@ -1,6 +1,6 @@ -let chain1 = ChainMap.Chain.makeUnsafe(~chainId=1) -let chain137 = ChainMap.Chain.makeUnsafe(~chainId=137) -let chain1337 = ChainMap.Chain.makeUnsafe(~chainId=1337) +let chain1 = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) +let chain137 = ChainMap.Chain.makeUnsafe(~chainId=137->ChainId.fromInt) +let chain1337 = ChainMap.Chain.makeUnsafe(~chainId=1337->ChainId.fromInt) let getEventConfig = (~config=?, ~contractName, ~eventName, ~chainId=?) => { let config = switch config { @@ -29,7 +29,11 @@ let getOnEventRegistration = (~config=?, ~contractName, ~eventName, ~chainId=?) let eventConfig = getEventConfig(~config, ~contractName, ~eventName, ~chainId?) let probeChainId = switch chainId { | Some(id) => id - | None => config.chainMap->ChainMap.values->Array.get(0)->Option.mapOr(0, c => c.id) + | None => + config.chainMap + ->ChainMap.values + ->Array.get(0) + ->Option.mapOr(0->ChainId.fromInt, c => c.id) } HandlerRegister.getSimulateOnEventRegistrations(~config, ~chainId=probeChainId, ~eventConfig) ->Array.get(0) diff --git a/scenarios/test_codegen/test/__mocks__/MockEvents.res b/scenarios/test_codegen/test/__mocks__/MockEvents.res index 134a0a5c59..8511985c7d 100644 --- a/scenarios/test_codegen/test/__mocks__/MockEvents.res +++ b/scenarios/test_codegen/test/__mocks__/MockEvents.res @@ -72,7 +72,7 @@ let newGravatarLog1: Internal.genericEvent< contractName: "Gravatar", eventName: "NewGravatar", params: newGravatar1, - chainId: 54321, + chainId: 54321->ChainId.fromInt, // TODO: this should be an address type srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, logIndex: 11, @@ -89,7 +89,7 @@ let newGravatarLog2: Internal.genericEvent< eventName: "NewGravatar", params: newGravatar2, block: block1, - chainId: 54321, + chainId: 54321->ChainId.fromInt, srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, transaction: tx1, logIndex: 12, @@ -103,7 +103,7 @@ let newGravatarLog3: Internal.genericEvent< contractName: "Gravatar", eventName: "NewGravatar", params: newGravatar3, - chainId: 54321, + chainId: 54321->ChainId.fromInt, srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, logIndex: 13, transaction: tx1, @@ -118,7 +118,7 @@ let newGravatarLog4: Internal.genericEvent< contractName: "Gravatar", eventName: "NewGravatar", params: newGravatar4_deleted, - chainId: 54321, + chainId: 54321->ChainId.fromInt, srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, logIndex: 13, transaction: tx1, @@ -133,7 +133,7 @@ let setGravatarLog1: Internal.genericEvent< contractName: "Gravatar", eventName: "UpdatedGravatar", params: setGravatar1, - chainId: 54321, + chainId: 54321->ChainId.fromInt, srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, logIndex: 14, transaction: tx1, @@ -148,7 +148,7 @@ let setGravatarLog2: Internal.genericEvent< contractName: "Gravatar", eventName: "UpdatedGravatar", params: setGravatar2, - chainId: 54321, + chainId: 54321->ChainId.fromInt, srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, logIndex: 15, transaction: tx1, @@ -163,7 +163,7 @@ let setGravatarLog3: Internal.genericEvent< contractName: "Gravatar", eventName: "UpdatedGravatar", params: setGravatar3, - chainId: 54321, + chainId: 54321->ChainId.fromInt, srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, logIndex: 16, transaction: tx1, @@ -177,7 +177,7 @@ let setGravatarLog4: Internal.genericEvent< contractName: "Gravatar", eventName: "UpdatedGravatar", params: setGravatar4, - chainId: 54321, + chainId: 54321->ChainId.fromInt, srcAddress: "0xabc0000000000000000000000000000000000000"->Address.Evm.fromStringOrThrow, logIndex: 17, transaction: tx1, diff --git a/scenarios/test_codegen/test/helpers/MockIndexer.res b/scenarios/test_codegen/test/helpers/MockIndexer.res index e02247d2e5..79c418bd75 100644 --- a/scenarios/test_codegen/test/helpers/MockIndexer.res +++ b/scenarios/test_codegen/test/helpers/MockIndexer.res @@ -348,7 +348,7 @@ let installMockSourceRegistrations = ( | _ => [] } if !(sourceStates->Utils.Array.isEmpty) { - let key = chainConfig.id->Int.toString + let key = chainConfig.id->ChainId.toString let registrations = switch registrationsByChainId->Utils.Dict.dangerouslyGetNonOption(key) { | Some(registrations) => registrations | None => @@ -450,7 +450,7 @@ module Indexer = { let chainMap = chains ->Array.map(chainConfig => { - let chain = ChainMap.Chain.makeUnsafe(~chainId=(chainConfig.chain :> int)) + let chain = ChainMap.Chain.makeUnsafe(~chainId=(chainConfig.chain :> int)->ChainId.fromInt) let originalChainConfig = baseConfig.chainMap->ChainMap.get(chain) ( chain, @@ -833,7 +833,7 @@ module Source = { } } - let chain = ChainMap.Chain.makeUnsafe(~chainId=(chain :> int)) + let chain = ChainMap.Chain.makeUnsafe(~chainId=(chain :> int)->ChainId.fromInt) let getHeightOrThrowCalls = [] let getHeightOrThrowResolveFns = [] let getHeightOrThrowRejectFns = [] @@ -1129,7 +1129,7 @@ module Helper = { } let mockRawEventRow: InternalTable.RawEvents.t = { - chain_id: 1, + chain_id: 1->ChainId.fromInt, event_id: 1234567890n, contract_name: "NftFactory", event_name: "SimpleNftCreated", diff --git a/scenarios/test_codegen/test/helpers/RpcSourcePins.res b/scenarios/test_codegen/test/helpers/RpcSourcePins.res index 0a7dc0c7f8..bb87533f61 100644 --- a/scenarios/test_codegen/test/helpers/RpcSourcePins.res +++ b/scenarios/test_codegen/test/helpers/RpcSourcePins.res @@ -4,7 +4,7 @@ type pinnedEvent = { registrationId: string, - chainId: int, + chainId: ChainId.t, blockNumber: int, logIndex: int, transactionIndex: int, diff --git a/scenarios/test_codegen/test/lib_tests/ChainState_test.res b/scenarios/test_codegen/test/lib_tests/ChainState_test.res index 855183bcf7..275d2d44eb 100644 --- a/scenarios/test_codegen/test/lib_tests/ChainState_test.res +++ b/scenarios/test_codegen/test/lib_tests/ChainState_test.res @@ -1,6 +1,6 @@ open Vitest -let chainId = 1 +let chainId = 1->ChainId.fromInt let baseChainConfig = {...Config.load().chainMap->ChainMap.values->Utils.Array.firstUnsafe, id: chainId} // A registrations map with an onBlock config (no address partition) so @@ -8,7 +8,7 @@ let baseChainConfig = {...Config.load().chainMap->ChainMap.values->Utils.Array.f let registrationsByChainId: HandlerRegister.registrationsByChainId = { let d = Dict.make() d->Dict.set( - chainId->Int.toString, + chainId->ChainId.toString, ({ onEventRegistrations: [], onBlockRegistrations: [ @@ -120,7 +120,7 @@ describe("ChainState chain density EMA (per batch)", () => { items: [], progressedChainsById: { let d = Dict.make() - d->Utils.Dict.setByInt( + d->ChainId.Dict.set( chainId, ({ batchSize: 0, diff --git a/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res b/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res index 0cb78a5fb8..67cd266216 100644 --- a/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res +++ b/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res @@ -4,7 +4,7 @@ let baseChainConfig = Config.load().chainMap->ChainMap.values->Utils.Array.first let mockEvent = (~blockNumber): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId=1), + chain: ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), blockNumber, // Carries an `index` so the buffer's dedup key resolves; the rest of the // registration is unused by these tests. @@ -172,7 +172,7 @@ let emptyBatch: Batch.t = { let makeCrossChainState = (~chainStatesList, ~isRealtime=false, ~targetBufferSize=100) => { let chainStates = Dict.make() chainStatesList->Array.forEach(cs => - chainStates->Utils.Dict.setByInt((cs->ChainState.chainConfig).id, cs) + chainStates->ChainId.Dict.set((cs->ChainState.chainConfig).id, cs) ) CrossChainState.make(~chainStates, ~isInReorgThreshold=false, ~isRealtime, ~targetBufferSize) } @@ -187,7 +187,7 @@ describe("ChainState event registration ownership", () => { it("rejects a registration whose index differs from its ChainState position", t => { t->toThrowErrorEqual(() => makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=10, ~frontier=10, ~firstEventBlock=0, @@ -201,14 +201,16 @@ describe("ChainState event registration ownership", () => { describe("CrossChainState fetch control", () => { it("priorityOrder visits the furthest-behind chain first", t => { - let a = makeChainState(~chainId=1, ~knownHeight=1000, ~frontier=100, ~firstEventBlock=0, ~bufferBlocks=[100]) - let b = makeChainState(~chainId=2, ~knownHeight=1000, ~frontier=500, ~firstEventBlock=0, ~bufferBlocks=[500]) - let cHead = makeChainState(~chainId=3, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0, ~bufferBlocks=[950]) + let a = makeChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=100, ~firstEventBlock=0, ~bufferBlocks=[100]) + let b = makeChainState(~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~frontier=500, ~firstEventBlock=0, ~bufferBlocks=[500]) + let cHead = makeChainState(~chainId=3->ChainId.fromInt, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0, ~bufferBlocks=[950]) let cm = makeCrossChainState(~chainStatesList=[cHead, a, b]) t.expect( - cm->CrossChainState.priorityOrder->Array.map(cs => (cs->ChainState.chainConfig).id), + cm + ->CrossChainState.priorityOrder + ->Array.map(cs => (cs->ChainState.chainConfig).id->ChainId.toInt), ).toEqual([1, 2, 3]) }) @@ -219,16 +221,18 @@ describe("CrossChainState fetch control", () => { // put the genuinely-behind chain 2 first, so it draws budget and anchors // the line before the ahead-but-eventless chain 1. let ahead = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=900, ~firstEventBlock=None, ) - let behind = makeFetchingChainState(~chainId=2, ~knownHeight=1000, ~latestFetchedBlock=300) + let behind = makeFetchingChainState(~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=300) let cm = makeCrossChainState(~chainStatesList=[ahead, behind]) t.expect( - cm->CrossChainState.priorityOrder->Array.map(cs => (cs->ChainState.chainConfig).id), + cm + ->CrossChainState.priorityOrder + ->Array.map(cs => (cs->ChainState.chainConfig).id->ChainId.toInt), ).toEqual([2, 1]) }) @@ -237,14 +241,14 @@ describe("CrossChainState fetch control", () => { // block, so they're dispatched with that action. A chain whose buffer is // already full of ready items (>= targetBufferSize) gets no budget, so it // isn't dispatched. - let a = makeChainState(~chainId=1, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0) - let b = makeChainState(~chainId=2, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0) + let a = makeChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0) + let b = makeChainState(~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0) let cm = makeCrossChainState(~chainStatesList=[a, b], ~isRealtime=true) let dispatched = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatched->Array.push((chain->ChainMap.Chain.toChainId, action))->ignore + dispatched->Array.push((chain->ChainMap.Chain.toChainId->ChainId.toInt, action))->ignore Promise.resolve() }) @@ -257,14 +261,14 @@ describe("CrossChainState fetch control", () => { // Both chains are backfilling with onBlock-only frontiers, so they have no // partitions to fetch; the pool being full leaves nothing to do. let a = makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=100, ~firstEventBlock=0, ~bufferBlocks=Array.make(~length=60, 100), ) let b = makeChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~frontier=100, ~firstEventBlock=0, @@ -274,7 +278,7 @@ describe("CrossChainState fetch control", () => { let dispatched = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action as _) => { - dispatched->Array.push(chain->ChainMap.Chain.toChainId)->ignore + dispatched->Array.push(chain->ChainMap.Chain.toChainId->ChainId.toInt)->ignore Promise.resolve() }) @@ -285,14 +289,14 @@ describe("CrossChainState fetch control", () => { // Fresh partition behind the head: its query estimates at the default // (10000), far above the tiny remaining budget (1). Admission must still let // one query through, otherwise the chain would never make progress. - let cs = makeFetchingChainState(~chainId=1, ~knownHeight=1000, ~latestFetchedBlock=0) + let cs = makeFetchingChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=0) let cm = makeCrossChainState(~chainStatesList=[cs], ~targetBufferSize=1) let dispatched = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { dispatched ->Array.push(( - chain->ChainMap.Chain.toChainId, + chain->ChainMap.Chain.toChainId->ChainId.toInt, switch action { | Ready(queries) => queries->Array.length | _ => 0 @@ -308,13 +312,13 @@ describe("CrossChainState fetch control", () => { let queryWithFreeBudget = async (~freeBudget) => { let targetBufferSize = 100 let buffered = makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=900, ~firstEventBlock=0, ~bufferBlocks=Array.make(~length=targetBufferSize - freeBudget, 900), ) - let fetching = makeFetchingChainState(~chainId=2, ~knownHeight=1000, ~latestFetchedBlock=0) + let fetching = makeFetchingChainState(~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=0) let cm = makeCrossChainState(~chainStatesList=[buffered, fetching], ~targetBufferSize) let admitted = [] @@ -323,7 +327,7 @@ describe("CrossChainState fetch control", () => { | Ready(queries) => admitted ->Array.push(( - chain->ChainMap.Chain.toChainId, + chain->ChainMap.Chain.toChainId->ChainId.toInt, queries->Array.reduce(0, (sum, query: FetchState.query) => sum + query.itemsEst), )) ->ignore @@ -344,14 +348,14 @@ describe("CrossChainState fetch control", () => { Async.it("starts no polls below the admission floor, except for height discovery", async t => { let atHead = makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0, ~bufferBlocks=Array.make(~length=91, 1000), ) - let behind = makeFetchingChainState(~chainId=2, ~knownHeight=1000, ~latestFetchedBlock=0) - let waitingForHeight = makeFetchingChainState(~chainId=3, ~knownHeight=0, ~latestFetchedBlock=0) + let behind = makeFetchingChainState(~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=0) + let waitingForHeight = makeFetchingChainState(~chainId=3->ChainId.fromInt, ~knownHeight=0, ~latestFetchedBlock=0) let cm = makeCrossChainState( ~chainStatesList=[atHead, behind, waitingForHeight], ~targetBufferSize=100, @@ -359,7 +363,7 @@ describe("CrossChainState fetch control", () => { let dispatched = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatched->Array.push((chain->ChainMap.Chain.toChainId, action))->ignore + dispatched->Array.push((chain->ChainMap.Chain.toChainId->ChainId.toInt, action))->ignore Promise.resolve() }) @@ -370,10 +374,10 @@ describe("CrossChainState fetch control", () => { }) Async.it("waits below the admission unit and retries after a response releases budget", async t => { - let first = makeFetchingChainState(~chainId=1, ~knownHeight=1000, ~latestFetchedBlock=0) - let second = makeFetchingChainState(~chainId=2, ~knownHeight=1000, ~latestFetchedBlock=500) + let first = makeFetchingChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=0) + let second = makeFetchingChainState(~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=500) let buffered = makeChainState( - ~chainId=3, + ~chainId=3->ChainId.fromInt, ~knownHeight=1000, ~frontier=900, ~firstEventBlock=0, @@ -385,7 +389,7 @@ describe("CrossChainState fetch control", () => { await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { switch action { | Ready(queries) => - firstTickQueries->Array.push((chain->ChainMap.Chain.toChainId, queries))->ignore + firstTickQueries->Array.push((chain->ChainMap.Chain.toChainId->ChainId.toInt, queries))->ignore | _ => () } Promise.resolve() @@ -408,7 +412,7 @@ describe("CrossChainState fetch control", () => { let secondTickChains = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { switch action { - | Ready(_) => secondTickChains->Array.push(chain->ChainMap.Chain.toChainId)->ignore + | Ready(_) => secondTickChains->Array.push(chain->ChainMap.Chain.toChainId->ChainId.toInt)->ignore | _ => () } Promise.resolve() @@ -468,7 +472,7 @@ describe("CrossChainState fetch control", () => { normalSelection, latestOnBlockBlockNumber: 0, maxOnBlockBufferSize: 10000, - chainId: 1, + chainId: 1->ChainId.fromInt, contractConfigs: Dict.make(), blockLag: 0, onBlockRegistrations: [], @@ -478,7 +482,7 @@ describe("CrossChainState fetch control", () => { } let mockSource1 = MockIndexer.Source.make([], ~chain=#1) let a = ChainState.make( - ~chainConfig={...baseChainConfig, id: 1}, + ~chainConfig={...baseChainConfig, id: 1->ChainId.fromInt}, ~fetchState=fetchState1, ~indexingAddresses=indexingAddresses1, ~sourceManager=SourceManager.make(~sources=[mockSource1.source], ~isRealtime=false), @@ -495,7 +499,7 @@ describe("CrossChainState fetch control", () => { // sizes exactly to whatever budget it's given, so it directly reflects // what chain 1 left behind. let b = makeFetchingChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=500, ~chainDensity=Some(10.), @@ -505,7 +509,7 @@ describe("CrossChainState fetch control", () => { let dispatchedItemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatchedItemsByChain->Utils.Dict.setByInt( + dispatchedItemsByChain->ChainId.Dict.set( chain->ChainMap.Chain.toChainId, switch action { | Ready(queries) => @@ -517,8 +521,8 @@ describe("CrossChainState fetch control", () => { }) ( - dispatchedItemsByChain->Utils.Dict.dangerouslyGetByIntNonOption(1), - dispatchedItemsByChain->Utils.Dict.dangerouslyGetByIntNonOption(2), + dispatchedItemsByChain->ChainId.Dict.dangerouslyGetNonOption(1->ChainId.fromInt), + dispatchedItemsByChain->ChainId.Dict.dangerouslyGetNonOption(2->ChainId.fromInt), a->ChainState.pendingBudget, b->ChainState.pendingBudget, ) @@ -548,13 +552,13 @@ describe("CrossChainState fetch control", () => { // for a new block instead of setting the alignment line from a // degenerate progress range and letting every other chain run // unconstrained on a stale line. - let a = makeFetchingChainState(~chainId=1, ~knownHeight=0, ~latestFetchedBlock=0) - let b = makeFetchingChainState(~chainId=2, ~knownHeight=1000, ~latestFetchedBlock=500) + let a = makeFetchingChainState(~chainId=1->ChainId.fromInt, ~knownHeight=0, ~latestFetchedBlock=0) + let b = makeFetchingChainState(~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=500) let cm = makeCrossChainState(~chainStatesList=[a, b], ~targetBufferSize=3000) let actionsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - actionsByChain->Utils.Dict.setByInt( + actionsByChain->ChainId.Dict.set( chain->ChainMap.Chain.toChainId, switch action { | WaitingForNewBlock => "waitingForNewBlock" @@ -584,14 +588,14 @@ describe("CrossChainState fetch control", () => { // not the raw endBlock (500/1e9 ≈ 0%) — the latter would clamp the // follower below its own frontier and stall it. let anchor = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=500, ~endBlock=Some(1_000_000_000), ~chainDensity=Some(1.), ) let follower = makeFetchingChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=520, ~chainDensity=Some(1.), @@ -603,7 +607,7 @@ describe("CrossChainState fetch control", () => { let estimatesByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - estimatesByChain->Utils.Dict.setByInt( + estimatesByChain->ChainId.Dict.set( chain->ChainMap.Chain.toChainId, switch action { | Ready(queries) => @@ -624,7 +628,7 @@ describe("CrossChainState fetch control", () => { it("getNextQuery caps the budget at the plain range cost regardless of caught-up status", t => { let makeChain = (~caughtUpOnce) => makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=0, ~endBlock=Some(20), @@ -650,14 +654,14 @@ describe("CrossChainState readiness", () => { // Chain 1 reached head with an empty buffer; chain 2 is mid-backfill with // ready events left to process. let atHead = makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0, ~isProgressAtHead=true, ) let backfilling = makeChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~frontier=300, ~firstEventBlock=0, @@ -676,14 +680,14 @@ describe("CrossChainState readiness", () => { it("marks every chain ready together once the whole indexer is caught up", t => { let a = makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0, ~isProgressAtHead=true, ) let b = makeChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~frontier=1000, ~firstEventBlock=0, @@ -703,13 +707,13 @@ describe("CrossChainState readiness", () => { describe("ChainState cold start", () => { it("targets frontier + 20k with no density signal", t => { - let cs = makeFetchingChainState(~chainId=1, ~knownHeight=1_000_000, ~latestFetchedBlock=5_000) + let cs = makeFetchingChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=5_000) t.expect(cs->ChainState.targetBlock(~chainTargetItems=1000.)).toBe(25_000) }) it("caps the cold target at an endBlock inside the horizon", t => { let cs = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=0, ~endBlock=Some(5_000), @@ -721,9 +725,9 @@ describe("ChainState cold start", () => { // Chain 1 is cold and most behind. Its target is a guess, but its frontier // is a real measurement — chain 2 must not run ahead of it just because // chain 1 hasn't produced a density signal yet. - let a = makeFetchingChainState(~chainId=1, ~knownHeight=1_000_000, ~latestFetchedBlock=0) + let a = makeFetchingChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=0) let b = makeFetchingChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=500, ~chainDensity=Some(10.), @@ -732,7 +736,7 @@ describe("ChainState cold start", () => { let dispatchedItemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatchedItemsByChain->Utils.Dict.setByInt( + dispatchedItemsByChain->ChainId.Dict.set( chain->ChainMap.Chain.toChainId, switch action { | Ready(queries) => @@ -755,14 +759,14 @@ describe("ChainState cold start", () => { // anchoring, such a tick left the line unset and chain 2 ran unclamped to // its head; now chain 2 stays held at chain 1's frontier (+10% margin). let a = makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=100, ~firstEventBlock=0, ~bufferBlocks=[100], ) let b = makeFetchingChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=500, ~chainDensity=Some(10.), @@ -771,7 +775,7 @@ describe("ChainState cold start", () => { let actionsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - actionsByChain->Utils.Dict.setByInt( + actionsByChain->ChainId.Dict.set( chain->ChainMap.Chain.toChainId, switch action { | WaitingForNewBlock => "waitingForNewBlock" @@ -796,14 +800,14 @@ describe("ChainState cold start", () => { // Same shape as the anchoring test above, but the indexer is realtime: // chain 2 must be free to fetch to its head regardless of chain 1. let a = makeChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~frontier=100, ~firstEventBlock=0, ~bufferBlocks=[100], ) let b = makeFetchingChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=500, ~chainDensity=Some(10.), @@ -816,7 +820,7 @@ describe("ChainState cold start", () => { let dispatchedItemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatchedItemsByChain->Utils.Dict.setByInt( + dispatchedItemsByChain->ChainId.Dict.set( chain->ChainMap.Chain.toChainId, switch action { | Ready(queries) => @@ -835,7 +839,7 @@ describe("ChainState cold start", () => { Async.it("gives a cold chain one 10% admission unit", async t => { let probeSize = async (~targetBufferSize) => { - let cs = makeFetchingChainState(~chainId=1, ~knownHeight=1_000_000, ~latestFetchedBlock=0) + let cs = makeFetchingChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=0) let cm = makeCrossChainState(~chainStatesList=[cs], ~targetBufferSize) let dispatched = ref(0.) await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain as _, ~action) => { @@ -862,7 +866,7 @@ describe("ChainState cold start", () => { // A chain fetched to 800 is fully caught up and must read 1.0, not 0.8, so // it never looks behind against blocks it can't fetch yet. let cs = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=800, ~blockLag=200, @@ -878,20 +882,20 @@ describe("ChainState cold start", () => { // so chain 3 (just ahead of chain 2) is held near chain 2's line instead of // racing to head on chain 1's non-clamping frontier. let scanning = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=900, ~chainDensity=Some(10.), ~firstEventBlock=None, ) let behind = makeFetchingChainState( - ~chainId=2, + ~chainId=2->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=300, ~chainDensity=Some(10.), ) let slightlyAhead = makeFetchingChainState( - ~chainId=3, + ~chainId=3->ChainId.fromInt, ~knownHeight=1000, ~latestFetchedBlock=310, ~chainDensity=Some(10.), @@ -903,7 +907,7 @@ describe("ChainState cold start", () => { let itemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - itemsByChain->Utils.Dict.setByInt( + itemsByChain->ChainId.Dict.set( chain->ChainMap.Chain.toChainId, switch action { | Ready(queries) => queries->Array.reduce(0, (acc, q: FetchState.query) => acc + q.itemsEst) @@ -913,7 +917,8 @@ describe("ChainState cold start", () => { Promise.resolve() }) - let items = chainId => itemsByChain->Utils.Dict.dangerouslyGetByIntNonOption(chainId)->Option.getOr(0) + let items = chainId => + itemsByChain->ChainId.Dict.dangerouslyGetNonOption(chainId->ChainId.fromInt)->Option.getOr(0) // Structural, not exact: chain 1 (scanning, firstEventBlock=None) must set // no line and idle; chain 2 (lowest frontier progress) anchors and fetches // freely; chain 3 stays clamped near chain 2's line — far below what it @@ -938,7 +943,7 @@ describe("ChainState density from the ready buffer", () => { // 100 ready items over the 101-block span (-1 committed progress -> // frontier 100) prove ~1 item/block even though the EMA says 0.001. let cs = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=100, ~chainDensity=Some(0.001), @@ -949,7 +954,7 @@ describe("ChainState density from the ready buffer", () => { it("falls back to the processing EMA when the buffer is empty", t => { let cs = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=100, ~chainDensity=Some(0.5), @@ -963,14 +968,14 @@ describe("ChainState density from the ready buffer", () => { // the 100 blocks since the batch's progress — not the 201 since the still // uncommitted progress (-1). let cs = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=200, ~bufferBlocks=[150, 160], ) let progressedChainsById = Dict.make() - progressedChainsById->Utils.Dict.setByInt( - 1, + progressedChainsById->ChainId.Dict.set( + 1->ChainId.fromInt, ( { batchSize: 5, @@ -991,7 +996,7 @@ describe("ChainState density from the ready buffer", () => { it("ready items alone take the chain out of cold mode before the first batch commits", t => { let cs = makeFetchingChainState( - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=100, ~bufferBlocks=[50], diff --git a/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res b/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res index 131de5f4db..1b4a7a5f43 100644 --- a/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res +++ b/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res @@ -35,13 +35,13 @@ describe("Dynamic contracts startup size", () => { let sql = PgStorage.makeClient() let pgSchema = Env.Db.publicSchema - let chainId = 1337 + let chainId = 1337->ChainId.fromInt let rowCount = 120 let contractNameLength = 5_000_000 let _ = await sql->Postgres.unsafe( `INSERT INTO "${pgSchema}"."${Config.EnvioAddresses.name}" ("id", "chain_id", "registration_block", "registration_log_index", "contract_name") -SELECT '${chainId->Int.toString}-0x' || lpad(to_hex(g), 40, '0'), ${chainId->Int.toString}, 0, -1, repeat('x', ${contractNameLength->Int.toString}) +SELECT '${chainId->ChainId.toString}-0x' || lpad(to_hex(g), 40, '0'), ${chainId->ChainId.toString}, 0, -1, repeat('x', ${contractNameLength->Int.toString}) FROM generate_series(1, ${rowCount->Int.toString}) AS g;`, ) diff --git a/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res b/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res index b8ff066eb5..2c5c66fba0 100644 --- a/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res +++ b/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res @@ -348,7 +348,7 @@ describe("Test indexer reports deleted ids with the entity's id type", () => { }, ], ~checkpointIds=[1n], - ~checkpointChainIds=[1337], + ~checkpointChainIds=[1337->ChainId.fromInt], ~checkpointBlockNumbers=[5], ~checkpointEventsProcessed=[1], ) diff --git a/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res b/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res index 98b0634575..43575f41d1 100644 --- a/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res +++ b/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res @@ -1,6 +1,6 @@ open Vitest -let chainId = 0 +let chainId = 0->ChainId.fromInt // Spread into query literals so the common fields don't have to be repeated; // every other field is overridden at the call site. diff --git a/scenarios/test_codegen/test/lib_tests/FetchState_test.res b/scenarios/test_codegen/test/lib_tests/FetchState_test.res index 688bc1a46f..2a4280f9fc 100644 --- a/scenarios/test_codegen/test/lib_tests/FetchState_test.res +++ b/scenarios/test_codegen/test/lib_tests/FetchState_test.res @@ -1,6 +1,6 @@ open Vitest -let chainId = 0 +let chainId = 0->ChainId.fromInt let targetBufferSize = 5000 let knownHeight = 0 @@ -74,7 +74,7 @@ let makeConfigContract = (contractName, address): Internal.indexingAddress => { } } -let mockEvent = (~blockNumber, ~logIndex=0, ~chainId=1, ~registrationIndex=0): Internal.item => +let mockEvent = (~blockNumber, ~logIndex=0, ~chainId=1->ChainId.fromInt, ~registrationIndex=0): Internal.item => Internal.Event({ chain: ChainMap.Chain.makeUnsafe(~chainId), blockNumber, @@ -254,7 +254,7 @@ describe("FetchState.make", () => { maxOnBlockBufferSize: 5000, buffer: [], normalSelection: fetchState.normalSelection, - chainId: 0, + chainId: 0->ChainId.fromInt, contractConfigs: fetchState.contractConfigs, blockLag: 0, onBlockRegistrations: [], diff --git a/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res b/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res index 13ab20d852..08128a31d9 100644 --- a/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res +++ b/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res @@ -213,7 +213,7 @@ describe("EVM event decoding via EvmRpcClient.getLogs", () => { Internal.Event({ onEventRegistration: (MockIndexer.evmOnEventRegistration(~contractName="ERC20") :> Internal.onEventRegistration), - chain: ChainMap.Chain.makeUnsafe(~chainId=137), + chain: ChainMap.Chain.makeUnsafe(~chainId=137->ChainId.fromInt), blockNumber, logIndex, transactionIndex: 0, @@ -221,7 +221,7 @@ describe("EVM event decoding via EvmRpcClient.getLogs", () => { })->Internal.castUnsafeEventItem t.expect(MockIndexer.config.ecosystem.toRawEvent(eventItem)).toEqual({ - chain_id: 137, + chain_id: 137->ChainId.fromInt, event_id: EventUtils.packEventIndex(~logIndex, ~blockNumber), event_name: "EventWithoutFields", contract_name: "ERC20", diff --git a/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res b/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res index 096897adec..a8b081dc60 100644 --- a/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res +++ b/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res @@ -45,7 +45,7 @@ let makeState = (~onError=errHandler => errHandler->ErrorHandling.raiseExn, ()) ~committedProgressBlockNumber=-1, ~logger=Logging.getLogger(), ) - chainStates->Utils.Dict.setByInt(chainConfig.id, chainState) + chainStates->ChainId.Dict.set(chainConfig.id, chainState) }) IndexerState.make( diff --git a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res index 8cc7b2c0f8..2106efa9bd 100644 --- a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res +++ b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res @@ -226,7 +226,7 @@ describe("Test PgStorage SQL generation functions", () => { ~chainConfigs=[ { name: "Chain1", - id: 1, + id: 1->ChainId.fromInt, startBlock: 100, endBlock: 200, maxReorgDepth: 10, @@ -236,7 +236,7 @@ describe("Test PgStorage SQL generation functions", () => { }, { name: "Chain137", - id: 137, + id: 137->ChainId.fromInt, startBlock: 0, maxReorgDepth: 200, blockLag: 0, @@ -712,7 +712,7 @@ WHERE cp."block_hash" IS NOT NULL async t => { let chainConfig: Config.chain = { name: "Chain1", - id: 1, + id: 1->ChainId.fromInt, startBlock: 100, endBlock: 200, maxReorgDepth: 5, @@ -740,7 +740,7 @@ VALUES (1, 100, 200, 5, 0, NULL, -1, -1, NULL, 0, false);` async t => { let chainConfig: Config.chain = { name: "Chain1", - id: 1, + id: 1->ChainId.fromInt, startBlock: 100, maxReorgDepth: 5, blockLag: 0, @@ -768,7 +768,7 @@ VALUES (1, 100, NULL, 5, 0, NULL, -1, -1, NULL, 0, false);` async t => { let chainConfig1: Config.chain = { name: "Chain1", - id: 1, + id: 1->ChainId.fromInt, startBlock: 100, endBlock: 200, maxReorgDepth: 5, @@ -779,7 +779,7 @@ VALUES (1, 100, NULL, 5, 0, NULL, -1, -1, NULL, 0, false);` let chainConfig2: Config.chain = { name: "Chain42", - id: 42, + id: 42->ChainId.fromInt, startBlock: 500, maxReorgDepth: 0, blockLag: 0, @@ -1049,7 +1049,7 @@ describe("ecosystem.toRawEvent", () => { Internal.Event({ onEventRegistration: (MockIndexer.evmOnEventRegistration(~contractName="ERC20") :> Internal.onEventRegistration), - chain: ChainMap.Chain.makeUnsafe(~chainId=137), + chain: ChainMap.Chain.makeUnsafe(~chainId=137->ChainId.fromInt), blockNumber, logIndex, transactionIndex: 0, @@ -1057,7 +1057,7 @@ describe("ecosystem.toRawEvent", () => { })->Internal.castUnsafeEventItem t.expect(MockIndexer.config.ecosystem.toRawEvent(eventItem)).toEqual({ - chain_id: 137, + chain_id: 137->ChainId.fromInt, event_id: EventUtils.packEventIndex(~logIndex, ~blockNumber), event_name: "EventWithoutFields", contract_name: "ERC20", @@ -1079,7 +1079,7 @@ describe("PgStorage.removeInvalidUtf8InPlace", () => { "Strips NUL bytes from raw event rows, including deep inside jsonb params and field selections", async t => { let rawEvent: InternalTable.RawEvents.t = { - chain_id: 1, + chain_id: 1->ChainId.fromInt, event_id: 42n, event_name: "Name\x00Changed", contract_name: "Resolver", @@ -1098,7 +1098,7 @@ describe("PgStorage.removeInvalidUtf8InPlace", () => { [rawEvent]->PgStorage.removeInvalidUtf8InPlace t.expect(rawEvent).toEqual({ - chain_id: 1, + chain_id: 1->ChainId.fromInt, event_id: 42n, event_name: "NameChanged", contract_name: "Resolver", diff --git a/scenarios/test_codegen/test/lib_tests/SameSignatureEventDecode_test.res b/scenarios/test_codegen/test/lib_tests/SameSignatureEventDecode_test.res index 86990f9502..8df4b490d2 100644 --- a/scenarios/test_codegen/test/lib_tests/SameSignatureEventDecode_test.res +++ b/scenarios/test_codegen/test/lib_tests/SameSignatureEventDecode_test.res @@ -25,7 +25,7 @@ let makeReg = (~name, ~params): Internal.evmOnEventRegistration => ~handler=None, ~contractRegister=None, ~where=None, - ~chainId=1, + ~chainId=1->ChainId.fromInt, ~onEventBlockFilterSchema=Evm.make(~logger=Logging.getLogger()).onEventBlockFilterSchema, ) diff --git a/scenarios/test_codegen/test/lib_tests/SourceManager_test.res b/scenarios/test_codegen/test/lib_tests/SourceManager_test.res index b71a788017..46785c9f63 100644 --- a/scenarios/test_codegen/test/lib_tests/SourceManager_test.res +++ b/scenarios/test_codegen/test/lib_tests/SourceManager_test.res @@ -463,7 +463,7 @@ describe("SourceManager fetchNext", () => { normalSelection, latestOnBlockBlockNumber: latestFullyFetchedBlock.contents.blockNumber, maxOnBlockBufferSize: targetBufferSize, - chainId: 0, + chainId: 0->ChainId.fromInt, contractConfigs: Dict.make(), blockLag: 0, onBlockRegistrations: [], diff --git a/scenarios/test_codegen/test/rollback/ChainMocking.res b/scenarios/test_codegen/test/rollback/ChainMocking.res index 439672adfb..62512b715c 100644 --- a/scenarios/test_codegen/test/rollback/ChainMocking.res +++ b/scenarios/test_codegen/test/rollback/ChainMocking.res @@ -54,7 +54,7 @@ module Make = () => { } type composedEventConstructor = ( - ~chainId: int, + ~chainId: ChainId.t, ~blockTimestamp: int, ~blockNumber: int, ~transactionIndex: int, diff --git a/scenarios/test_codegen/test/rollback/Rollback_test.res b/scenarios/test_codegen/test/rollback/Rollback_test.res index 569cbdb821..4604766796 100644 --- a/scenarios/test_codegen/test/rollback/Rollback_test.res +++ b/scenarios/test_codegen/test/rollback/Rollback_test.res @@ -14,7 +14,7 @@ describe("E2E rollback tests", () => { ~sourceMock: MockIndexer.Source.t, ~indexerMock: MockIndexer.Indexer.t, ~firstHistoryCheckpointId=2n, - ~chainId=1337, + ~chainId=1337->ChainId.fromInt, ) => { t.expect( sourceMock.getItemsOrThrowCalls->Array.map(c => c.payload)->Utils.Array.last, @@ -692,7 +692,7 @@ describe("E2E rollback tests", () => { t.expect( rollbackCommitCalls, ~message="Should fire once for the reorged chain with the last valid block", - ).toEqual([{RollbackCommit.chainId: 1337, rollbackToBlock: 100}]) + ).toEqual([{RollbackCommit.chainId: 1337->ChainId.fromInt, rollbackToBlock: 100}]) }) Async.it( @@ -725,7 +725,7 @@ describe("E2E rollback tests", () => { { id: 2n, eventsProcessed: 0, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 102, blockHash: Js.Null.Value("0x102"), }, @@ -801,7 +801,7 @@ describe("E2E rollback tests", () => { { id: 4n, eventsProcessed: 0, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 102, blockHash: Js.Null.Value("0x102-reorged"), }, @@ -846,7 +846,7 @@ describe("E2E rollback tests", () => { ~sourceMock=sourceMock2, ~indexerMock, ~firstHistoryCheckpointId=3n, - ~chainId=100, + ~chainId=100->ChainId.fromInt, ) }, ) @@ -994,7 +994,7 @@ describe("E2E rollback tests", () => { id: `1337-${Envio.TestHelpers.Addresses.mockAddresses ->Array.getUnsafe(0) ->Address.toString}`, - chainId: 1337, + chainId: 1337->ChainId.fromInt, registrationBlock: 102, registrationLogIndex: 2, contractName: "SimpleNft", @@ -1088,7 +1088,7 @@ This might be wrong after we start exposing a block hash for progress block.`, id: `1337-${Envio.TestHelpers.Addresses.mockAddresses ->Array.getUnsafe(0) ->Address.toString}`, - chainId: 1337, + chainId: 1337->ChainId.fromInt, registrationBlock: 102, registrationLogIndex: 2, contractName: "SimpleNft", @@ -1232,35 +1232,35 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 3n, eventsProcessed: 1, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, { id: 4n, eventsProcessed: 2, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, { id: 5n, eventsProcessed: 1, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 106, blockHash: Js.Null.Value("0x106"), }, { id: 6n, eventsProcessed: 1, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 106, blockHash: Js.Null.Value("0x106"), }, { id: 7n, eventsProcessed: 1, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 107, blockHash: Js.Null.Null, }, @@ -1269,7 +1269,7 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 8n, eventsProcessed: 0, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 109, blockHash: Js.Null.Value("0x109"), }, @@ -1493,14 +1493,14 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 3n, eventsProcessed: 1, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, { id: 4n, eventsProcessed: 2, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, @@ -1510,14 +1510,14 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 10n, eventsProcessed: 2, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 106, blockHash: Js.Null.Value("0x106"), }, { id: 11n, eventsProcessed: 0, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 111, blockHash: Js.Null.Value("0x111"), }, @@ -1700,35 +1700,35 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 3n, eventsProcessed: 1, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, { id: 4n, eventsProcessed: 2, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, { id: 5n, eventsProcessed: 1, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 106, blockHash: Js.Null.Value("0x106"), }, { id: 6n, eventsProcessed: 2, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 106, blockHash: Js.Null.Value("0x106"), }, { id: 7n, eventsProcessed: 1, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 107, blockHash: Js.Null.Null, }, @@ -1737,7 +1737,7 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 8n, eventsProcessed: 0, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 109, blockHash: Js.Null.Value("0x109"), }, @@ -1908,14 +1908,14 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 3n, eventsProcessed: 1, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, { id: 4n, eventsProcessed: 2, - chainId: 1337, + chainId: 1337->ChainId.fromInt, blockNumber: 103, blockHash: Js.Null.Value("0x103"), }, @@ -1925,14 +1925,14 @@ This might be wrong after we start exposing a block hash for progress block.`, { id: 10n, eventsProcessed: 2, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 106, blockHash: Js.Null.Value("0x106"), }, { id: 11n, eventsProcessed: 0, - chainId: 100, + chainId: 100->ChainId.fromInt, blockNumber: 111, blockHash: Js.Null.Value("0x111"), }, From dca49f72e9d567aba0d46da508a78a9907d1fd45 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 08:09:51 +0000 Subject: [PATCH 3/6] Drop ChainMap.Chain in favour of ChainId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Chain.t` was an alias for `ChainId.t`, and `makeUnsafe`/`toChainId` were both `%identity` — a second name for the same type, with a constructor that no longer constructed anything. Callers now pass `ChainId.t` directly; `ChainId.fromInt` is the one way to make a chain id from an int literal. ChainMap keeps its Belt.Map wrapper, keyed on ChainId.t. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ECqzHdVS4cLxfCXPw8zpb6 --- .../test/ClientAddressFilter_test.res | 4 +-- packages/envio-tests/test/RateLimit_test.res | 2 +- .../test/SvmHyperSyncSource_test.res | 2 +- .../test/lib_tests/ChainIdMode_test.res | 2 +- .../lib_tests/ChainState_materialize_test.res | 2 +- packages/envio/src/Batch.res | 2 +- packages/envio/src/ChainFetching.res | 4 +-- packages/envio/src/ChainMap.res | 34 +++++++------------ packages/envio/src/ChainMap.resi | 26 +++++--------- packages/envio/src/ChainState.res | 4 +-- packages/envio/src/Config.res | 15 ++++---- .../envio/src/ContractRegisterContext.res | 2 +- packages/envio/src/CrossChainState.res | 6 ++-- packages/envio/src/CrossChainState.resi | 2 +- packages/envio/src/IndexerState.res | 8 ++--- packages/envio/src/IndexerState.resi | 2 +- packages/envio/src/Internal.res | 6 ++-- packages/envio/src/Main.res | 2 +- packages/envio/src/RawEvent.res | 3 +- packages/envio/src/Rollback.res | 8 ++--- .../envio/src/SimulateDeadInputTracker.res | 2 +- packages/envio/src/SimulateItems.res | 13 ++++--- packages/envio/src/TestIndexer.res | 8 ++--- packages/envio/src/sources/Evm.res | 2 +- .../envio/src/sources/EvmHyperSyncSource.res | 8 ++--- packages/envio/src/sources/Fuel.res | 2 +- .../envio/src/sources/FuelHyperSyncSource.res | 8 ++--- packages/envio/src/sources/RpcSource.res | 17 +++++----- packages/envio/src/sources/SimulateSource.res | 2 +- packages/envio/src/sources/Source.res | 2 +- packages/envio/src/sources/SourceManager.res | 18 +++++----- packages/envio/src/sources/Svm.res | 5 ++- .../envio/src/sources/SvmHyperSyncSource.res | 2 +- .../test/FuelHyperSyncSourceHeight_test.res | 2 +- .../test/IndexerStateStall_test.res | 2 +- .../test_codegen/test/IndexerState_test.res | 9 +++-- .../test/RpcSourceContract_test.res | 2 +- .../test_codegen/test/RpcSource_test.res | 2 +- .../test/SourceBlockHashes_test.res | 2 +- .../test/__mocks__/MockConfig.res | 6 ++-- .../test_codegen/test/helpers/MockIndexer.res | 6 ++-- .../test/helpers/RpcSourcePins.res | 2 +- .../test/lib_tests/CrossChainState_test.res | 30 ++++++++-------- .../lib_tests/FetchState_onBlock_test.res | 2 +- .../test/lib_tests/FetchState_test.res | 2 +- .../test/lib_tests/HyperSyncDecoder_test.res | 2 +- .../test/lib_tests/PgStorage_test.res | 2 +- .../test/rollback/ChainMocking.res | 2 +- 48 files changed, 132 insertions(+), 166 deletions(-) diff --git a/packages/envio-tests/test/ClientAddressFilter_test.res b/packages/envio-tests/test/ClientAddressFilter_test.res index b65d7106c9..4fb83c2a1d 100644 --- a/packages/envio-tests/test/ClientAddressFilter_test.res +++ b/packages/envio-tests/test/ClientAddressFilter_test.res @@ -189,7 +189,7 @@ describe("filterByClientAddress applies clientAddressFilter", () => { let onEventRegistration = (onEventRegistration :> Internal.onEventRegistration) let makeItem = (~to, ~blockNumber): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), + chain: 1->ChainId.fromInt, blockNumber, onEventRegistration, logIndex: 0, @@ -242,7 +242,7 @@ describe("filterByClientAddress drops over-fetched non-wildcard srcAddress event let onEventRegistration = (onEventRegistration :> Internal.onEventRegistration) let makeItem = (~srcAddress, ~blockNumber): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), + chain: 1->ChainId.fromInt, blockNumber, onEventRegistration, logIndex: 0, diff --git a/packages/envio-tests/test/RateLimit_test.res b/packages/envio-tests/test/RateLimit_test.res index 12bfcdede4..e25b91f150 100644 --- a/packages/envio-tests/test/RateLimit_test.res +++ b/packages/envio-tests/test/RateLimit_test.res @@ -1,6 +1,6 @@ open Vitest -let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) +let chain = 1->ChainId.fromInt // Mock source that throws Source.RateLimited on the first N calls, then // returns Ok with the requested block data. Lets us exercise diff --git a/packages/envio-tests/test/SvmHyperSyncSource_test.res b/packages/envio-tests/test/SvmHyperSyncSource_test.res index 49a59d011d..b7a24d7858 100644 --- a/packages/envio-tests/test/SvmHyperSyncSource_test.res +++ b/packages/envio-tests/test/SvmHyperSyncSource_test.res @@ -11,7 +11,7 @@ open Vitest // logIndex, and Rust-decoded params parsed from JSON strings. let metaplexProgramId = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" -let chain = ChainMap.Chain.makeUnsafe(~chainId=0->ChainId.fromInt) +let chain = 0->ChainId.fromInt let blockTime = 1778064393 let slot = 417950033 diff --git a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res index 5dbfbe5640..d80a8c7480 100644 --- a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res +++ b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res @@ -71,7 +71,7 @@ describe("ChainIdMode resolution", () => { it("parses wide chain ids losslessly through the public config", t => { t.expect( [tronConfig, multichainConfig, maxSafeConfig]->Array.map(config => - config.chainMap->ChainMap.keys->Array.map(ChainMap.Chain.toString) + config.chainMap->ChainMap.keys->Array.map(ChainId.toString) ), ).toEqual([ ["2494104990", "3448148188"], diff --git a/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res b/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res index 81fe9cedca..1f562cbd3f 100644 --- a/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res +++ b/packages/envio-tests/test/lib_tests/ChainState_materialize_test.res @@ -28,7 +28,7 @@ let makeItem = ( "kind": 0, "blockNumber": blockNumber, "transactionIndex": transactionIndex, - "chain": ChainMap.Chain.makeUnsafe(~chainId=materializeChainId), + "chain": materializeChainId, "onEventRegistration": { "eventConfig": {"transactionFieldMask": transactionMask, "blockFieldMask": blockMask}, diff --git a/packages/envio/src/Batch.res b/packages/envio/src/Batch.res index 96ac2e6ec2..2ca36ef71f 100644 --- a/packages/envio/src/Batch.res +++ b/packages/envio/src/Batch.res @@ -325,7 +325,7 @@ let findLastEventItem = (batch: t, ~chainId) => { switch item { | Internal.Event(_) as eventItem => { let eventItem = eventItem->Internal.castUnsafeEventItem - if eventItem.chain->ChainMap.Chain.toChainId === chainId { + if eventItem.chain === chainId { result := Some(eventItem) } else { idx := idx.contents - 1 diff --git a/packages/envio/src/ChainFetching.res b/packages/envio/src/ChainFetching.res index 28e31f4b3a..250b5295ca 100644 --- a/packages/envio/src/ChainFetching.res +++ b/packages/envio/src/ChainFetching.res @@ -142,7 +142,7 @@ let rec onQueryResponse = async ( if numContractRegisterEvents === 0 { Logging.trace({ "msg": "Finished querying", - "chainId": chain->ChainMap.Chain.toChainId, + "chainId": chain, "partitionId": query.partitionId, "fromBlock": fromBlockQueried, "toBlock": latestFetchedBlockNumber, @@ -151,7 +151,7 @@ let rec onQueryResponse = async ( } else { Logging.trace({ "msg": "Finished querying", - "chainId": chain->ChainMap.Chain.toChainId, + "chainId": chain, "partitionId": query.partitionId, "fromBlock": fromBlockQueried, "toBlock": latestFetchedBlockNumber, diff --git a/packages/envio/src/ChainMap.res b/packages/envio/src/ChainMap.res index d75903455a..b8e39fc704 100644 --- a/packages/envio/src/ChainMap.res +++ b/packages/envio/src/ChainMap.res @@ -1,40 +1,32 @@ -module Chain = { - type t = ChainId.t - - external toChainId: t => ChainId.t = "%identity" - - let toString = chainId => chainId->ChainId.toString - - external makeUnsafe: (~chainId: ChainId.t) => t = "%identity" -} - module ChainIdCmp = Belt.Id.MakeComparable({ - type t = Chain.t + type t = ChainId.t let cmp = (a, b) => ChainId.compare(a, b)->Int.fromFloat }) type t<'a> = Belt.Map.t -let fromArrayUnsafe: array<(Chain.t, 'a)> => t<'a> = arr => { +let fromArrayUnsafe: array<(ChainId.t, 'a)> => t<'a> = arr => { arr->Belt.Map.fromArray(~id=module(ChainIdCmp)) } -let get: (t<'a>, Chain.t) => 'a = (self, chain) => +let get: (t<'a>, ChainId.t) => 'a = (self, chain) => switch Belt.Map.get(self, chain) { | Some(v) => v | None => - // Should be unreachable, since we validate on Chain.t creation + // Should be unreachable, since we validate chain ids when parsing the config. // Still throw just in case something went wrong - JsError.throwWithMessage("No chain with id " ++ chain->Chain.toString ++ " found in chain map") + JsError.throwWithMessage( + "No chain with id " ++ chain->ChainId.toString ++ " found in chain map", + ) } -let set: (t<'a>, Chain.t, 'a) => t<'a> = (map, chain, v) => Belt.Map.set(map, chain, v) +let set: (t<'a>, ChainId.t, 'a) => t<'a> = (map, chain, v) => Belt.Map.set(map, chain, v) let values: t<'a> => array<'a> = map => Belt.Map.valuesToArray(map) -let keys: t<'a> => array = map => Belt.Map.keysToArray(map) -let entries: t<'a> => array<(Chain.t, 'a)> = map => Belt.Map.toArray(map) -let has: (t<'a>, Chain.t) => bool = (map, chain) => Belt.Map.has(map, chain) +let keys: t<'a> => array = map => Belt.Map.keysToArray(map) +let entries: t<'a> => array<(ChainId.t, 'a)> = map => Belt.Map.toArray(map) +let has: (t<'a>, ChainId.t) => bool = (map, chain) => Belt.Map.has(map, chain) let map: (t<'a>, 'a => 'b) => t<'b> = (map, fn) => Belt.Map.map(map, fn) -let mapWithKey: (t<'a>, (Chain.t, 'a) => 'b) => t<'b> = (map, fn) => Belt.Map.mapWithKey(map, fn) +let mapWithKey: (t<'a>, (ChainId.t, 'a) => 'b) => t<'b> = (map, fn) => Belt.Map.mapWithKey(map, fn) let size: t<'a> => int = map => Belt.Map.size(map) -let update: (t<'a>, Chain.t, 'a => 'a) => t<'a> = (map, chain, updateFn) => +let update: (t<'a>, ChainId.t, 'a => 'a) => t<'a> = (map, chain, updateFn) => Belt.Map.update(map, chain, opt => opt->Option.map(updateFn)) diff --git a/packages/envio/src/ChainMap.resi b/packages/envio/src/ChainMap.resi index f2d4022a0c..9393326fcb 100644 --- a/packages/envio/src/ChainMap.resi +++ b/packages/envio/src/ChainMap.resi @@ -1,22 +1,12 @@ -module Chain: { - type t - - external toChainId: t => ChainId.t = "%identity" - - let toString: t => string - - external makeUnsafe: (~chainId: ChainId.t) => t = "%identity" -} - type t<'a> -let fromArrayUnsafe: array<(Chain.t, 'a)> => t<'a> -let get: (t<'a>, Chain.t) => 'a -let set: (t<'a>, Chain.t, 'a) => t<'a> +let fromArrayUnsafe: array<(ChainId.t, 'a)> => t<'a> +let get: (t<'a>, ChainId.t) => 'a +let set: (t<'a>, ChainId.t, 'a) => t<'a> let values: t<'a> => array<'a> -let keys: t<'a> => array -let entries: t<'a> => array<(Chain.t, 'a)> -let has: (t<'a>, Chain.t) => bool +let keys: t<'a> => array +let entries: t<'a> => array<(ChainId.t, 'a)> +let has: (t<'a>, ChainId.t) => bool let map: (t<'a>, 'a => 'b) => t<'b> -let mapWithKey: (t<'a>, (Chain.t, 'a) => 'b) => t<'b> +let mapWithKey: (t<'a>, (ChainId.t, 'a) => 'b) => t<'b> let size: t<'a> => int -let update: (t<'a>, Chain.t, 'a => 'a) => t<'a> +let update: (t<'a>, ChainId.t, 'a => 'a) => t<'a> diff --git a/packages/envio/src/ChainState.res b/packages/envio/src/ChainState.res index f855999d83..d47969ce8c 100644 --- a/packages/envio/src/ChainState.res +++ b/packages/envio/src/ChainState.res @@ -204,7 +204,7 @@ let makeInternal = ( }) // Create sources lazily here - this is where API token validation happens - let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id) + let chain = chainConfig.id let lowercaseAddresses = config.lowercaseAddresses let sources = switch chainConfig.sourceConfig { | Config.EvmSourceConfig({hypersync, rpcs}) => @@ -241,7 +241,7 @@ let makeInternal = ( switch (hypersync, rpc) { | (None, None) => JsError.throwWithMessage( - `Chain ${chain->ChainMap.Chain.toString} has no SVM data source`, + `Chain ${chain->ChainId.toString} has no SVM data source`, ) | (None, Some(rpc)) => [Svm.makeRPCSource(~chain, ~rpc)] | (Some(hypersyncUrl), _) => diff --git a/packages/envio/src/Config.res b/packages/envio/src/Config.res index f9f6f20dcf..c563cfa087 100644 --- a/packages/envio/src/Config.res +++ b/packages/envio/src/Config.res @@ -974,7 +974,7 @@ let fromPublic = (publicConfigJson: JSON.t) => { let chainMap = chains ->Array.map(chain => { - (ChainMap.Chain.makeUnsafe(~chainId=chain.id), chain) + (chain.id, chain) }) ->ChainMap.fromArrayUnsafe @@ -1101,8 +1101,7 @@ let normalizeSimulateAddress = (config: t, address: Address.t): Address.t => let getEventConfig = (config: t, ~contractName, ~eventName, ~chainId: option=?) => { let chains = switch chainId { | Some(chainId) => - let chain = ChainMap.Chain.makeUnsafe(~chainId) - switch config.chainMap->ChainMap.get(chain) { + switch config.chainMap->ChainMap.get(chainId) { | chainConfig => [chainConfig] | exception _ => JsError.throwWithMessage( @@ -1128,14 +1127,12 @@ let shouldSaveHistory = (config, ~isInReorgThreshold) => let shouldPruneHistory = (config, ~isInReorgThreshold) => !config.shouldSaveFullHistory && (config.shouldRollbackOnReorg && isInReorgThreshold) -let getChain = (config, ~chainId) => { - let chain = ChainMap.Chain.makeUnsafe(~chainId) - config.chainMap->ChainMap.has(chain) - ? chain +let getChain = (config, ~chainId) => + config.chainMap->ChainMap.has(chainId) + ? chainId : JsError.throwWithMessage( - "No chain with id " ++ chain->ChainMap.Chain.toString ++ " found in config.yaml", + "No chain with id " ++ chainId->ChainId.toString ++ " found in config.yaml", ) -} // A CLI command payload already contains the resolved JSON; priming lets // downstream callers skip the NAPI `getConfigJson` round-trip. Calling diff --git a/packages/envio/src/ContractRegisterContext.res b/packages/envio/src/ContractRegisterContext.res index 6fa21a42fe..3657cdce88 100644 --- a/packages/envio/src/ContractRegisterContext.res +++ b/packages/envio/src/ContractRegisterContext.res @@ -36,7 +36,7 @@ let contractRegisterChainTraps: Utils.Proxy.traps = { switch prop { | "id" => let eventItem = params.item->Internal.castUnsafeEventItem - eventItem.chain->ChainMap.Chain.toChainId->(Utils.magic: ChainId.t => unknown) + eventItem.chain->(Utils.magic: ChainId.t => unknown) | _ => // Look up the contract name directly in config contracts across all chains. let contractName = prop diff --git a/packages/envio/src/CrossChainState.res b/packages/envio/src/CrossChainState.res index 1937cfe837..a1cfc08e59 100644 --- a/packages/envio/src/CrossChainState.res +++ b/packages/envio/src/CrossChainState.res @@ -226,7 +226,7 @@ let idleOrWaitAction = (cs: ChainState.t) => // dropped — chains at head only trail each other by real-time block production. let checkAndFetch = async ( crossChainState: t, - ~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise, + ~dispatchChain: (~chain: ChainId.t, ~action: FetchState.nextQuery) => promise, ) => { let targetBudget = crossChainState.targetBufferSize->Int.toFloat let remaining = ref( @@ -342,9 +342,7 @@ let checkAndFetch = async ( switch actionByChain->ChainId.Dict.dangerouslyGetNonOption(chainId) { | Some(NothingToQuery) | None => () - | Some(action) => - let chain = ChainMap.Chain.makeUnsafe(~chainId) - promises->Array.push(dispatchChain(~chain, ~action)) + | Some(action) => promises->Array.push(dispatchChain(~chain=chainId, ~action)) } } let _ = await promises->Promise.all diff --git a/packages/envio/src/CrossChainState.resi b/packages/envio/src/CrossChainState.resi index de01c870f9..6887208604 100644 --- a/packages/envio/src/CrossChainState.resi +++ b/packages/envio/src/CrossChainState.resi @@ -36,5 +36,5 @@ let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => uni let priorityOrder: t => array let checkAndFetch: ( t, - ~dispatchChain: (~chain: ChainMap.Chain.t, ~action: FetchState.nextQuery) => promise, + ~dispatchChain: (~chain: ChainId.t, ~action: FetchState.nextQuery) => promise, ) => promise diff --git a/packages/envio/src/IndexerState.res b/packages/envio/src/IndexerState.res index 97e74485df..9b78000e81 100644 --- a/packages/envio/src/IndexerState.res +++ b/packages/envio/src/IndexerState.res @@ -1,4 +1,4 @@ -type chain = ChainMap.Chain.t +type chain = ChainId.t type rollbackState = | NoRollback | ReorgDetected({chain: chain, blockNumber: int}) @@ -361,12 +361,12 @@ let stop = (state: t) => { let getChainState = (state: t, ~chain: chain): ChainState.t => switch state.crossChainState ->CrossChainState.chainStates - ->ChainId.Dict.dangerouslyGetNonOption(chain->ChainMap.Chain.toChainId) { + ->ChainId.Dict.dangerouslyGetNonOption(chain) { | Some(cs) => cs | None => - // Should be unreachable, since we validate on Chain.t creation + // Should be unreachable: every configured chain gets a state at startup JsError.throwWithMessage( - "No chain with id " ++ chain->ChainMap.Chain.toString ++ " found in chain states", + "No chain with id " ++ chain->ChainId.toString ++ " found in chain states", ) } diff --git a/packages/envio/src/IndexerState.resi b/packages/envio/src/IndexerState.resi index 367898eb0d..17e3669649 100644 --- a/packages/envio/src/IndexerState.resi +++ b/packages/envio/src/IndexerState.resi @@ -1,7 +1,7 @@ // The indexer state. `t` is opaque: other modules read it through the accessors // and change it only through the transitions and setters exposed here. -type chain = ChainMap.Chain.t +type chain = ChainId.t type rollbackState = | NoRollback diff --git a/packages/envio/src/Internal.res b/packages/envio/src/Internal.res index 591f18af11..2fc6a20b11 100644 --- a/packages/envio/src/Internal.res +++ b/packages/envio/src/Internal.res @@ -616,7 +616,7 @@ type dcs = array type eventItem = private { kind: [#0], onEventRegistration: onEventRegistration, - chain: ChainMap.Chain.t, + chain: ChainId.t, blockNumber: int, logIndex: int, // Within-block transaction index — the key into the per-chain transaction @@ -669,7 +669,7 @@ type item = | @as(0) Event({ onEventRegistration: onEventRegistration, - chain: ChainMap.Chain.t, + chain: ChainId.t, blockNumber: int, logIndex: int, transactionIndex: int, @@ -686,7 +686,7 @@ external getItemLogIndex: item => int = "logIndex" let getItemChainId = item => switch item { - | Event({chain}) => chain->ChainMap.Chain.toChainId + | Event({chain}) => chain | Block({onBlockRegistration: {chainId}}) => chainId } diff --git a/packages/envio/src/Main.res b/packages/envio/src/Main.res index 7da20074a0..d005f58af1 100644 --- a/packages/envio/src/Main.res +++ b/packages/envio/src/Main.res @@ -183,7 +183,7 @@ let buildChainsObject = (~config: Config.t) => { get: () => { switch getIndexerState() { | Some(state) => { - let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id) + let chain = chainConfig.id let chainState = state->IndexerState.getChainState(~chain) chainState->ChainState.contractAddresses(~contractName=contract.name) } diff --git a/packages/envio/src/RawEvent.res b/packages/envio/src/RawEvent.res index 13f9221206..0f6147b5c3 100644 --- a/packages/envio/src/RawEvent.res +++ b/packages/envio/src/RawEvent.res @@ -34,7 +34,6 @@ let make = ( ): Internal.rawEvent => { let {chain, blockNumber, logIndex} = eventItem let eventConfig = eventItem.onEventRegistration.eventConfig - let chainId = chain->ChainMap.Chain.toChainId let eventId = EventUtils.packEventIndex(~logIndex, ~blockNumber) let blockFields = block @@ -62,7 +61,7 @@ let make = ( } { - chain_id: chainId, + chain_id: chain, event_id: eventId, event_name: eventConfig.name, contract_name: eventConfig.contractName, diff --git a/packages/envio/src/Rollback.res b/packages/envio/src/Rollback.res index 3c18b68de4..acbd2b77dd 100644 --- a/packages/envio/src/Rollback.res +++ b/packages/envio/src/Rollback.res @@ -111,8 +111,6 @@ and executeRollback = async ( ->IndexerState.getChainState(~chain=reorgChain) ->ChainState.setRollbackTargetBlock(~blockNumber=rollbackTargetBlockNumber) - let reorgChainId = reorgChain->ChainMap.Chain.toChainId - // Finish pending batch writes first: the target checkpoint, the progress // diff and the rollback diff below must all be computed from the same db // state. Otherwise an in-flight batch lands after the progress reads and @@ -122,7 +120,7 @@ and executeRollback = async ( let rollbackTargetCheckpointId = { switch await (state->IndexerState.persistence).storage.getRollbackTargetCheckpoint( - ~reorgChainId, + ~reorgChainId=reorgChain, ~lastKnownValidBlockNumber=rollbackTargetBlockNumber, ) { | Some(checkpointId) => checkpointId @@ -151,7 +149,7 @@ and executeRollback = async ( ) newProgressBlockNumberPerChain->ChainId.Dict.set( diff["chain_id"], - if rollbackTargetCheckpointId === 0n && diff["chain_id"] === reorgChainId { + if rollbackTargetCheckpointId === 0n && diff["chain_id"] === reorgChain { Pervasives.min(diff["new_progress_block_number"], rollbackTargetBlockNumber) } else { diff["new_progress_block_number"] @@ -174,7 +172,7 @@ and executeRollback = async ( chainId, ), ~rollbackTargetBlockNumber, - ~isReorgChain=chainId === reorgChainId, + ~isReorgChain=chainId === reorgChain, ) let toBlock = cs->ChainState.committedProgressBlockNumber if fromBlock !== toBlock { diff --git a/packages/envio/src/SimulateDeadInputTracker.res b/packages/envio/src/SimulateDeadInputTracker.res index de20866320..5f6d43c561 100644 --- a/packages/envio/src/SimulateDeadInputTracker.res +++ b/packages/envio/src/SimulateDeadInputTracker.res @@ -8,7 +8,7 @@ let itemKey = (item: Internal.item): string => switch item { | Internal.Event({chain, blockNumber, logIndex}) => `${chain - ->ChainMap.Chain.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}` + ->ChainId.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}` | Internal.Block(_) => "" } diff --git a/packages/envio/src/SimulateItems.res b/packages/envio/src/SimulateItems.res index 373ab518dc..1b9d2c21c0 100644 --- a/packages/envio/src/SimulateItems.res +++ b/packages/envio/src/SimulateItems.res @@ -246,8 +246,7 @@ let parse = ( ~chainConfig: Config.chain, ~onEventRegistrations: array, ): array => { - let chain = ChainMap.Chain.makeUnsafe(~chainId=chainConfig.id) - let chainId = chainConfig.id + let chain = chainConfig.id let startBlock = chainConfig.startBlock let currentBlock = ref(startBlock) let currentLogIndex = ref(0) @@ -334,7 +333,7 @@ let parse = ( switch seenCoordinates->Dict.get(coordinate) { | Some(firstIndex) => JsError.throwWithMessage( - `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chainId->ChainId.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`, + `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chain->ChainId.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`, ) | None => seenCoordinates->Dict.set(coordinate, itemIndex) } @@ -351,7 +350,7 @@ let parse = ( let liveRegistrations = HandlerRegister.getSimulateOnEventRegistrations( ~config, - ~chainId, + ~chainId=chain, ~eventConfig, )->Array.filter(reg => (reg.handler->Option.isSome || reg.contractRegister->Option.isSome) && @@ -363,7 +362,7 @@ let parse = ( ->ChainMap.values ->Array.length { | 1 => "" - | _ => ` on chain ${chainId->ChainId.toString}` + | _ => ` on chain ${chain->ChainId.toString}` }}. Register a handler with indexer.onEvent (and check any \`where\` filter isn't excluding this chain) before simulating it.`, ) } @@ -389,7 +388,7 @@ let parse = ( contractName: eventConfig.contractName, eventName: eventConfig.name, params, - chainId, + chainId: chain, srcAddress, logIndex, transaction, @@ -421,7 +420,7 @@ let patchConfig = ( switch processChains { | Some(chainsDict) => let newChainMap = config.chainMap->ChainMap.mapWithKey((chain, chainConfig) => { - let chainIdStr = chain->ChainMap.Chain.toString + let chainIdStr = chain->ChainId.toString switch chainsDict->Dict.get(chainIdStr) { | Some(processChainJson) => let raw = processChainJson->(Utils.magic: JSON.t => {..}) diff --git a/packages/envio/src/TestIndexer.res b/packages/envio/src/TestIndexer.res index 422c26a848..a89afd5c2c 100644 --- a/packages/envio/src/TestIndexer.res +++ b/packages/envio/src/TestIndexer.res @@ -223,8 +223,7 @@ let makeInitialState = ( ): Persistence.initialState => { let chainKeys = processConfigChains->Dict.keysToArray let chains = chainKeys->Array.map(chainIdStr => { - let chainId = chainIdStr->ChainId.normalizeOrThrow - let chain = ChainMap.Chain.makeUnsafe(~chainId) + let chain = chainIdStr->ChainId.normalizeOrThrow if !(config.chainMap->ChainMap.has(chain)) { JsError.throwWithMessage(`Chain ${chainIdStr} is not configured in config.yaml`) @@ -233,7 +232,7 @@ let makeInitialState = ( let processChainConfig = processConfigChains->Dict.getUnsafe(chainIdStr) let indexingAddresses = indexingAddressesByChain->Dict.get(chainIdStr)->Option.getOr([]) { - Persistence.id: chainId, + Persistence.id: chain, startBlock: processChainConfig.startBlock, endBlock: processChainConfig.endBlock, sourceBlockNumber: processChainConfig.endBlock->Option.getOr(0), @@ -310,10 +309,9 @@ let parseBlockRange = ( ~rawChainConfig: rawChainConfig, ~progressBlock: option, ): chainConfig => { - let chainId = try chainIdStr->ChainId.normalizeOrThrow catch { + let chain = try chainIdStr->ChainId.normalizeOrThrow catch { | _ => JsError.throwWithMessage(`Invalid chain ID "${chainIdStr}": expected a numeric chain ID`) } - let chain = ChainMap.Chain.makeUnsafe(~chainId) if !(config.chainMap->ChainMap.has(chain)) { JsError.throwWithMessage(`Chain ${chainIdStr} is not configured in config.yaml`) } diff --git a/packages/envio/src/sources/Evm.res b/packages/envio/src/sources/Evm.res index 055153ce0a..d9b6e1a84a 100644 --- a/packages/envio/src/sources/Evm.res +++ b/packages/envio/src/sources/Evm.res @@ -106,7 +106,7 @@ let make = (~logger: Pino.t): Ecosystem.t => { ~params={ "contract": eventItem.onEventRegistration.eventConfig.contractName, "event": eventItem.onEventRegistration.eventConfig.name, - "chainId": eventItem.chain->ChainMap.Chain.toChainId, + "chainId": eventItem.chain, "block": eventItem.blockNumber, "logIndex": eventItem.logIndex, "address": (eventItem.payload->toPayload).srcAddress, diff --git a/packages/envio/src/sources/EvmHyperSyncSource.res b/packages/envio/src/sources/EvmHyperSyncSource.res index 0c3c44997c..e46d8e16f5 100644 --- a/packages/envio/src/sources/EvmHyperSyncSource.res +++ b/packages/envio/src/sources/EvmHyperSyncSource.res @@ -6,7 +6,7 @@ open Source let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized") type options = { - chain: ChainMap.Chain.t, + chain: ChainId.t, endpointUrl: string, // The chain's registrations, indexed by their sequential `index`. onEventRegistrations: array, @@ -75,7 +75,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) payload: { contractName: onEventRegistration.eventConfig.contractName, eventName: onEventRegistration.eventConfig.name, - chainId: chain->ChainMap.Chain.toChainId, + chainId: chain, params: item.params, srcAddress, logIndex, @@ -250,7 +250,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) ~client, ~blockNumbers, ~sourceName=name, - ~chainId=chain->ChainMap.Chain.toChainId, + ~chainId=chain, ~logger, )->Promise.thenResolve(((queryRes, requestStats)) => { Source.result: queryRes->HyperSync.mapExn, @@ -287,7 +287,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) HyperSyncHeightStream.subscribe( ~hyperSyncUrl=endpointUrl, ~apiToken, - ~chainId=chain->ChainMap.Chain.toChainId, + ~chainId=chain, ~onHeight, ), } diff --git a/packages/envio/src/sources/Fuel.res b/packages/envio/src/sources/Fuel.res index 77094e54f3..093d4142c6 100644 --- a/packages/envio/src/sources/Fuel.res +++ b/packages/envio/src/sources/Fuel.res @@ -48,7 +48,7 @@ let make = (~logger: Pino.t): Ecosystem.t => { ~params={ "contract": eventItem.onEventRegistration.eventConfig.contractName, "event": eventItem.onEventRegistration.eventConfig.name, - "chainId": eventItem.chain->ChainMap.Chain.toChainId, + "chainId": eventItem.chain, "block": eventItem.blockNumber, "logIndex": eventItem.logIndex, "address": (eventItem.payload->toPayload).srcAddress, diff --git a/packages/envio/src/sources/FuelHyperSyncSource.res b/packages/envio/src/sources/FuelHyperSyncSource.res index d79be286f0..e62135916c 100644 --- a/packages/envio/src/sources/FuelHyperSyncSource.res +++ b/packages/envio/src/sources/FuelHyperSyncSource.res @@ -3,7 +3,7 @@ open Source let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized") type options = { - chain: ChainMap.Chain.t, + chain: ChainId.t, endpointUrl: string, apiToken: option, // The chain's registrations, indexed by their sequential `index`. @@ -117,8 +117,6 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) blocksByHeight->Utils.Map.set(block.height, block)->ignore }) - let chainId = chain->ChainMap.Chain.toChainId - let parsedQueueItems = pageUnsafe.items->Array.map(item => { // Routing happened in Rust; the item references its registration by // chain-scoped index. @@ -138,7 +136,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) try decode(data) catch { | exn => { let params = { - "chainId": chainId, + "chainId": chain, "blockNumber": item.blockHeight, "logIndex": item.receiptIndex, } @@ -177,7 +175,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) payload: { contractName: eventConfig.contractName, eventName: eventConfig.name, - chainId, + chainId: chain, params, transaction: { "id": item.txId, diff --git a/packages/envio/src/sources/RpcSource.res b/packages/envio/src/sources/RpcSource.res index bcf7650db0..cb8164d282 100644 --- a/packages/envio/src/sources/RpcSource.res +++ b/packages/envio/src/sources/RpcSource.res @@ -60,7 +60,7 @@ let getKnownRawBlockWithBackoff = async ( "err": err->Utils.prettifyExn, "msg": `Issue while running fetching batch of events from the RPC. Will wait ${currentBackoff.contents->Int.toString}ms and try again.`, "source": sourceName, - "chainId": chain->ChainMap.Chain.toChainId, + "chainId": chain, "type": "EXPONENTIAL_BACKOFF", }) await Time.resolvePromiseAfterDelay(~delayMilliseconds=currentBackoff.contents) @@ -613,7 +613,7 @@ type options = { sourceFor: Source.sourceFor, syncConfig: Config.sourceSync, url: string, - chain: ChainMap.Chain.t, + chain: ChainId.t, // The chain's registrations, indexed by their sequential `index`. onEventRegistrations: array, lowercaseAddresses: bool, @@ -633,11 +633,10 @@ let make = ( ?headers, }: options, ): t => { - let chainId = chain->ChainMap.Chain.toChainId let urlHost = switch Utils.Url.getHostFromUrl(url) { | None => JsError.throwWithMessage( - `The RPC url for chain ${chainId->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, + `The RPC url for chain ${chain->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, ) | Some(host) => host } @@ -690,7 +689,7 @@ let make = ( "msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000) ->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`, "source": name, - "chainId": chain->ChainMap.Chain.toChainId, + "chainId": chain, "metadata": { { "asyncTaskName": "transactionLoader: fetching transaction data - `getTransaction` rpc call", @@ -719,7 +718,7 @@ let make = ( "msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000) ->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`, "source": name, - "chainId": chain->ChainMap.Chain.toChainId, + "chainId": chain, "metadata": { { "asyncTaskName": "blockLoader: fetching block data - `getBlock` rpc call", @@ -750,7 +749,7 @@ let make = ( "msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000) ->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`, "source": name, - "chainId": chain->ChainMap.Chain.toChainId, + "chainId": chain, "metadata": { { "asyncTaskName": "receiptLoader: fetching transaction receipt - `getTransactionReceipt` rpc call", @@ -927,7 +926,7 @@ let make = ( payload: { contractName: eventConfig.contractName, eventName: eventConfig.name, - chainId: chain->ChainMap.Chain.toChainId, + chainId: chain, params: decoded, block, transaction, @@ -1019,7 +1018,7 @@ let make = ( let createHeightSubscription = ws->Option.map(wsUrl => - (~onHeight) => RpcWebSocketHeightStream.subscribe(~wsUrl, ~chainId, ~onHeight) + (~onHeight) => RpcWebSocketHeightStream.subscribe(~wsUrl, ~chainId=chain, ~onHeight) ) { diff --git a/packages/envio/src/sources/SimulateSource.res b/packages/envio/src/sources/SimulateSource.res index 8cacd6f542..f2d04d7cb8 100644 --- a/packages/envio/src/sources/SimulateSource.res +++ b/packages/envio/src/sources/SimulateSource.res @@ -1,4 +1,4 @@ -let make = (~items: array, ~endBlock: int, ~chain: ChainMap.Chain.t): Source.t => { +let make = (~items: array, ~endBlock: int, ~chain: ChainId.t): Source.t => { let reportedHeight = max(endBlock, 1) { diff --git a/packages/envio/src/sources/Source.res b/packages/envio/src/sources/Source.res index 98c903a521..1c84eec97e 100644 --- a/packages/envio/src/sources/Source.res +++ b/packages/envio/src/sources/Source.res @@ -64,7 +64,7 @@ type sourceFor = Sync | Fallback | Realtime type t = { name: string, sourceFor: sourceFor, - chain: ChainMap.Chain.t, + chain: ChainId.t, poweredByHyperSync: bool, /* Frequency (in ms) used when polling for new events on this network. */ pollingInterval: int, diff --git a/packages/envio/src/sources/SourceManager.res b/packages/envio/src/sources/SourceManager.res index 1e84d5ca92..1f1496c8f2 100644 --- a/packages/envio/src/sources/SourceManager.res +++ b/packages/envio/src/sources/SourceManager.res @@ -81,7 +81,7 @@ let getActiveSource = sourceManager => sourceManager.activeSource let getRequestStatSamples = (sourceManager: t): array => { let samples = [] sourceManager.sourcesState->Array.forEach(sourceState => { - let chainId = sourceState.source.chain->ChainMap.Chain.toChainId + let chainId = sourceState.source.chain sourceState.requestStats->Utils.Dict.forEachWithKey((agg, method) => { samples ->Array.push({ @@ -111,7 +111,7 @@ let getSourceHeightSamples = (sourceManager: t): array => { if sourceState.knownHeight > 0 { samples->Array.push({ sourceName: sourceState.source.name, - chainId: sourceState.source.chain->ChainMap.Chain.toChainId, + chainId: sourceState.source.chain, height: sourceState.knownHeight, }) } @@ -425,7 +425,7 @@ let getSourceNewHeight = async ( logger->Logging.childTrace({ "msg": "onHeight subscription stale, switching to polling fallback", "source": source.name, - "chainId": source.chain->ChainMap.Chain.toChainId, + "chainId": source.chain, }) let h = ref(initialHeight) while h.contents <= knownHeight && !(newHeight.contents > initialHeight) { @@ -585,7 +585,7 @@ let waitForNewBlock = async (sourceManager: t, ~knownHeight, ~isRealtime, ~reduc let logger = Logging.createChild( ~params={ - "chainId": sourceManager.activeSource.chain->ChainMap.Chain.toChainId, + "chainId": sourceManager.activeSource.chain, "knownHeight": knownHeight, }, ) @@ -725,7 +725,7 @@ let executeQuery = async ( | Some(s) => if s.source !== sourceManager.activeSource { let logger = Logging.createChild( - ~params={"chainId": sourceManager.activeSource.chain->ChainMap.Chain.toChainId}, + ~params={"chainId": sourceManager.activeSource.chain}, ) logger->Logging.childInfo({ "msg": "Switching data-source", @@ -737,7 +737,7 @@ let executeQuery = async ( s | None => let logger = Logging.createChild( - ~params={"chainId": sourceManager.activeSource.chain->ChainMap.Chain.toChainId}, + ~params={"chainId": sourceManager.activeSource.chain}, ) %raw(`null`)->ErrorHandling.mkLogAndRaise(~logger, ~msg=noSourcesError) } @@ -748,7 +748,7 @@ let executeQuery = async ( let logger = Logging.createChild( ~params={ - "chainId": source.chain->ChainMap.Chain.toChainId, + "chainId": source.chain, "logType": "Block Range Query", "partitionId": query.partitionId, "source": source.name, @@ -897,7 +897,7 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array, ~isReal | Some(s) => s | None => let logger = Logging.createChild( - ~params={"chainId": sourceManager.activeSource.chain->ChainMap.Chain.toChainId}, + ~params={"chainId": sourceManager.activeSource.chain}, ) %raw(`null`)->ErrorHandling.mkLogAndRaise( ~logger, @@ -910,7 +910,7 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array, ~isReal let logger = Logging.createChild( ~params={ - "chainId": source.chain->ChainMap.Chain.toChainId, + "chainId": source.chain, "logType": "Block Hash Query", "source": source.name, "retry": retry, diff --git a/packages/envio/src/sources/Svm.res b/packages/envio/src/sources/Svm.res index 0e7a1711a0..349717dae7 100644 --- a/packages/envio/src/sources/Svm.res +++ b/packages/envio/src/sources/Svm.res @@ -50,7 +50,7 @@ let make = (~logger: Pino.t): Ecosystem.t => { ~params={ "program": eventItem.onEventRegistration.eventConfig.contractName, "instruction": eventItem.onEventRegistration.eventConfig.name, - "chainId": eventItem.chain->ChainMap.Chain.toChainId, + "chainId": eventItem.chain, "slot": eventItem.blockNumber, "programId": instruction.programId, }, @@ -72,12 +72,11 @@ module GetFinalizedSlot = { let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): Source.t => { let client = Rest.client(rpc) - let chainId = chain->ChainMap.Chain.toChainId let urlHost = switch Utils.Url.getHostFromUrl(rpc) { | None => JsError.throwWithMessage( - `The RPC url for chain ${chainId->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, + `The RPC url for chain ${chain->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, ) | Some(host) => host } diff --git a/packages/envio/src/sources/SvmHyperSyncSource.res b/packages/envio/src/sources/SvmHyperSyncSource.res index 61557de04c..3f68fede32 100644 --- a/packages/envio/src/sources/SvmHyperSyncSource.res +++ b/packages/envio/src/sources/SvmHyperSyncSource.res @@ -1,7 +1,7 @@ open Source type options = { - chain: ChainMap.Chain.t, + chain: ChainId.t, endpointUrl: string, apiToken: option, onEventRegistrations: array, diff --git a/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res b/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res index e22d420d94..0cf3bfd1a5 100644 --- a/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res +++ b/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res @@ -38,7 +38,7 @@ let withServer = async (handler, body) => { } describe("FuelHyperSyncSource - getHeightOrThrow", () => { - let chain = ChainMap.Chain.makeUnsafe(~chainId=0->ChainId.fromInt) + let chain = 0->ChainId.fromInt // The native client validates that the token is a UUID before sending requests. let apiToken = "11111111-1111-1111-1111-111111111111" diff --git a/scenarios/test_codegen/test/IndexerStateStall_test.res b/scenarios/test_codegen/test/IndexerStateStall_test.res index 7f940b88ef..dcaebe6835 100644 --- a/scenarios/test_codegen/test/IndexerStateStall_test.res +++ b/scenarios/test_codegen/test/IndexerStateStall_test.res @@ -25,7 +25,7 @@ describe("IndexerState fetch stall accounting", () => { state->IndexerState.markProcessingStalledOnFetch await Time.resolvePromiseAfterDelay(~delayMilliseconds=50) state->IndexerState.beginReorg( - ~chain=ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), + ~chain=1->ChainId.fromInt, ~blockNumber=100, ) // Settled, not discarded: the wait before the reorg still has to land in diff --git a/scenarios/test_codegen/test/IndexerState_test.res b/scenarios/test_codegen/test/IndexerState_test.res index 19b2659d78..0c19bb4a48 100644 --- a/scenarios/test_codegen/test/IndexerState_test.res +++ b/scenarios/test_codegen/test/IndexerState_test.res @@ -73,7 +73,7 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) for logIndex in 0 to numberOfEventsInBatch { let batchItem = Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId=id), + chain: id, blockNumber: currentBlockNumber.contents, logIndex, transactionIndex: 0, @@ -158,7 +158,7 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) let getItemKey = (item: Internal.item) => switch item { | Event({chain, blockNumber, logIndex}) => ( - chain->ChainMap.Chain.toChainId, + chain, blockNumber, logIndex, ) @@ -293,7 +293,7 @@ describe("IndexerState", () => { ~latestFetchedBlock={blockNumber, blockTimestamp: blockNumber * 15}, ~newItems=[ Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId), + chain: chainId, blockNumber, logIndex: 0, transactionIndex: 0, @@ -354,7 +354,6 @@ describe("IndexerState", () => { ) let chain = config.chainMap->ChainMap.keys->Array.getUnsafe(0) - let chainId = chain->ChainMap.Chain.toChainId // A fetch lands mid-batch and appends block 15 to this chain's buffer // (its batch-time snapshot held only block 5). @@ -396,7 +395,7 @@ describe("IndexerState", () => { let resultCs = state->IndexerState.getChainState(~chain) let progressed = batch.progressedChainsById - ->ChainId.Dict.dangerouslyGetNonOption(chainId) + ->ChainId.Dict.dangerouslyGetNonOption(chain) ->Option.getUnsafe t.expect( diff --git a/scenarios/test_codegen/test/RpcSourceContract_test.res b/scenarios/test_codegen/test/RpcSourceContract_test.res index 8be03fe148..1a0e3f1582 100644 --- a/scenarios/test_codegen/test/RpcSourceContract_test.res +++ b/scenarios/test_codegen/test/RpcSourceContract_test.res @@ -2,7 +2,7 @@ open Vitest type sourceFactory = RpcSource.options => Source.t -let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) +let chain = 1->ChainId.fromInt let sighash = "0xcf16a92280c1bbb43f72d31126b724d508df2877835849e8744017ab36a9b47f" let transactionHash = "0x27e26f21f744064a4af53810d8002bbd7208a2ca4865503a99b9c529e5cff5ea" let contractAddress = "0x00000000000000000000000000000000000000AA" diff --git a/scenarios/test_codegen/test/RpcSource_test.res b/scenarios/test_codegen/test/RpcSource_test.res index 422f39853c..3124dd1c87 100644 --- a/scenarios/test_codegen/test/RpcSource_test.res +++ b/scenarios/test_codegen/test/RpcSource_test.res @@ -727,7 +727,7 @@ describe("RpcSource - fieldRegistry completeness", () => { }) }) -let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) +let chain = 1->ChainId.fromInt describe("RpcSource - empty selection", () => { Async.it("Throws UnsupportedSelection when the selection has no event configs", async t => { let source = RpcSource.make({ diff --git a/scenarios/test_codegen/test/SourceBlockHashes_test.res b/scenarios/test_codegen/test/SourceBlockHashes_test.res index 605e41234e..0fcf09bb33 100644 --- a/scenarios/test_codegen/test/SourceBlockHashes_test.res +++ b/scenarios/test_codegen/test/SourceBlockHashes_test.res @@ -6,7 +6,7 @@ let testApiToken = ) // Ethereum mainnet. -let chain = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) +let chain = 1->ChainId.fromInt // Uniswap V2 Factory's PairCreated event (topic0 = keccak("PairCreated(address,address,address,uint256)")) // 2 indexed args (token0, token1) ⇒ topicCount = 3. diff --git a/scenarios/test_codegen/test/__mocks__/MockConfig.res b/scenarios/test_codegen/test/__mocks__/MockConfig.res index 1c4d1a6cd3..38d0868e73 100644 --- a/scenarios/test_codegen/test/__mocks__/MockConfig.res +++ b/scenarios/test_codegen/test/__mocks__/MockConfig.res @@ -1,6 +1,6 @@ -let chain1 = ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt) -let chain137 = ChainMap.Chain.makeUnsafe(~chainId=137->ChainId.fromInt) -let chain1337 = ChainMap.Chain.makeUnsafe(~chainId=1337->ChainId.fromInt) +let chain1 = 1->ChainId.fromInt +let chain137 = 137->ChainId.fromInt +let chain1337 = 1337->ChainId.fromInt let getEventConfig = (~config=?, ~contractName, ~eventName, ~chainId=?) => { let config = switch config { diff --git a/scenarios/test_codegen/test/helpers/MockIndexer.res b/scenarios/test_codegen/test/helpers/MockIndexer.res index 79c418bd75..b0e2e29a73 100644 --- a/scenarios/test_codegen/test/helpers/MockIndexer.res +++ b/scenarios/test_codegen/test/helpers/MockIndexer.res @@ -450,7 +450,7 @@ module Indexer = { let chainMap = chains ->Array.map(chainConfig => { - let chain = ChainMap.Chain.makeUnsafe(~chainId=(chainConfig.chain :> int)->ChainId.fromInt) + let chain = (chainConfig.chain :> int)->ChainId.fromInt let originalChainConfig = baseConfig.chainMap->ChainMap.get(chain) ( chain, @@ -833,7 +833,7 @@ module Source = { } } - let chain = ChainMap.Chain.makeUnsafe(~chainId=(chain :> int)->ChainId.fromInt) + let chain = (chain :> int)->ChainId.fromInt let getHeightOrThrowCalls = [] let getHeightOrThrowResolveFns = [] let getHeightOrThrowRejectFns = [] @@ -1029,7 +1029,7 @@ module Source = { contractName: onEventRegistration.eventConfig.contractName, eventName: onEventRegistration.eventConfig.name, params: %raw(`{}`), - chainId: chain->ChainMap.Chain.toChainId, + chainId: chain, srcAddress: "0x0000000000000000000000000000000000000000"->Address.unsafeFromString, logIndex: item.logIndex, block: { diff --git a/scenarios/test_codegen/test/helpers/RpcSourcePins.res b/scenarios/test_codegen/test/helpers/RpcSourcePins.res index bb87533f61..d37f38bf61 100644 --- a/scenarios/test_codegen/test/helpers/RpcSourcePins.res +++ b/scenarios/test_codegen/test/helpers/RpcSourcePins.res @@ -66,7 +66,7 @@ let normalizeEvent = item => let payload = payload->Evm.toPayload { registrationId: onEventRegistration.eventConfig.id, - chainId: chain->ChainMap.Chain.toChainId, + chainId: chain, blockNumber, logIndex, transactionIndex, diff --git a/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res b/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res index 67cd266216..821b680494 100644 --- a/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res +++ b/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res @@ -4,7 +4,7 @@ let baseChainConfig = Config.load().chainMap->ChainMap.values->Utils.Array.first let mockEvent = (~blockNumber): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId=1->ChainId.fromInt), + chain: 1->ChainId.fromInt, blockNumber, // Carries an `index` so the buffer's dedup key resolves; the rest of the // registration is unused by these tests. @@ -248,7 +248,7 @@ describe("CrossChainState fetch control", () => { let dispatched = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatched->Array.push((chain->ChainMap.Chain.toChainId->ChainId.toInt, action))->ignore + dispatched->Array.push((chain->ChainId.toInt, action))->ignore Promise.resolve() }) @@ -278,7 +278,7 @@ describe("CrossChainState fetch control", () => { let dispatched = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action as _) => { - dispatched->Array.push(chain->ChainMap.Chain.toChainId->ChainId.toInt)->ignore + dispatched->Array.push(chain->ChainId.toInt)->ignore Promise.resolve() }) @@ -296,7 +296,7 @@ describe("CrossChainState fetch control", () => { await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { dispatched ->Array.push(( - chain->ChainMap.Chain.toChainId->ChainId.toInt, + chain->ChainId.toInt, switch action { | Ready(queries) => queries->Array.length | _ => 0 @@ -327,7 +327,7 @@ describe("CrossChainState fetch control", () => { | Ready(queries) => admitted ->Array.push(( - chain->ChainMap.Chain.toChainId->ChainId.toInt, + chain->ChainId.toInt, queries->Array.reduce(0, (sum, query: FetchState.query) => sum + query.itemsEst), )) ->ignore @@ -363,7 +363,7 @@ describe("CrossChainState fetch control", () => { let dispatched = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatched->Array.push((chain->ChainMap.Chain.toChainId->ChainId.toInt, action))->ignore + dispatched->Array.push((chain->ChainId.toInt, action))->ignore Promise.resolve() }) @@ -389,7 +389,7 @@ describe("CrossChainState fetch control", () => { await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { switch action { | Ready(queries) => - firstTickQueries->Array.push((chain->ChainMap.Chain.toChainId->ChainId.toInt, queries))->ignore + firstTickQueries->Array.push((chain->ChainId.toInt, queries))->ignore | _ => () } Promise.resolve() @@ -412,7 +412,7 @@ describe("CrossChainState fetch control", () => { let secondTickChains = [] await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { switch action { - | Ready(_) => secondTickChains->Array.push(chain->ChainMap.Chain.toChainId->ChainId.toInt)->ignore + | Ready(_) => secondTickChains->Array.push(chain->ChainId.toInt)->ignore | _ => () } Promise.resolve() @@ -510,7 +510,7 @@ describe("CrossChainState fetch control", () => { let dispatchedItemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { dispatchedItemsByChain->ChainId.Dict.set( - chain->ChainMap.Chain.toChainId, + chain, switch action { | Ready(queries) => queries->Array.reduce(0., (acc, q: FetchState.query) => acc +. q.itemsEst->Int.toFloat) @@ -559,7 +559,7 @@ describe("CrossChainState fetch control", () => { let actionsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { actionsByChain->ChainId.Dict.set( - chain->ChainMap.Chain.toChainId, + chain, switch action { | WaitingForNewBlock => "waitingForNewBlock" | NothingToQuery => "nothingToQuery" @@ -608,7 +608,7 @@ describe("CrossChainState fetch control", () => { let estimatesByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { estimatesByChain->ChainId.Dict.set( - chain->ChainMap.Chain.toChainId, + chain, switch action { | Ready(queries) => queries->Array.reduce(0, (total, query: FetchState.query) => total + query.itemsEst) @@ -737,7 +737,7 @@ describe("ChainState cold start", () => { let dispatchedItemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { dispatchedItemsByChain->ChainId.Dict.set( - chain->ChainMap.Chain.toChainId, + chain, switch action { | Ready(queries) => queries->Array.reduce(0., (acc, q: FetchState.query) => acc +. q.itemsEst->Int.toFloat) @@ -776,7 +776,7 @@ describe("ChainState cold start", () => { let actionsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { actionsByChain->ChainId.Dict.set( - chain->ChainMap.Chain.toChainId, + chain, switch action { | WaitingForNewBlock => "waitingForNewBlock" | NothingToQuery => "nothingToQuery" @@ -821,7 +821,7 @@ describe("ChainState cold start", () => { let dispatchedItemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { dispatchedItemsByChain->ChainId.Dict.set( - chain->ChainMap.Chain.toChainId, + chain, switch action { | Ready(queries) => queries->Array.reduce(0, (acc, q: FetchState.query) => acc + q.itemsEst) @@ -908,7 +908,7 @@ describe("ChainState cold start", () => { let itemsByChain = Dict.make() await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { itemsByChain->ChainId.Dict.set( - chain->ChainMap.Chain.toChainId, + chain, switch action { | Ready(queries) => queries->Array.reduce(0, (acc, q: FetchState.query) => acc + q.itemsEst) | _ => 0 diff --git a/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res b/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res index 43575f41d1..125745a767 100644 --- a/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res +++ b/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res @@ -70,7 +70,7 @@ let makeInitialWithOnBlock = (~startBlock=0, ~onBlockRegistrations) => { } let mockEvent = (~blockNumber, ~logIndex=0): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId), + chain: chainId, blockNumber, // Carries an `index` so the buffer's dedup key (blockNumber, logIndex, index) // resolves; the rest of the registration is unused by these tests. diff --git a/scenarios/test_codegen/test/lib_tests/FetchState_test.res b/scenarios/test_codegen/test/lib_tests/FetchState_test.res index 2a4280f9fc..180b116c34 100644 --- a/scenarios/test_codegen/test/lib_tests/FetchState_test.res +++ b/scenarios/test_codegen/test/lib_tests/FetchState_test.res @@ -76,7 +76,7 @@ let makeConfigContract = (contractName, address): Internal.indexingAddress => { let mockEvent = (~blockNumber, ~logIndex=0, ~chainId=1->ChainId.fromInt, ~registrationIndex=0): Internal.item => Internal.Event({ - chain: ChainMap.Chain.makeUnsafe(~chainId), + chain: chainId, blockNumber, // Carries an `index` so the buffer's dedup key (blockNumber, logIndex, index) // resolves; the rest of the registration is unused by these tests. diff --git a/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res b/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res index 08128a31d9..1e6f3fe531 100644 --- a/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res +++ b/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res @@ -213,7 +213,7 @@ describe("EVM event decoding via EvmRpcClient.getLogs", () => { Internal.Event({ onEventRegistration: (MockIndexer.evmOnEventRegistration(~contractName="ERC20") :> Internal.onEventRegistration), - chain: ChainMap.Chain.makeUnsafe(~chainId=137->ChainId.fromInt), + chain: 137->ChainId.fromInt, blockNumber, logIndex, transactionIndex: 0, diff --git a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res index 2106efa9bd..a01bcffbd6 100644 --- a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res +++ b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res @@ -1049,7 +1049,7 @@ describe("ecosystem.toRawEvent", () => { Internal.Event({ onEventRegistration: (MockIndexer.evmOnEventRegistration(~contractName="ERC20") :> Internal.onEventRegistration), - chain: ChainMap.Chain.makeUnsafe(~chainId=137->ChainId.fromInt), + chain: 137->ChainId.fromInt, blockNumber, logIndex, transactionIndex: 0, diff --git a/scenarios/test_codegen/test/rollback/ChainMocking.res b/scenarios/test_codegen/test/rollback/ChainMocking.res index 62512b715c..87ac289e84 100644 --- a/scenarios/test_codegen/test/rollback/ChainMocking.res +++ b/scenarios/test_codegen/test/rollback/ChainMocking.res @@ -177,7 +177,7 @@ module Make = () => { let log = Internal.Event({ onEventRegistration: (onEventRegistration :> Internal.onEventRegistration), payload: makeEvent(~blockHash), - chain: ChainMap.Chain.makeUnsafe(~chainId=self.chainConfig.id), + chain: self.chainConfig.id, blockNumber, logIndex, transactionIndex, From cbba022b6cf079c9152e523f3772045b8b531e95 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 08:49:50 +0000 Subject: [PATCH 4/6] Drop dead chain-id API and settle on one name for a chain id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dead since the ChainId migration (or before it): `ChainId.toFloat`, `ChainId.equal` (`===` works on the opaque type and is what callers use), `ChainId.maxSafe`'s export, `ChainMap.set`/`entries`/`map`/`size`/ `update`, and `Utils.Dict.incrementByInt`. `ChainId.compare` now returns `int`, which is what Belt's `cmp` wants — the only caller was undoing a float. Naming: a `ChainId.t` is now called `chainId` everywhere internal. `Internal.item` spelled its field `chain` while `onBlockRegistration` in the same file spelled it `chainId`, and `getItemChainId` existed to bridge the two; sources, IndexerState, ChainFetching and CrossChainState each picked their own. `IndexerState.chain`, an alias for `ChainId.t`, is gone. `context.chain` and `Config.chain` are untouched — the first is the handler-facing API, the second is a record, not an id. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ECqzHdVS4cLxfCXPw8zpb6 --- .../test/ClientAddressFilter_test.res | 4 +- packages/envio-tests/test/RateLimit_test.res | 4 +- .../test/SvmHyperSyncSource_test.res | 4 +- .../test/lib_tests/ChainIdMode_test.res | 4 +- packages/envio/src/Batch.res | 2 +- packages/envio/src/ChainFetching.res | 30 ++++----- packages/envio/src/ChainId.res | 4 +- packages/envio/src/ChainId.resi | 6 +- packages/envio/src/ChainMap.res | 16 ++--- packages/envio/src/ChainMap.resi | 5 -- packages/envio/src/ChainState.res | 12 ++-- .../envio/src/ContractRegisterContext.res | 2 +- packages/envio/src/CrossChainState.res | 4 +- packages/envio/src/CrossChainState.resi | 2 +- packages/envio/src/IndexerLoop.res | 4 +- packages/envio/src/IndexerState.res | 31 +++++---- packages/envio/src/IndexerState.resi | 11 ++-- packages/envio/src/Internal.res | 6 +- packages/envio/src/Main.res | 3 +- packages/envio/src/RawEvent.res | 4 +- packages/envio/src/Rollback.res | 20 +++--- .../envio/src/SimulateDeadInputTracker.res | 4 +- packages/envio/src/SimulateItems.res | 18 ++--- packages/envio/src/Utils.res | 4 -- packages/envio/src/sources/Evm.res | 2 +- packages/envio/src/sources/EvmChain.res | 6 +- .../envio/src/sources/EvmHyperSyncSource.res | 14 ++-- packages/envio/src/sources/Fuel.res | 2 +- .../envio/src/sources/FuelHyperSyncSource.res | 12 ++-- packages/envio/src/sources/RpcSource.res | 26 ++++---- packages/envio/src/sources/SimulateSource.res | 4 +- packages/envio/src/sources/Source.res | 2 +- packages/envio/src/sources/SourceManager.res | 18 ++--- packages/envio/src/sources/Svm.res | 8 +-- .../envio/src/sources/SvmHyperSyncSource.res | 8 +-- .../test/FuelHyperSyncSourceHeight_test.res | 6 +- .../test/BelowHeadPollingPin_test.res | 4 +- scenarios/test_codegen/test/BlockLag_test.res | 2 +- .../test/ClientFilterDedup_test.res | 2 +- .../test/ConcurrentWrite_test.res | 2 +- scenarios/test_codegen/test/E2E_test.res | 54 +++++++-------- .../test/EnterReorgThreshold_test.res | 6 +- .../test/EntityColumnTypes_test.res | 2 +- .../test/IndexerStateStall_test.res | 2 +- .../test_codegen/test/IndexerState_test.res | 24 +++---- .../test/RawEventsTableMigration_test.res | 4 +- .../test/RpcSourceContract_test.res | 8 +-- .../test_codegen/test/RpcSource_test.res | 24 +++---- .../test/SourceBlockHashes_test.res | 6 +- .../test_codegen/test/StalledPolling_test.res | 2 +- .../test_codegen/test/WriteRead_test.res | 8 +-- .../test/YamlConfigIndexer_test.res | 2 +- .../test/__mocks__/MockEvents.res | 4 +- .../test_codegen/test/helpers/MockIndexer.res | 16 ++--- .../test/helpers/RpcSourcePins.res | 4 +- .../test/lib_tests/CrossChainState_test.res | 66 +++++++++---------- .../DynamicContractsStartupSize_test.res | 2 +- .../test/lib_tests/EntityIdType_test.res | 2 +- .../lib_tests/FetchState_onBlock_test.res | 2 +- .../test/lib_tests/FetchState_test.res | 2 +- .../test/lib_tests/HyperSyncDecoder_test.res | 2 +- .../test/lib_tests/IndexerLoop_test.res | 2 +- .../test/lib_tests/PgStorage_test.res | 2 +- .../test/rollback/ChainMocking.res | 2 +- .../test/rollback/Rollback_test.res | 56 ++++++++-------- .../test/schema_types/BigDecimal_test.res | 2 +- .../test/schema_types/Timestamp_test.res | 2 +- 67 files changed, 303 insertions(+), 327 deletions(-) diff --git a/packages/envio-tests/test/ClientAddressFilter_test.res b/packages/envio-tests/test/ClientAddressFilter_test.res index 4fb83c2a1d..ae15d8bdbe 100644 --- a/packages/envio-tests/test/ClientAddressFilter_test.res +++ b/packages/envio-tests/test/ClientAddressFilter_test.res @@ -189,7 +189,7 @@ describe("filterByClientAddress applies clientAddressFilter", () => { let onEventRegistration = (onEventRegistration :> Internal.onEventRegistration) let makeItem = (~to, ~blockNumber): Internal.item => Internal.Event({ - chain: 1->ChainId.fromInt, + chainId: 1->ChainId.fromInt, blockNumber, onEventRegistration, logIndex: 0, @@ -242,7 +242,7 @@ describe("filterByClientAddress drops over-fetched non-wildcard srcAddress event let onEventRegistration = (onEventRegistration :> Internal.onEventRegistration) let makeItem = (~srcAddress, ~blockNumber): Internal.item => Internal.Event({ - chain: 1->ChainId.fromInt, + chainId: 1->ChainId.fromInt, blockNumber, onEventRegistration, logIndex: 0, diff --git a/packages/envio-tests/test/RateLimit_test.res b/packages/envio-tests/test/RateLimit_test.res index e25b91f150..7b7172f3ee 100644 --- a/packages/envio-tests/test/RateLimit_test.res +++ b/packages/envio-tests/test/RateLimit_test.res @@ -1,6 +1,6 @@ open Vitest -let chain = 1->ChainId.fromInt +let chainId = 1->ChainId.fromInt // Mock source that throws Source.RateLimited on the first N calls, then // returns Ok with the requested block data. Lets us exercise @@ -11,7 +11,7 @@ let makeMockSource = (~rateLimitedCalls: int, ~resetMs: int): Source.t => { { name: "MockHyperSync", sourceFor: Sync, - chain, + chainId, poweredByHyperSync: true, pollingInterval: 100, getBlockHashes: (~blockNumbers, ~logger as _) => { diff --git a/packages/envio-tests/test/SvmHyperSyncSource_test.res b/packages/envio-tests/test/SvmHyperSyncSource_test.res index b7a24d7858..bcd3e8113f 100644 --- a/packages/envio-tests/test/SvmHyperSyncSource_test.res +++ b/packages/envio-tests/test/SvmHyperSyncSource_test.res @@ -11,7 +11,7 @@ open Vitest // logIndex, and Rust-decoded params parsed from JSON strings. let metaplexProgramId = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" -let chain = 0->ChainId.fromInt +let chainId = 0->ChainId.fromInt let blockTime = 1778064393 let slot = 417950033 @@ -134,7 +134,7 @@ let makeSource = (~onEventRegistrations=[makeReg()], ~client=mockClient) => { }->(Utils.magic: {..} => Core.addon), ) let source = try SvmHyperSyncSource.make({ - chain, + chainId, endpointUrl: "https://solana.hypersync.xyz", apiToken: None, onEventRegistrations, diff --git a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res index d80a8c7480..130ab1d799 100644 --- a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res +++ b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res @@ -168,8 +168,8 @@ describe("ChainId runtime representation", () => { "3448148188"->ChainId.normalizeOrThrow->ChainId.toString, 3448148188.->ChainId.normalizeOrThrow->ChainId.toString, ChainId.compare("1"->ChainId.normalizeOrThrow, "2147483648"->ChainId.normalizeOrThrow), - ChainId.equal("42"->ChainId.normalizeOrThrow, 42->ChainId.fromInt), - )).toEqual(("3448148188", "3448148188", -1., true)) + "42"->ChainId.normalizeOrThrow === 42->ChainId.fromInt, + )).toEqual(("3448148188", "3448148188", -1, true)) }) it("rejects values that can't be a chain id", t => { diff --git a/packages/envio/src/Batch.res b/packages/envio/src/Batch.res index 2ca36ef71f..323c38b7a0 100644 --- a/packages/envio/src/Batch.res +++ b/packages/envio/src/Batch.res @@ -325,7 +325,7 @@ let findLastEventItem = (batch: t, ~chainId) => { switch item { | Internal.Event(_) as eventItem => { let eventItem = eventItem->Internal.castUnsafeEventItem - if eventItem.chain === chainId { + if eventItem.chainId === chainId { result := Some(eventItem) } else { idx := idx.contents - 1 diff --git a/packages/envio/src/ChainFetching.res b/packages/envio/src/ChainFetching.res index 250b5295ca..2be270e8af 100644 --- a/packages/envio/src/ChainFetching.res +++ b/packages/envio/src/ChainFetching.res @@ -3,7 +3,7 @@ // (state + transitions) and leaf effect modules. type partitionQueryResponse = { - chain: IndexerState.chain, + chainId: ChainId.t, response: Source.blockRangeFetchResponse, query: FetchState.query, } @@ -106,7 +106,7 @@ let runContractRegistersOrThrow = async ( let rec onQueryResponse = async ( state: IndexerState.t, - {chain, response, query}: partitionQueryResponse, + {chainId, response, query}: partitionQueryResponse, ~stateId, ~scheduleFetch, ~scheduleProcessing, @@ -115,7 +115,7 @@ let rec onQueryResponse = async ( if state->IndexerState.isStale(~stateId) { () } else { - let chainState = state->IndexerState.getChainState(~chain) + let chainState = state->IndexerState.getChainState(~chainId) let { parsedQueueItems, transactionStore, @@ -142,7 +142,7 @@ let rec onQueryResponse = async ( if numContractRegisterEvents === 0 { Logging.trace({ "msg": "Finished querying", - "chainId": chain, + "chainId": chainId, "partitionId": query.partitionId, "fromBlock": fromBlockQueried, "toBlock": latestFetchedBlockNumber, @@ -151,7 +151,7 @@ let rec onQueryResponse = async ( } else { Logging.trace({ "msg": "Finished querying", - "chainId": chain, + "chainId": chainId, "partitionId": query.partitionId, "fromBlock": fromBlockQueried, "toBlock": latestFetchedBlockNumber, @@ -204,7 +204,7 @@ let rec onQueryResponse = async ( }, ) ) - state->IndexerState.beginReorg(~chain, ~blockNumber=reorgDetectedBlockNumber) + state->IndexerState.beginReorg(~chainId, ~blockNumber=reorgDetectedBlockNumber) // Advances synchronously to FindingReorgDepth, so a concurrent rollback // kick (eg from the processing loop quiescing) collapses into this one. scheduleRollback() @@ -229,7 +229,7 @@ let rec onQueryResponse = async ( if !(state->IndexerState.isStale(~stateId)) { applyQueryResponse( state, - ~chain, + ~chainId, ~newItems, ~newItemsWithDcs, ~knownHeight, @@ -264,7 +264,7 @@ let rec onQueryResponse = async ( and applyQueryResponse = ( state: IndexerState.t, - ~chain, + ~chainId, ~newItems, ~newItemsWithDcs, ~knownHeight, @@ -273,7 +273,7 @@ and applyQueryResponse = ( ~transactionStore, ~blockStore, ) => { - let chainState = state->IndexerState.getChainState(~chain) + let chainState = state->IndexerState.getChainState(~chainId) let wasFetchingAtHead = chainState->ChainState.isFetchingAtHead chainState->ChainState.handleQueryResult( @@ -307,7 +307,7 @@ and applyQueryResponse = ( let finishWaitingForNewBlock = ( state: IndexerState.t, - ~chain, + ~chainId, ~knownHeight, ~stateId, ~scheduleFetch, @@ -316,7 +316,7 @@ let finishWaitingForNewBlock = ( if state->IndexerState.isStale(~stateId) { () } else { - let chainState = state->IndexerState.getChainState(~chain) + let chainState = state->IndexerState.getChainState(~chainId) chainState->ChainState.updateKnownHeight(~knownHeight) // No reorg-threshold check here: scheduleProcessing always runs at least one @@ -327,14 +327,14 @@ let finishWaitingForNewBlock = ( let fetchChain = async ( state: IndexerState.t, - chain, + chainId, ~action, ~stateId, ~scheduleFetch, ~scheduleProcessing, ~scheduleRollback, ) => { - let chainState = state->IndexerState.getChainState(~chain) + let chainState = state->IndexerState.getChainState(~chainId) if !(state->IndexerState.isResolvingReorg) && !(state->IndexerState.isStopped) { let isRealtime = state->IndexerState.isRealtime let sourceManager = chainState->ChainState.sourceManager @@ -352,7 +352,7 @@ let fetchChain = async ( ~onNewBlock=(~knownHeight) => finishWaitingForNewBlock( state, - ~chain, + ~chainId, ~knownHeight, ~stateId, ~scheduleFetch, @@ -370,7 +370,7 @@ let fetchChain = async ( ) await onQueryResponse( state, - {chain, response, query}, + {chainId, response, query}, ~stateId, ~scheduleFetch, ~scheduleProcessing, diff --git a/packages/envio/src/ChainId.res b/packages/envio/src/ChainId.res index eb713c9f7f..2fa7f05152 100644 --- a/packages/envio/src/ChainId.res +++ b/packages/envio/src/ChainId.res @@ -22,11 +22,9 @@ let maxSafe = 9007199254740991. // tests. Both are the identity at runtime — an `int` is already a JS number. external fromInt: int => t = "%identity" external toInt: t => int = "%identity" -external toFloat: t => float = "%identity" let toString = (chainId: t) => chainId->Float.toString -let compare = (a: t, b: t) => a < b ? -1. : a > b ? 1. : 0. -let equal = (a: t, b: t) => a === b +let compare = (a: t, b: t) => a < b ? -1 : a > b ? 1 : 0 // PostgreSQL BIGINT and ClickHouse UInt64 columns come back as strings (a // JS number can't hold their full range), so the parser accepts both and diff --git a/packages/envio/src/ChainId.resi b/packages/envio/src/ChainId.resi index 95ab125cd3..a632f0258d 100644 --- a/packages/envio/src/ChainId.resi +++ b/packages/envio/src/ChainId.resi @@ -4,18 +4,14 @@ type mode = | @as("int32") Int32 | @as("int64") Int64 let modeSchema: S.t -let maxSafe: float - // Escapes for the boundaries that stay `int`: the handler-facing // `context.chain.id` / `Envio.effectChain.id`, and int literals in configs and // tests. Both are the identity at runtime — an `int` is already a JS number. external fromInt: int => t = "%identity" external toInt: t => int = "%identity" -external toFloat: t => float = "%identity" let toString: t => string -let compare: (t, t) => float -let equal: (t, t) => bool +let compare: (t, t) => int let schema: S.t diff --git a/packages/envio/src/ChainMap.res b/packages/envio/src/ChainMap.res index b8e39fc704..7a6b556398 100644 --- a/packages/envio/src/ChainMap.res +++ b/packages/envio/src/ChainMap.res @@ -1,6 +1,6 @@ module ChainIdCmp = Belt.Id.MakeComparable({ type t = ChainId.t - let cmp = (a, b) => ChainId.compare(a, b)->Int.fromFloat + let cmp = ChainId.compare }) type t<'a> = Belt.Map.t @@ -9,24 +9,18 @@ let fromArrayUnsafe: array<(ChainId.t, 'a)> => t<'a> = arr => { arr->Belt.Map.fromArray(~id=module(ChainIdCmp)) } -let get: (t<'a>, ChainId.t) => 'a = (self, chain) => - switch Belt.Map.get(self, chain) { +let get: (t<'a>, ChainId.t) => 'a = (self, chainId) => + switch Belt.Map.get(self, chainId) { | Some(v) => v | None => // Should be unreachable, since we validate chain ids when parsing the config. // Still throw just in case something went wrong JsError.throwWithMessage( - "No chain with id " ++ chain->ChainId.toString ++ " found in chain map", + "No chain with id " ++ chainId->ChainId.toString ++ " found in chain map", ) } -let set: (t<'a>, ChainId.t, 'a) => t<'a> = (map, chain, v) => Belt.Map.set(map, chain, v) let values: t<'a> => array<'a> = map => Belt.Map.valuesToArray(map) let keys: t<'a> => array = map => Belt.Map.keysToArray(map) -let entries: t<'a> => array<(ChainId.t, 'a)> = map => Belt.Map.toArray(map) -let has: (t<'a>, ChainId.t) => bool = (map, chain) => Belt.Map.has(map, chain) -let map: (t<'a>, 'a => 'b) => t<'b> = (map, fn) => Belt.Map.map(map, fn) +let has: (t<'a>, ChainId.t) => bool = (map, chainId) => Belt.Map.has(map, chainId) let mapWithKey: (t<'a>, (ChainId.t, 'a) => 'b) => t<'b> = (map, fn) => Belt.Map.mapWithKey(map, fn) -let size: t<'a> => int = map => Belt.Map.size(map) -let update: (t<'a>, ChainId.t, 'a => 'a) => t<'a> = (map, chain, updateFn) => - Belt.Map.update(map, chain, opt => opt->Option.map(updateFn)) diff --git a/packages/envio/src/ChainMap.resi b/packages/envio/src/ChainMap.resi index 9393326fcb..d294875a79 100644 --- a/packages/envio/src/ChainMap.resi +++ b/packages/envio/src/ChainMap.resi @@ -1,12 +1,7 @@ type t<'a> let fromArrayUnsafe: array<(ChainId.t, 'a)> => t<'a> let get: (t<'a>, ChainId.t) => 'a -let set: (t<'a>, ChainId.t, 'a) => t<'a> let values: t<'a> => array<'a> let keys: t<'a> => array -let entries: t<'a> => array<(ChainId.t, 'a)> let has: (t<'a>, ChainId.t) => bool -let map: (t<'a>, 'a => 'b) => t<'b> let mapWithKey: (t<'a>, (ChainId.t, 'a) => 'b) => t<'b> -let size: t<'a> => int -let update: (t<'a>, ChainId.t, 'a => 'a) => t<'a> diff --git a/packages/envio/src/ChainState.res b/packages/envio/src/ChainState.res index d47969ce8c..da37351b6a 100644 --- a/packages/envio/src/ChainState.res +++ b/packages/envio/src/ChainState.res @@ -204,7 +204,7 @@ let makeInternal = ( }) // Create sources lazily here - this is where API token validation happens - let chain = chainConfig.id + let chainId = chainConfig.id let lowercaseAddresses = config.lowercaseAddresses let sources = switch chainConfig.sourceConfig { | Config.EvmSourceConfig({hypersync, rpcs}) => @@ -221,7 +221,7 @@ let makeInternal = ( } }) EvmChain.makeSources( - ~chain, + ~chainId, ~onEventRegistrations=onEventRegistrations->( Utils.magic: array => array ), @@ -231,7 +231,7 @@ let makeInternal = ( ) | Config.FuelSourceConfig({hypersync}) => [ FuelHyperSyncSource.make({ - chain, + chainId, endpointUrl: hypersync, apiToken: Env.envioApiToken, onEventRegistrations, @@ -241,16 +241,16 @@ let makeInternal = ( switch (hypersync, rpc) { | (None, None) => JsError.throwWithMessage( - `Chain ${chain->ChainId.toString} has no SVM data source`, + `Chain ${chainId->ChainId.toString} has no SVM data source`, ) - | (None, Some(rpc)) => [Svm.makeRPCSource(~chain, ~rpc)] + | (None, Some(rpc)) => [Svm.makeRPCSource(~chainId, ~rpc)] | (Some(hypersyncUrl), _) => // HyperSync drives instruction sync. A configured RPC is ignored for now // (RPC fallback isn't wired up yet). let apiToken = Env.envioApiToken [ SvmHyperSyncSource.make({ - chain, + chainId, endpointUrl: hypersyncUrl, apiToken, onEventRegistrations, diff --git a/packages/envio/src/ContractRegisterContext.res b/packages/envio/src/ContractRegisterContext.res index 3657cdce88..be088be9c1 100644 --- a/packages/envio/src/ContractRegisterContext.res +++ b/packages/envio/src/ContractRegisterContext.res @@ -36,7 +36,7 @@ let contractRegisterChainTraps: Utils.Proxy.traps = { switch prop { | "id" => let eventItem = params.item->Internal.castUnsafeEventItem - eventItem.chain->(Utils.magic: ChainId.t => unknown) + eventItem.chainId->(Utils.magic: ChainId.t => unknown) | _ => // Look up the contract name directly in config contracts across all chains. let contractName = prop diff --git a/packages/envio/src/CrossChainState.res b/packages/envio/src/CrossChainState.res index a1cfc08e59..d7085de64b 100644 --- a/packages/envio/src/CrossChainState.res +++ b/packages/envio/src/CrossChainState.res @@ -226,7 +226,7 @@ let idleOrWaitAction = (cs: ChainState.t) => // dropped — chains at head only trail each other by real-time block production. let checkAndFetch = async ( crossChainState: t, - ~dispatchChain: (~chain: ChainId.t, ~action: FetchState.nextQuery) => promise, + ~dispatchChain: (~chainId: ChainId.t, ~action: FetchState.nextQuery) => promise, ) => { let targetBudget = crossChainState.targetBufferSize->Int.toFloat let remaining = ref( @@ -342,7 +342,7 @@ let checkAndFetch = async ( switch actionByChain->ChainId.Dict.dangerouslyGetNonOption(chainId) { | Some(NothingToQuery) | None => () - | Some(action) => promises->Array.push(dispatchChain(~chain=chainId, ~action)) + | Some(action) => promises->Array.push(dispatchChain(~chainId=chainId, ~action)) } } let _ = await promises->Promise.all diff --git a/packages/envio/src/CrossChainState.resi b/packages/envio/src/CrossChainState.resi index 6887208604..f2b5aba6d7 100644 --- a/packages/envio/src/CrossChainState.resi +++ b/packages/envio/src/CrossChainState.resi @@ -36,5 +36,5 @@ let applyBatchProgress: (t, ~batch: Batch.t, ~blockTimestampName: string) => uni let priorityOrder: t => array let checkAndFetch: ( t, - ~dispatchChain: (~chain: ChainId.t, ~action: FetchState.nextQuery) => promise, + ~dispatchChain: (~chainId: ChainId.t, ~action: FetchState.nextQuery) => promise, ) => promise diff --git a/packages/envio/src/IndexerLoop.res b/packages/envio/src/IndexerLoop.res index 295ed79a67..592ad703aa 100644 --- a/packages/envio/src/IndexerLoop.res +++ b/packages/envio/src/IndexerLoop.res @@ -19,10 +19,10 @@ let start = (state: IndexerState.t) => { launch(state, () => state ->IndexerState.crossChainState - ->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => + ->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => ChainFetching.fetchChain( state, - chain, + chainId, ~action, ~stateId=state->IndexerState.epoch, ~scheduleFetch, diff --git a/packages/envio/src/IndexerState.res b/packages/envio/src/IndexerState.res index 9b78000e81..93f63b5026 100644 --- a/packages/envio/src/IndexerState.res +++ b/packages/envio/src/IndexerState.res @@ -1,9 +1,8 @@ -type chain = ChainId.t type rollbackState = | NoRollback - | ReorgDetected({chain: chain, blockNumber: int}) + | ReorgDetected({chainId: ChainId.t, blockNumber: int}) | FindingReorgDepth - | FoundReorgDepth({chain: chain, rollbackTargetBlockNumber: int}) + | FoundReorgDepth({chainId: ChainId.t, rollbackTargetBlockNumber: int}) | RollbackReady({eventsProcessedDiffByChain: dict}) module EntityTables = { @@ -256,11 +255,11 @@ let makeFromDbState = ( false } else { // Check if any chain is in reorg threshold by comparing progress with sourceBlock - maxReorgDepth. - initialState.chains->Array.some(chain => + initialState.chains->Array.some(resumedChainState => isProgressInReorgThreshold( - ~progressBlockNumber=chain.progressBlockNumber, - ~sourceBlockNumber=chain.sourceBlockNumber, - ~maxReorgDepth=chain.maxReorgDepth, + ~progressBlockNumber=resumedChainState.progressBlockNumber, + ~sourceBlockNumber=resumedChainState.sourceBlockNumber, + ~maxReorgDepth=resumedChainState.maxReorgDepth, ) ) } @@ -274,8 +273,8 @@ let makeFromDbState = ( let chainStates = Dict.make() initialState.chains->Array.forEach((resumedChainState: Persistence.initialChainState) => { - let chain = Config.getChain(config, ~chainId=resumedChainState.id) - let chainConfig = config.chainMap->ChainMap.get(chain) + let chainId = Config.getChain(config, ~chainId=resumedChainState.id) + let chainConfig = config.chainMap->ChainMap.get(chainId) chainStates->ChainId.Dict.set( resumedChainState.id, chainConfig->ChainState.makeFromDbState( @@ -358,15 +357,15 @@ let stop = (state: t) => { state.isStopped = true } -let getChainState = (state: t, ~chain: chain): ChainState.t => +let getChainState = (state: t, ~chainId: ChainId.t): ChainState.t => switch state.crossChainState ->CrossChainState.chainStates - ->ChainId.Dict.dangerouslyGetNonOption(chain) { + ->ChainId.Dict.dangerouslyGetNonOption(chainId) { | Some(cs) => cs | None => // Should be unreachable: every configured chain gets a state at startup JsError.throwWithMessage( - "No chain with id " ++ chain->ChainId.toString ++ " found in chain states", + "No chain with id " ++ chainId->ChainId.toString ++ " found in chain states", ) } @@ -390,18 +389,18 @@ let enterReorgThreshold = (state: t) => state.crossChainState->CrossChainState.e // ReorgDetected state as one step, so the epoch bump can never be left out. The // caller has already mutated the chain states (restored counters, reset pending // queries). isResolvingReorg derives from rollbackState. -let beginReorg = (state: t, ~chain, ~blockNumber) => { +let beginReorg = (state: t, ~chainId, ~blockNumber) => { // Settle here, or the rollback that follows would be folded into the stall on // the next beginProcessing — time envio_rollback_seconds already counts. state->settleStalledOnFetch state.epoch = state.epoch + 1 - state.rollbackState = ReorgDetected({chain, blockNumber}) + state.rollbackState = ReorgDetected({chainId, blockNumber}) } let enterFindingReorgDepth = (state: t) => state.rollbackState = FindingReorgDepth -let foundReorgDepth = (state: t, ~chain, ~rollbackTargetBlockNumber) => - state.rollbackState = FoundReorgDepth({chain, rollbackTargetBlockNumber}) +let foundReorgDepth = (state: t, ~chainId, ~rollbackTargetBlockNumber) => + state.rollbackState = FoundReorgDepth({chainId, rollbackTargetBlockNumber}) // Finish a rollback. The caller has already rolled the chain states back in // place; this leaves the diff ready for the next batch to consume. diff --git a/packages/envio/src/IndexerState.resi b/packages/envio/src/IndexerState.resi index 17e3669649..83e0c95c0c 100644 --- a/packages/envio/src/IndexerState.resi +++ b/packages/envio/src/IndexerState.resi @@ -1,13 +1,12 @@ // The indexer state. `t` is opaque: other modules read it through the accessors // and change it only through the transitions and setters exposed here. -type chain = ChainId.t type rollbackState = | NoRollback - | ReorgDetected({chain: chain, blockNumber: int}) + | ReorgDetected({chainId: ChainId.t, blockNumber: int}) | FindingReorgDepth - | FoundReorgDepth({chain: chain, rollbackTargetBlockNumber: int}) + | FoundReorgDepth({chainId: ChainId.t, rollbackTargetBlockNumber: int}) | RollbackReady({eventsProcessedDiffByChain: dict}) module EntityTables: { @@ -54,11 +53,11 @@ let isStale: (t, ~stateId: int) => bool let isResolvingReorg: t => bool let errorExit: (t, ErrorHandling.t) => unit let stop: t => unit -let getChainState: (t, ~chain: chain) => ChainState.t +let getChainState: (t, ~chainId: ChainId.t) => ChainState.t let enterReorgThreshold: t => unit -let beginReorg: (t, ~chain: chain, ~blockNumber: int) => unit +let beginReorg: (t, ~chainId: ChainId.t, ~blockNumber: int) => unit let enterFindingReorgDepth: t => unit -let foundReorgDepth: (t, ~chain: chain, ~rollbackTargetBlockNumber: int) => unit +let foundReorgDepth: (t, ~chainId: ChainId.t, ~rollbackTargetBlockNumber: int) => unit let completeRollback: (t, ~eventsProcessedDiffByChain: dict) => unit let clearRollback: t => unit let invalidateInflight: t => unit diff --git a/packages/envio/src/Internal.res b/packages/envio/src/Internal.res index 2fc6a20b11..f430612060 100644 --- a/packages/envio/src/Internal.res +++ b/packages/envio/src/Internal.res @@ -616,7 +616,7 @@ type dcs = array type eventItem = private { kind: [#0], onEventRegistration: onEventRegistration, - chain: ChainId.t, + chainId: ChainId.t, blockNumber: int, logIndex: int, // Within-block transaction index — the key into the per-chain transaction @@ -669,7 +669,7 @@ type item = | @as(0) Event({ onEventRegistration: onEventRegistration, - chain: ChainId.t, + chainId: ChainId.t, blockNumber: int, logIndex: int, transactionIndex: int, @@ -686,7 +686,7 @@ external getItemLogIndex: item => int = "logIndex" let getItemChainId = item => switch item { - | Event({chain}) => chain + | Event({chainId}) | Block({onBlockRegistration: {chainId}}) => chainId } diff --git a/packages/envio/src/Main.res b/packages/envio/src/Main.res index d005f58af1..c7e20539ec 100644 --- a/packages/envio/src/Main.res +++ b/packages/envio/src/Main.res @@ -183,8 +183,7 @@ let buildChainsObject = (~config: Config.t) => { get: () => { switch getIndexerState() { | Some(state) => { - let chain = chainConfig.id - let chainState = state->IndexerState.getChainState(~chain) + let chainState = state->IndexerState.getChainState(~chainId=chainConfig.id) chainState->ChainState.contractAddresses(~contractName=contract.name) } // Before the global state is available (eg during handler diff --git a/packages/envio/src/RawEvent.res b/packages/envio/src/RawEvent.res index 0f6147b5c3..795d184c6b 100644 --- a/packages/envio/src/RawEvent.res +++ b/packages/envio/src/RawEvent.res @@ -32,7 +32,7 @@ let make = ( ~blockTimestamp: int, ~cleanUpRawEventFieldsInPlace: JSON.t => unit, ): Internal.rawEvent => { - let {chain, blockNumber, logIndex} = eventItem + let {chainId, blockNumber, logIndex} = eventItem let eventConfig = eventItem.onEventRegistration.eventConfig let eventId = EventUtils.packEventIndex(~logIndex, ~blockNumber) let blockFields = @@ -61,7 +61,7 @@ let make = ( } { - chain_id: chain, + chain_id: chainId, event_id: eventId, event_name: eventConfig.name, contract_name: eventConfig.contractName, diff --git a/packages/envio/src/Rollback.res b/packages/envio/src/Rollback.res index acbd2b77dd..d8eb7ad553 100644 --- a/packages/envio/src/Rollback.res +++ b/packages/envio/src/Rollback.res @@ -50,8 +50,8 @@ let rec rollback = async ( switch state->IndexerState.rollbackState { | NoRollback | RollbackReady(_) => JsError.throwWithMessage("Internal error: Rollback initiated with invalid state") - | ReorgDetected({chain, blockNumber: reorgBlockNumber}) => - let chainState = state->IndexerState.getChainState(~chain) + | ReorgDetected({chainId, blockNumber: reorgBlockNumber}) => + let chainState = state->IndexerState.getChainState(~chainId) state->IndexerState.enterFindingReorgDepth let rollbackTargetBlockNumber = await chainState->getLastKnownValidBlock( @@ -63,7 +63,7 @@ let rec rollback = async ( ->ChainState.sourceManager ->SourceManager.onReorg(~rollbackTargetBlock=rollbackTargetBlockNumber) - state->IndexerState.foundReorgDepth(~chain, ~rollbackTargetBlockNumber) + state->IndexerState.foundReorgDepth(~chainId, ~rollbackTargetBlockNumber) // Rendezvous with the processing loop: whichever of {depth found, loop // idle} happens last triggers the rollback; the earlier one finds the // other condition unmet and bails here. @@ -73,7 +73,7 @@ let rec rollback = async ( | FindingReorgDepth => () | FoundReorgDepth(_) if state->IndexerState.isProcessing => Logging.trace("Waiting for batch to finish processing before executing rollback") - | FoundReorgDepth({chain: reorgChain, rollbackTargetBlockNumber}) => + | FoundReorgDepth({chainId: reorgChain, rollbackTargetBlockNumber}) => await executeRollback( state, ~reorgChain, @@ -108,7 +108,7 @@ and executeRollback = async ( ) logger->Logging.childInfo("Started rollback on reorg") state - ->IndexerState.getChainState(~chain=reorgChain) + ->IndexerState.getChainState(~chainId=reorgChain) ->ChainState.setRollbackTargetBlock(~blockNumber=rollbackTargetBlockNumber) // Finish pending batch writes first: the target checkpoint, the progress @@ -195,13 +195,13 @@ and executeRollback = async ( ~progressBlockNumberByChainId=newProgressBlockNumberPerChain, ) - rolledBackChains->Array.forEach(chain => { + rolledBackChains->Array.forEach(rolledBack => { logger->Logging.childInfo({ "msg": "Rollbacked", - "chainId": chain["chainId"], - "fromBlock": chain["fromBlock"], - "toBlock": chain["toBlock"], - "rollbackedEvents": chain["rollbackedEvents"], + "chainId": rolledBack["chainId"], + "fromBlock": rolledBack["fromBlock"], + "toBlock": rolledBack["toBlock"], + "rollbackedEvents": rolledBack["rollbackedEvents"], }) }) logger->Logging.childTrace({ diff --git a/packages/envio/src/SimulateDeadInputTracker.res b/packages/envio/src/SimulateDeadInputTracker.res index 5f6d43c561..4ac07b5fc0 100644 --- a/packages/envio/src/SimulateDeadInputTracker.res +++ b/packages/envio/src/SimulateDeadInputTracker.res @@ -6,8 +6,8 @@ // transform of the item between the source and the batch. let itemKey = (item: Internal.item): string => switch item { - | Internal.Event({chain, blockNumber, logIndex}) => - `${chain + | Internal.Event({chainId, blockNumber, logIndex}) => + `${chainId ->ChainId.toString}:${blockNumber->Int.toString}:${logIndex->Int.toString}` | Internal.Block(_) => "" } diff --git a/packages/envio/src/SimulateItems.res b/packages/envio/src/SimulateItems.res index 1b9d2c21c0..e2f710de51 100644 --- a/packages/envio/src/SimulateItems.res +++ b/packages/envio/src/SimulateItems.res @@ -246,7 +246,7 @@ let parse = ( ~chainConfig: Config.chain, ~onEventRegistrations: array, ): array => { - let chain = chainConfig.id + let chainId = chainConfig.id let startBlock = chainConfig.startBlock let currentBlock = ref(startBlock) let currentLogIndex = ref(0) @@ -333,7 +333,7 @@ let parse = ( switch seenCoordinates->Dict.get(coordinate) { | Some(firstIndex) => JsError.throwWithMessage( - `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chain->ChainId.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`, + `simulate: items at index ${firstIndex->Int.toString} and ${itemIndex->Int.toString} on chain ${chainId->ChainId.toString} both resolve to block ${blockNumber->Int.toString}, logIndex ${logIndex->Int.toString}. Give each item a distinct logIndex (or omit logIndex so they auto-increment).`, ) | None => seenCoordinates->Dict.set(coordinate, itemIndex) } @@ -350,7 +350,7 @@ let parse = ( let liveRegistrations = HandlerRegister.getSimulateOnEventRegistrations( ~config, - ~chainId=chain, + ~chainId=chainId, ~eventConfig, )->Array.filter(reg => (reg.handler->Option.isSome || reg.contractRegister->Option.isSome) && @@ -362,7 +362,7 @@ let parse = ( ->ChainMap.values ->Array.length { | 1 => "" - | _ => ` on chain ${chain->ChainId.toString}` + | _ => ` on chain ${chainId->ChainId.toString}` }}. Register a handler with indexer.onEvent (and check any \`where\` filter isn't excluding this chain) before simulating it.`, ) } @@ -377,7 +377,7 @@ let parse = ( ->Array.push( Internal.Event({ onEventRegistration, - chain, + chainId, blockNumber, logIndex, // Simulate keeps the transaction inline on the payload, so the store @@ -388,7 +388,7 @@ let parse = ( contractName: eventConfig.contractName, eventName: eventConfig.name, params, - chainId: chain, + chainId: chainId, srcAddress, logIndex, transaction, @@ -419,8 +419,8 @@ let patchConfig = ( (processConfig->(Utils.magic: JSON.t => {..}))["chains"]->Nullable.toOption switch processChains { | Some(chainsDict) => - let newChainMap = config.chainMap->ChainMap.mapWithKey((chain, chainConfig) => { - let chainIdStr = chain->ChainId.toString + let newChainMap = config.chainMap->ChainMap.mapWithKey((chainId, chainConfig) => { + let chainIdStr = chainId->ChainId.toString switch chainsDict->Dict.get(chainIdStr) { | Some(processChainJson) => let raw = processChainJson->(Utils.magic: JSON.t => {..}) @@ -450,7 +450,7 @@ let patchConfig = ( ~chainConfig, ~onEventRegistrations=chainRegistrations.onEventRegistrations, ) - let source = SimulateSource.make(~items, ~endBlock, ~chain) + let source = SimulateSource.make(~items, ~endBlock, ~chainId) {...chainConfig, sourceConfig: Config.CustomSources([source])} | None => chainConfig } diff --git a/packages/envio/src/Utils.res b/packages/envio/src/Utils.res index b891405adb..0d2f4ea37b 100644 --- a/packages/envio/src/Utils.res +++ b/packages/envio/src/Utils.res @@ -218,10 +218,6 @@ module Dict = { @set_index external setByInt: (dict<'a>, int, 'a) => unit = "" - - let incrementByInt: (dict, int) => unit = %raw(`(dict, key) => { - dict[key]++ - }`) } module Math = { diff --git a/packages/envio/src/sources/Evm.res b/packages/envio/src/sources/Evm.res index d9b6e1a84a..a67147eef5 100644 --- a/packages/envio/src/sources/Evm.res +++ b/packages/envio/src/sources/Evm.res @@ -106,7 +106,7 @@ let make = (~logger: Pino.t): Ecosystem.t => { ~params={ "contract": eventItem.onEventRegistration.eventConfig.contractName, "event": eventItem.onEventRegistration.eventConfig.name, - "chainId": eventItem.chain, + "chainId": eventItem.chainId, "block": eventItem.blockNumber, "logIndex": eventItem.logIndex, "address": (eventItem.payload->toPayload).srcAddress, diff --git a/packages/envio/src/sources/EvmChain.res b/packages/envio/src/sources/EvmChain.res index 82a1c421f6..6a2c200ef0 100644 --- a/packages/envio/src/sources/EvmChain.res +++ b/packages/envio/src/sources/EvmChain.res @@ -40,7 +40,7 @@ let getSyncConfig = ( } let makeSources = ( - ~chain, + ~chainId, ~onEventRegistrations: array, ~hyperSync, ~rpcs: array, @@ -49,7 +49,7 @@ let makeSources = ( let sources = switch hyperSync { | Some(endpointUrl) => [ EvmHyperSyncSource.make({ - chain, + chainId, endpointUrl, onEventRegistrations, apiToken: Env.envioApiToken, @@ -64,7 +64,7 @@ let makeSources = ( } rpcs->Array.forEach(({?syncConfig, url, sourceFor, ?ws, ?headers}) => { let source = RpcSource.make({ - chain, + chainId, sourceFor, syncConfig: getSyncConfig(syncConfig->Option.getOr({})), url, diff --git a/packages/envio/src/sources/EvmHyperSyncSource.res b/packages/envio/src/sources/EvmHyperSyncSource.res index e46d8e16f5..30c1560bd1 100644 --- a/packages/envio/src/sources/EvmHyperSyncSource.res +++ b/packages/envio/src/sources/EvmHyperSyncSource.res @@ -6,7 +6,7 @@ open Source let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized") type options = { - chain: ChainId.t, + chainId: ChainId.t, endpointUrl: string, // The chain's registrations, indexed by their sequential `index`. onEventRegistrations: array, @@ -20,7 +20,7 @@ type options = { let make = ( { - chain, + chainId, endpointUrl, onEventRegistrations, apiToken, @@ -66,7 +66,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) Internal.Event({ onEventRegistration: (onEventRegistration :> Internal.onEventRegistration), - chain, + chainId, blockNumber: item.blockNumber, logIndex, transactionIndex, @@ -75,7 +75,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) payload: { contractName: onEventRegistration.eventConfig.contractName, eventName: onEventRegistration.eventConfig.name, - chainId: chain, + chainId: chainId, params: item.params, srcAddress, logIndex, @@ -250,7 +250,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) ~client, ~blockNumbers, ~sourceName=name, - ~chainId=chain, + ~chainId=chainId, ~logger, )->Promise.thenResolve(((queryRes, requestStats)) => { Source.result: queryRes->HyperSync.mapExn, @@ -260,7 +260,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) { name, sourceFor: Sync, - chain, + chainId, pollingInterval: 100, poweredByHyperSync: true, getBlockHashes, @@ -287,7 +287,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) HyperSyncHeightStream.subscribe( ~hyperSyncUrl=endpointUrl, ~apiToken, - ~chainId=chain, + ~chainId=chainId, ~onHeight, ), } diff --git a/packages/envio/src/sources/Fuel.res b/packages/envio/src/sources/Fuel.res index 093d4142c6..0618c1e2be 100644 --- a/packages/envio/src/sources/Fuel.res +++ b/packages/envio/src/sources/Fuel.res @@ -48,7 +48,7 @@ let make = (~logger: Pino.t): Ecosystem.t => { ~params={ "contract": eventItem.onEventRegistration.eventConfig.contractName, "event": eventItem.onEventRegistration.eventConfig.name, - "chainId": eventItem.chain, + "chainId": eventItem.chainId, "block": eventItem.blockNumber, "logIndex": eventItem.logIndex, "address": (eventItem.payload->toPayload).srcAddress, diff --git a/packages/envio/src/sources/FuelHyperSyncSource.res b/packages/envio/src/sources/FuelHyperSyncSource.res index e62135916c..646d15c0e2 100644 --- a/packages/envio/src/sources/FuelHyperSyncSource.res +++ b/packages/envio/src/sources/FuelHyperSyncSource.res @@ -3,14 +3,14 @@ open Source let isUnauthorizedError = (message: string) => message->String.includes("401 Unauthorized") type options = { - chain: ChainId.t, + chainId: ChainId.t, endpointUrl: string, apiToken: option, // The chain's registrations, indexed by their sequential `index`. onEventRegistrations: array, } -let make = ({chain, endpointUrl, apiToken, onEventRegistrations}: options): t => { +let make = ({chainId, endpointUrl, apiToken, onEventRegistrations}: options): t => { let name = "HyperFuel" let apiToken = switch apiToken { @@ -136,7 +136,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) try decode(data) catch { | exn => { let params = { - "chainId": chain, + "chainId": chainId, "blockNumber": item.blockHeight, "logIndex": item.receiptIndex, } @@ -166,7 +166,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) Internal.Event({ onEventRegistration, - chain, + chainId, blockNumber: item.blockHeight, logIndex: item.receiptIndex, // Fuel carries the transaction inline on the payload; the store key is @@ -175,7 +175,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) payload: { contractName: eventConfig.contractName, eventName: eventConfig.name, - chainId: chain, + chainId: chainId, params, transaction: { "id": item.txId, @@ -230,7 +230,7 @@ Learn more or get a free Envio API token at: https://envio.dev/app/api-tokens`) { name, sourceFor: Sync, - chain, + chainId, getBlockHashes, pollingInterval: 100, poweredByHyperSync: true, diff --git a/packages/envio/src/sources/RpcSource.res b/packages/envio/src/sources/RpcSource.res index cb8164d282..dd553e5a37 100644 --- a/packages/envio/src/sources/RpcSource.res +++ b/packages/envio/src/sources/RpcSource.res @@ -43,7 +43,7 @@ let parseBlockInfo = (json: JSON.t): blockInfo => { let getKnownRawBlockWithBackoff = async ( ~client, ~sourceName, - ~chain, + ~chainId, ~blockNumber, ~backoffMsOnFailure, ~recordRequest: (~method: string, ~seconds: float) => unit, @@ -60,7 +60,7 @@ let getKnownRawBlockWithBackoff = async ( "err": err->Utils.prettifyExn, "msg": `Issue while running fetching batch of events from the RPC. Will wait ${currentBackoff.contents->Int.toString}ms and try again.`, "source": sourceName, - "chainId": chain, + "chainId": chainId, "type": "EXPONENTIAL_BACKOFF", }) await Time.resolvePromiseAfterDelay(~delayMilliseconds=currentBackoff.contents) @@ -613,7 +613,7 @@ type options = { sourceFor: Source.sourceFor, syncConfig: Config.sourceSync, url: string, - chain: ChainId.t, + chainId: ChainId.t, // The chain's registrations, indexed by their sequential `index`. onEventRegistrations: array, lowercaseAddresses: bool, @@ -626,7 +626,7 @@ let make = ( sourceFor, syncConfig, url, - chain, + chainId, onEventRegistrations, lowercaseAddresses, ?ws, @@ -636,7 +636,7 @@ let make = ( let urlHost = switch Utils.Url.getHostFromUrl(url) { | None => JsError.throwWithMessage( - `The RPC url for chain ${chain->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, + `The RPC url for chain ${chainId->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, ) | Some(host) => host } @@ -689,7 +689,7 @@ let make = ( "msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000) ->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`, "source": name, - "chainId": chain, + "chainId": chainId, "metadata": { { "asyncTaskName": "transactionLoader: fetching transaction data - `getTransaction` rpc call", @@ -706,7 +706,7 @@ let make = ( getKnownRawBlockWithBackoff( ~client, ~sourceName=name, - ~chain, + ~chainId, ~backoffMsOnFailure=1000, ~blockNumber, ~recordRequest, @@ -718,7 +718,7 @@ let make = ( "msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000) ->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`, "source": name, - "chainId": chain, + "chainId": chainId, "metadata": { { "asyncTaskName": "blockLoader: fetching block data - `getBlock` rpc call", @@ -749,7 +749,7 @@ let make = ( "msg": `Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000) ->Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`, "source": name, - "chainId": chain, + "chainId": chainId, "metadata": { { "asyncTaskName": "receiptLoader: fetching transaction receipt - `getTransactionReceipt` rpc call", @@ -920,13 +920,13 @@ let make = ( Internal.Event({ onEventRegistration: (onEventRegistration :> Internal.onEventRegistration), blockNumber: block->getBlockNumber, - chain, + chainId, logIndex: log.logIndex, transactionIndex: log.transactionIndex, payload: { contractName: eventConfig.contractName, eventName: eventConfig.name, - chainId: chain, + chainId: chainId, params: decoded, block, transaction, @@ -1018,13 +1018,13 @@ let make = ( let createHeightSubscription = ws->Option.map(wsUrl => - (~onHeight) => RpcWebSocketHeightStream.subscribe(~wsUrl, ~chainId=chain, ~onHeight) + (~onHeight) => RpcWebSocketHeightStream.subscribe(~wsUrl, ~chainId=chainId, ~onHeight) ) { name, sourceFor, - chain, + chainId, poweredByHyperSync: false, pollingInterval: syncConfig.pollingInterval, getBlockHashes, diff --git a/packages/envio/src/sources/SimulateSource.res b/packages/envio/src/sources/SimulateSource.res index f2d04d7cb8..4b06444574 100644 --- a/packages/envio/src/sources/SimulateSource.res +++ b/packages/envio/src/sources/SimulateSource.res @@ -1,11 +1,11 @@ -let make = (~items: array, ~endBlock: int, ~chain: ChainId.t): Source.t => { +let make = (~items: array, ~endBlock: int, ~chainId: ChainId.t): Source.t => { let reportedHeight = max(endBlock, 1) { name: "SimulateSource", simulateItems: items, sourceFor: Sync, - chain, + chainId, poweredByHyperSync: false, pollingInterval: 0, getBlockHashes: (~blockNumbers as _, ~logger as _) => { diff --git a/packages/envio/src/sources/Source.res b/packages/envio/src/sources/Source.res index 1c84eec97e..4fcfc16262 100644 --- a/packages/envio/src/sources/Source.res +++ b/packages/envio/src/sources/Source.res @@ -64,7 +64,7 @@ type sourceFor = Sync | Fallback | Realtime type t = { name: string, sourceFor: sourceFor, - chain: ChainId.t, + chainId: ChainId.t, poweredByHyperSync: bool, /* Frequency (in ms) used when polling for new events on this network. */ pollingInterval: int, diff --git a/packages/envio/src/sources/SourceManager.res b/packages/envio/src/sources/SourceManager.res index 1f1496c8f2..87a289683b 100644 --- a/packages/envio/src/sources/SourceManager.res +++ b/packages/envio/src/sources/SourceManager.res @@ -81,7 +81,7 @@ let getActiveSource = sourceManager => sourceManager.activeSource let getRequestStatSamples = (sourceManager: t): array => { let samples = [] sourceManager.sourcesState->Array.forEach(sourceState => { - let chainId = sourceState.source.chain + let chainId = sourceState.source.chainId sourceState.requestStats->Utils.Dict.forEachWithKey((agg, method) => { samples ->Array.push({ @@ -111,7 +111,7 @@ let getSourceHeightSamples = (sourceManager: t): array => { if sourceState.knownHeight > 0 { samples->Array.push({ sourceName: sourceState.source.name, - chainId: sourceState.source.chain, + chainId: sourceState.source.chainId, height: sourceState.knownHeight, }) } @@ -425,7 +425,7 @@ let getSourceNewHeight = async ( logger->Logging.childTrace({ "msg": "onHeight subscription stale, switching to polling fallback", "source": source.name, - "chainId": source.chain, + "chainId": source.chainId, }) let h = ref(initialHeight) while h.contents <= knownHeight && !(newHeight.contents > initialHeight) { @@ -585,7 +585,7 @@ let waitForNewBlock = async (sourceManager: t, ~knownHeight, ~isRealtime, ~reduc let logger = Logging.createChild( ~params={ - "chainId": sourceManager.activeSource.chain, + "chainId": sourceManager.activeSource.chainId, "knownHeight": knownHeight, }, ) @@ -725,7 +725,7 @@ let executeQuery = async ( | Some(s) => if s.source !== sourceManager.activeSource { let logger = Logging.createChild( - ~params={"chainId": sourceManager.activeSource.chain}, + ~params={"chainId": sourceManager.activeSource.chainId}, ) logger->Logging.childInfo({ "msg": "Switching data-source", @@ -737,7 +737,7 @@ let executeQuery = async ( s | None => let logger = Logging.createChild( - ~params={"chainId": sourceManager.activeSource.chain}, + ~params={"chainId": sourceManager.activeSource.chainId}, ) %raw(`null`)->ErrorHandling.mkLogAndRaise(~logger, ~msg=noSourcesError) } @@ -748,7 +748,7 @@ let executeQuery = async ( let logger = Logging.createChild( ~params={ - "chainId": source.chain, + "chainId": source.chainId, "logType": "Block Range Query", "partitionId": query.partitionId, "source": source.name, @@ -897,7 +897,7 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array, ~isReal | Some(s) => s | None => let logger = Logging.createChild( - ~params={"chainId": sourceManager.activeSource.chain}, + ~params={"chainId": sourceManager.activeSource.chainId}, ) %raw(`null`)->ErrorHandling.mkLogAndRaise( ~logger, @@ -910,7 +910,7 @@ let getBlockHashes = async (sourceManager: t, ~blockNumbers: array, ~isReal let logger = Logging.createChild( ~params={ - "chainId": source.chain, + "chainId": source.chainId, "logType": "Block Hash Query", "source": source.name, "retry": retry, diff --git a/packages/envio/src/sources/Svm.res b/packages/envio/src/sources/Svm.res index 349717dae7..09f9709506 100644 --- a/packages/envio/src/sources/Svm.res +++ b/packages/envio/src/sources/Svm.res @@ -50,7 +50,7 @@ let make = (~logger: Pino.t): Ecosystem.t => { ~params={ "program": eventItem.onEventRegistration.eventConfig.contractName, "instruction": eventItem.onEventRegistration.eventConfig.name, - "chainId": eventItem.chain, + "chainId": eventItem.chainId, "slot": eventItem.blockNumber, "programId": instruction.programId, }, @@ -70,13 +70,13 @@ module GetFinalizedSlot = { ) } -let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): Source.t => { +let makeRPCSource = (~chainId, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): Source.t => { let client = Rest.client(rpc) let urlHost = switch Utils.Url.getHostFromUrl(rpc) { | None => JsError.throwWithMessage( - `The RPC url for chain ${chain->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, + `The RPC url for chain ${chainId->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`, ) | Some(host) => host } @@ -85,7 +85,7 @@ let makeRPCSource = (~chain, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): S { name, sourceFor, - chain, + chainId, poweredByHyperSync: false, pollingInterval: 10_000, getBlockHashes: (~blockNumbers as _, ~logger as _) => diff --git a/packages/envio/src/sources/SvmHyperSyncSource.res b/packages/envio/src/sources/SvmHyperSyncSource.res index 3f68fede32..34aa489bc8 100644 --- a/packages/envio/src/sources/SvmHyperSyncSource.res +++ b/packages/envio/src/sources/SvmHyperSyncSource.res @@ -1,7 +1,7 @@ open Source type options = { - chain: ChainId.t, + chainId: ChainId.t, endpointUrl: string, apiToken: option, onEventRegistrations: array, @@ -66,7 +66,7 @@ let toSvmInstruction = ( } let make = ( - {chain, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis}: options, + {chainId, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis}: options, ): t => { let name = "SvmHyperSync" @@ -154,7 +154,7 @@ let make = ( ) Internal.Event({ onEventRegistration, - chain, + chainId, blockNumber: item.slot, logIndex: synthLogIndex( ~transactionIndex=item.transactionIndex, @@ -278,7 +278,7 @@ let make = ( { name, sourceFor: Sync, - chain, + chainId, pollingInterval: 1000, poweredByHyperSync: true, getBlockHashes, diff --git a/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res b/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res index 0cf3bfd1a5..8145d9e7ca 100644 --- a/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res +++ b/scenarios/fuel_test/test/FuelHyperSyncSourceHeight_test.res @@ -38,7 +38,7 @@ let withServer = async (handler, body) => { } describe("FuelHyperSyncSource - getHeightOrThrow", () => { - let chain = 0->ChainId.fromInt + let chainId = 0->ChainId.fromInt // The native client validates that the token is a UUID before sending requests. let apiToken = "11111111-1111-1111-1111-111111111111" @@ -51,7 +51,7 @@ describe("FuelHyperSyncSource - getHeightOrThrow", () => { res->endWith(`{"height": 123}`) }, async endpointUrl => { let source = FuelHyperSyncSource.make({ - chain, + chainId, endpointUrl, apiToken: Some(apiToken), onEventRegistrations: [], @@ -77,7 +77,7 @@ describe("FuelHyperSyncSource - getHeightOrThrow", () => { res->endWith("Unauthorized") }, async endpointUrl => { let source = FuelHyperSyncSource.make({ - chain, + chainId, endpointUrl, apiToken: Some(apiToken), onEventRegistrations: [], diff --git a/scenarios/test_codegen/test/BelowHeadPollingPin_test.res b/scenarios/test_codegen/test/BelowHeadPollingPin_test.res index bbfa0cacc6..2ff8ae4213 100644 --- a/scenarios/test_codegen/test/BelowHeadPollingPin_test.res +++ b/scenarios/test_codegen/test/BelowHeadPollingPin_test.res @@ -9,7 +9,7 @@ describe("PIN: chains keep indexing after entering the reorg threshold", () => { // indexer enters the threshold, so it parks in WaitingForNewBlock. let chainAtLaggedHead = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) // This chain initially stops at 1000 - 200 = 800. Entering the threshold @@ -17,7 +17,7 @@ describe("PIN: chains keep indexing after entering the reorg threshold", () => { // before the multichain indexer can become ready. let chainWithThresholdWork = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( diff --git a/scenarios/test_codegen/test/BlockLag_test.res b/scenarios/test_codegen/test/BlockLag_test.res index 032964363c..66a9b3751b 100644 --- a/scenarios/test_codegen/test/BlockLag_test.res +++ b/scenarios/test_codegen/test/BlockLag_test.res @@ -6,7 +6,7 @@ describe("E2E blockLag tests", () => { async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/ClientFilterDedup_test.res b/scenarios/test_codegen/test/ClientFilterDedup_test.res index 576f030710..0beec32486 100644 --- a/scenarios/test_codegen/test/ClientFilterDedup_test.res +++ b/scenarios/test_codegen/test/ClientFilterDedup_test.res @@ -14,7 +14,7 @@ describe("Client-side address filtering item dedup", () => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([sourceMock.source])}], diff --git a/scenarios/test_codegen/test/ConcurrentWrite_test.res b/scenarios/test_codegen/test/ConcurrentWrite_test.res index 053e76b6dc..00fe343926 100644 --- a/scenarios/test_codegen/test/ConcurrentWrite_test.res +++ b/scenarios/test_codegen/test/ConcurrentWrite_test.res @@ -10,7 +10,7 @@ describe("Concurrent batch write and processing", () => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/E2E_test.res b/scenarios/test_codegen/test/E2E_test.res index aedbfac116..d34d12d26a 100644 --- a/scenarios/test_codegen/test/E2E_test.res +++ b/scenarios/test_codegen/test/E2E_test.res @@ -17,7 +17,7 @@ describe("E2E tests", () => { Async.it( "Populates config addresses on init and preserves them across restart", async t => { - let sourceMock = MockIndexer.Source.make([], ~chain=#1337) + let sourceMock = MockIndexer.Source.make([], ~chainId=#1337) let indexerMock = await MockIndexer.Indexer.make( ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([sourceMock.source])}], ) @@ -44,7 +44,7 @@ describe("E2E tests", () => { Async.it("Currectly starts indexing from a non-zero start block", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let _indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -74,7 +74,7 @@ describe("E2E tests", () => { Async.it("Correctly sets Prom metrics", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -114,11 +114,11 @@ describe("E2E tests", () => { Async.itWithOptions("Prom readiness metrics are gated on the whole indexer", {retry: 3}, async t => { let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -182,7 +182,7 @@ describe("E2E tests", () => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -257,7 +257,7 @@ describe("E2E tests", () => { Async.it("Track effects in prom metrics", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -496,7 +496,7 @@ describe("E2E tests", () => { Async.it("context.log should be accessible from inside an effect handler", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -555,7 +555,7 @@ describe("E2E tests", () => { async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -672,11 +672,11 @@ describe("E2E tests", () => { async t => { let sourceMockPrimary = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMockFallback = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -747,7 +747,7 @@ describe("E2E tests", () => { Async.it("Effect rate limiting across multiple windows", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -850,7 +850,7 @@ describe("E2E tests", () => { Async.it("Effect rate limiting with single call per window", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -964,7 +964,7 @@ describe("E2E tests", () => { Async.it("Effect cache can be disabled per-call via context.cache", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1039,7 +1039,7 @@ describe("E2E tests", () => { Async.it("Effect error in one call shouldn't cause other calls to fail", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1113,12 +1113,12 @@ describe("E2E tests", () => { // Create a Sync source (simulating HyperSync) and a Live source (simulating RPC for live) let syncSource = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ~sourceFor=Source.Sync, ) let liveSource = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ~sourceFor=Source.Realtime, ) @@ -1215,7 +1215,7 @@ describe("E2E tests", () => { Async.it("Partition queries adjust ranges depending on responses", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1324,7 +1324,7 @@ describe("E2E tests", () => { Async.it("Items from later chunk wait for earlier chunk to complete", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1445,7 +1445,7 @@ describe("E2E tests", () => { Async.it("Partition merging works for fetching partitions via mergeBlock", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1630,7 +1630,7 @@ describe("E2E tests", () => { async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1698,11 +1698,11 @@ describe("E2E tests", () => { async t => { let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1764,11 +1764,11 @@ describe("E2E tests", () => { async t => { let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1844,11 +1844,11 @@ describe("E2E tests", () => { async t => { let leaderSource = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let followerSource = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/EnterReorgThreshold_test.res b/scenarios/test_codegen/test/EnterReorgThreshold_test.res index 8650854e8b..b992e68da9 100644 --- a/scenarios/test_codegen/test/EnterReorgThreshold_test.res +++ b/scenarios/test_codegen/test/EnterReorgThreshold_test.res @@ -30,11 +30,11 @@ describe("PIN: multichain indexer enters the reorg threshold", () => { // threshold. Head starts at 1000, so the pre-threshold head is 800. let chainA = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let chainB = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( @@ -113,7 +113,7 @@ describe("PIN: multichain indexer enters the reorg threshold", () => { async t => { let source = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/EntityColumnTypes_test.res b/scenarios/test_codegen/test/EntityColumnTypes_test.res index 43fcc48039..ff288abdac 100644 --- a/scenarios/test_codegen/test/EntityColumnTypes_test.res +++ b/scenarios/test_codegen/test/EntityColumnTypes_test.res @@ -6,7 +6,7 @@ describe("Postgres Numeric Precision Entity Tester Migrations", () => { async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let _indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/IndexerStateStall_test.res b/scenarios/test_codegen/test/IndexerStateStall_test.res index dcaebe6835..68d161662d 100644 --- a/scenarios/test_codegen/test/IndexerStateStall_test.res +++ b/scenarios/test_codegen/test/IndexerStateStall_test.res @@ -25,7 +25,7 @@ describe("IndexerState fetch stall accounting", () => { state->IndexerState.markProcessingStalledOnFetch await Time.resolvePromiseAfterDelay(~delayMilliseconds=50) state->IndexerState.beginReorg( - ~chain=1->ChainId.fromInt, + ~chainId=1->ChainId.fromInt, ~blockNumber=100, ) // Settled, not discarded: the wait before the reorg still has to land in diff --git a/scenarios/test_codegen/test/IndexerState_test.res b/scenarios/test_codegen/test/IndexerState_test.res index 0c19bb4a48..8b579e08c5 100644 --- a/scenarios/test_codegen/test/IndexerState_test.res +++ b/scenarios/test_codegen/test/IndexerState_test.res @@ -73,7 +73,7 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) for logIndex in 0 to numberOfEventsInBatch { let batchItem = Internal.Event({ - chain: id, + chainId: id, blockNumber: currentBlockNumber.contents, logIndex, transactionIndex: 0, @@ -124,7 +124,7 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) let chainConfig = config.defaultChain->Option.getUnsafe // For this test we don't need real sources - just testing event ordering // Create a mock source that satisfies SourceManager requirements (chain ID doesn't matter here) - let mockSource = MockIndexer.Source.make([], ~chain=#1) + let mockSource = MockIndexer.Source.make([], ~chainId=#1) let mockChainState = ChainState.make( ~chainConfig, ~fetchState=fetchState.contents, @@ -157,8 +157,8 @@ let populateChainQueuesWithRandomEvents = (~runTime=1000, ~maxBlockTime=15, ()) let getItemKey = (item: Internal.item) => switch item { - | Event({chain, blockNumber, logIndex}) => ( - chain, + | Event({chainId, blockNumber, logIndex}) => ( + chainId, blockNumber, logIndex, ) @@ -183,7 +183,7 @@ describe("IndexerState", () => { let (state, numberOfMockEventsCreated, _allEvents) = populateChainQueuesWithRandomEvents() let defaultFirstEvent = Internal.Event({ - chain: MockConfig.chain1, + chainId: MockConfig.chain1, blockNumber: 0, logIndex: 0, transactionIndex: 0, @@ -293,7 +293,7 @@ describe("IndexerState", () => { ~latestFetchedBlock={blockNumber, blockTimestamp: blockNumber * 15}, ~newItems=[ Internal.Event({ - chain: chainId, + chainId: chainId, blockNumber, logIndex: 0, transactionIndex: 0, @@ -316,7 +316,7 @@ describe("IndexerState", () => { ->ChainMap.values ->Array.forEach( chainConfig => { - let mockSource = MockIndexer.Source.make([], ~chain=#1) + let mockSource = MockIndexer.Source.make([], ~chainId=#1) let (fetchState, indexingAddresses) = makeFetchState(~chainId=chainConfig.id, ~eventBlocks) let chainState = ChainState.make( ~chainConfig, @@ -353,11 +353,11 @@ describe("IndexerState", () => { ~isRollback=false, ) - let chain = config.chainMap->ChainMap.keys->Array.getUnsafe(0) + let chainId = config.chainMap->ChainMap.keys->Array.getUnsafe(0) // A fetch lands mid-batch and appends block 15 to this chain's buffer // (its batch-time snapshot held only block 5). - let cs = state->IndexerState.getChainState(~chain) + let cs = state->IndexerState.getChainState(~chainId) let concurrentQuery: FetchState.query = { partitionId: "0", itemsTarget: Some(0), @@ -375,7 +375,7 @@ describe("IndexerState", () => { ~latestFetchedBlock={blockNumber: 15, blockTimestamp: 15 * 15}, ~newItems=[ Internal.Event({ - chain, + chainId, blockNumber: 15, logIndex: 0, transactionIndex: 0, @@ -392,10 +392,10 @@ describe("IndexerState", () => { ) state->IndexerState.applyBatchProgress(~batch) - let resultCs = state->IndexerState.getChainState(~chain) + let resultCs = state->IndexerState.getChainState(~chainId) let progressed = batch.progressedChainsById - ->ChainId.Dict.dangerouslyGetNonOption(chain) + ->ChainId.Dict.dangerouslyGetNonOption(chainId) ->Option.getUnsafe t.expect( diff --git a/scenarios/test_codegen/test/RawEventsTableMigration_test.res b/scenarios/test_codegen/test/RawEventsTableMigration_test.res index b923299823..f473c3bc8a 100644 --- a/scenarios/test_codegen/test/RawEventsTableMigration_test.res +++ b/scenarios/test_codegen/test/RawEventsTableMigration_test.res @@ -4,7 +4,7 @@ describe("Raw Events Table Migrations", () => { Async.it("Raw events table should migrate successfully", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let _indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -50,7 +50,7 @@ describe("Raw Events Table Migrations", () => { Async.it("Inserting 2 rows with the same pk should pass", async _t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let _indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/RpcSourceContract_test.res b/scenarios/test_codegen/test/RpcSourceContract_test.res index 1a0e3f1582..e1d610d330 100644 --- a/scenarios/test_codegen/test/RpcSourceContract_test.res +++ b/scenarios/test_codegen/test/RpcSourceContract_test.res @@ -2,7 +2,7 @@ open Vitest type sourceFactory = RpcSource.options => Source.t -let chain = 1->ChainId.fromInt +let chainId = 1->ChainId.fromInt let sighash = "0xcf16a92280c1bbb43f72d31126b724d508df2877835849e8744017ab36a9b47f" let transactionHash = "0x27e26f21f744064a4af53810d8002bbd7208a2ca4865503a99b9c529e5cff5ea" let contractAddress = "0x00000000000000000000000000000000000000AA" @@ -71,7 +71,7 @@ let syncConfig = EvmChain.getSyncConfig({ let makeSource = (~factory, ~url, ~registration: Internal.evmOnEventRegistration) => { let options: RpcSource.options = { url, - chain, + chainId, onEventRegistrations: [registration], sourceFor: Sync, syncConfig, @@ -419,7 +419,7 @@ let registerContractTests = (~name, ~factory: sourceFactory) => { let registration = makeRegistration() let options: RpcSource.options = { url: mock.url, - chain, + chainId, onEventRegistrations: [registration], sourceFor: Sync, syncConfig: defaultSyncConfig, @@ -679,7 +679,7 @@ let registerContractTests = (~name, ~factory: sourceFactory) => { async mock => { let options: RpcSource.options = { url: mock.url, - chain, + chainId, onEventRegistrations: [eventA, eventB], sourceFor: Sync, syncConfig, diff --git a/scenarios/test_codegen/test/RpcSource_test.res b/scenarios/test_codegen/test/RpcSource_test.res index 3124dd1c87..d017c8b134 100644 --- a/scenarios/test_codegen/test/RpcSource_test.res +++ b/scenarios/test_codegen/test/RpcSource_test.res @@ -25,7 +25,7 @@ describe("RpcSource - name", () => { it("Returns the name of the source including sanitized rpc url", t => { let source = RpcSource.make({ url: "https://eth.rpc.hypersync.xyz?api_key=123", - chain: MockConfig.chain1337, + chainId: MockConfig.chain1337, onEventRegistrations: [], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -39,7 +39,7 @@ describe("RpcSource - getHeightOrThrow", () => { Async.it("Returns the current height of the chain", async t => { let source = RpcSource.make({ url: `https://eth.rpc.hypersync.xyz/${testApiToken}`, - chain: MockConfig.chain1337, + chainId: MockConfig.chain1337, onEventRegistrations: [], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -727,12 +727,12 @@ describe("RpcSource - fieldRegistry completeness", () => { }) }) -let chain = 1->ChainId.fromInt +let chainId = 1->ChainId.fromInt describe("RpcSource - empty selection", () => { Async.it("Throws UnsupportedSelection when the selection has no event configs", async t => { let source = RpcSource.make({ url: "http://localhost:1", - chain, + chainId, onEventRegistrations: [], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -815,7 +815,7 @@ describe("RpcSource - getItemsOrThrow on response-too-large", () => { let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: [eventConfig], sourceFor: Sync, // initialBlockInterval=ceiling=10000, backoffMultiplicative=0.8 @@ -944,7 +944,7 @@ describe("RpcSource - getItemsOrThrow on response-too-large", () => { let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: [eventConfig], sourceFor: Sync, // initialBlockInterval=ceiling=10000, backoffMultiplicative=0.8, accelerationAdditive=500 @@ -1094,7 +1094,7 @@ describe("RpcSource - getItemsOrThrow classifies real provider block-range error let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: [eventConfig], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -1188,7 +1188,7 @@ describe("RpcSource - getItemsOrThrow with missing transaction data", () => { let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: [eventConfig], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -1340,7 +1340,7 @@ describe("RpcSource - getItemsOrThrow fans out multiple selections", () => { let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: [eventConfig], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -1468,7 +1468,7 @@ describe("RpcSource - builds partition log selections end to end", () => { ) let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: allRegistrations, sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -1558,7 +1558,7 @@ describe("RpcSource - getItemsOrThrow with a skip-all event filter", () => { let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: [eventConfig], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), @@ -1719,7 +1719,7 @@ describe("RpcSource - getItemsOrThrow scopes filters to each contract's addresse let addressesByContractName = Dict.fromArray([("ContractA", [addrA]), ("ContractB", [addrB])]) let source = RpcSource.make({ url: mock.url, - chain, + chainId, onEventRegistrations: [eventA, eventB], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), diff --git a/scenarios/test_codegen/test/SourceBlockHashes_test.res b/scenarios/test_codegen/test/SourceBlockHashes_test.res index 0fcf09bb33..b278409fde 100644 --- a/scenarios/test_codegen/test/SourceBlockHashes_test.res +++ b/scenarios/test_codegen/test/SourceBlockHashes_test.res @@ -6,7 +6,7 @@ let testApiToken = ) // Ethereum mainnet. -let chain = 1->ChainId.fromInt +let chainId = 1->ChainId.fromInt // Uniswap V2 Factory's PairCreated event (topic0 = keccak("PairCreated(address,address,address,uint256)")) // 2 indexed args (token0, token1) ⇒ topicCount = 3. @@ -92,7 +92,7 @@ let makeSelection = (): FetchState.selection => { let makeHyperSyncSource = () => EvmHyperSyncSource.make({ - chain, + chainId, endpointUrl: "https://eth.hypersync.xyz", onEventRegistrations: [pairCreatedRegistration], apiToken: Some(testApiToken), @@ -106,7 +106,7 @@ let makeHyperSyncSource = () => let makeRpcSource = () => RpcSource.make({ url: `https://eth.rpc.hypersync.xyz/${testApiToken}`, - chain, + chainId, onEventRegistrations: [pairCreatedRegistration], sourceFor: Sync, syncConfig: EvmChain.getSyncConfig({}), diff --git a/scenarios/test_codegen/test/StalledPolling_test.res b/scenarios/test_codegen/test/StalledPolling_test.res index 52b95adda0..9f84224116 100644 --- a/scenarios/test_codegen/test/StalledPolling_test.res +++ b/scenarios/test_codegen/test/StalledPolling_test.res @@ -9,7 +9,7 @@ describe("Polling-stall loophole", () => { let source = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ~pollingInterval, ) let _indexerMock = await MockIndexer.Indexer.make( diff --git a/scenarios/test_codegen/test/WriteRead_test.res b/scenarios/test_codegen/test/WriteRead_test.res index ab5e497aa9..cc668ad27b 100644 --- a/scenarios/test_codegen/test/WriteRead_test.res +++ b/scenarios/test_codegen/test/WriteRead_test.res @@ -9,7 +9,7 @@ let mockDate = (~year=2024, ~month=1, ~day=1) => { describe("Write/read tests", () => { Async.itSkipInClaudeCloud("Test writing and reading entities with special cases", async t => { - let sourceMock = MockIndexer.Source.make(~chain=#1337, [#getHeightOrThrow, #getItemsOrThrow]) + let sourceMock = MockIndexer.Source.make(~chainId=#1337, [#getHeightOrThrow, #getItemsOrThrow]) let indexerMock = await MockIndexer.Indexer.make( ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([sourceMock.source])}], ~saveFullHistory=true, @@ -181,7 +181,7 @@ breaking precicion on big values. https://github.com/enviodev/hyperindex/issues/ Async.it( "Keeps committed entities across batches without rewriting their history", async t => { - let sourceMock = MockIndexer.Source.make(~chain=#1337, [#getHeightOrThrow, #getItemsOrThrow]) + let sourceMock = MockIndexer.Source.make(~chainId=#1337, [#getHeightOrThrow, #getItemsOrThrow]) let indexerMock = await MockIndexer.Indexer.make( ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([sourceMock.source])}], ~saveFullHistory=true, @@ -272,7 +272,7 @@ breaking precicion on big values. https://github.com/enviodev/hyperindex/issues/ }) Async.it("Test getWhere queries with eq and gt operators", async t => { - let sourceMock = MockIndexer.Source.make(~chain=#1337, [#getHeightOrThrow, #getItemsOrThrow]) + let sourceMock = MockIndexer.Source.make(~chainId=#1337, [#getHeightOrThrow, #getItemsOrThrow]) let indexerMock = await MockIndexer.Indexer.make( ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([sourceMock.source])}], ) @@ -464,7 +464,7 @@ breaking precicion on big values. https://github.com/enviodev/hyperindex/issues/ }) Async.it("getWhere throws a user friendly error for an invalid filter", async t => { - let sourceMock = MockIndexer.Source.make(~chain=#1337, [#getHeightOrThrow, #getItemsOrThrow]) + let sourceMock = MockIndexer.Source.make(~chainId=#1337, [#getHeightOrThrow, #getItemsOrThrow]) let indexerMock = await MockIndexer.Indexer.make( ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([sourceMock.source])}], ) diff --git a/scenarios/test_codegen/test/YamlConfigIndexer_test.res b/scenarios/test_codegen/test/YamlConfigIndexer_test.res index e922b05db9..fbbf36dc62 100644 --- a/scenarios/test_codegen/test/YamlConfigIndexer_test.res +++ b/scenarios/test_codegen/test/YamlConfigIndexer_test.res @@ -37,7 +37,7 @@ chains: `, ) - let source = MockIndexer.Source.make([#getHeightOrThrow, #getItemsOrThrow], ~chain=#1337) + let source = MockIndexer.Source.make([#getHeightOrThrow, #getItemsOrThrow], ~chainId=#1337) let indexerMock = await MockIndexer.Indexer.make( ~config, ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([source.source])}], diff --git a/scenarios/test_codegen/test/__mocks__/MockEvents.res b/scenarios/test_codegen/test/__mocks__/MockEvents.res index 8511985c7d..588cab54ed 100644 --- a/scenarios/test_codegen/test/__mocks__/MockEvents.res +++ b/scenarios/test_codegen/test/__mocks__/MockEvents.res @@ -191,7 +191,7 @@ let newGravatarEventToBatchItem = ( Indexer.Transaction.t, >, ): Internal.item => Internal.Event({ - chain: MockConfig.chain1337, + chainId: MockConfig.chain1337, blockNumber: event.block.number, logIndex: event.logIndex, transactionIndex: 0, @@ -207,7 +207,7 @@ let updatedGravatarEventToBatchItem = ( Indexer.Transaction.t, >, ): Internal.item => Internal.Event({ - chain: MockConfig.chain1337, + chainId: MockConfig.chain1337, blockNumber: event.block.number, logIndex: event.logIndex, transactionIndex: 0, diff --git a/scenarios/test_codegen/test/helpers/MockIndexer.res b/scenarios/test_codegen/test/helpers/MockIndexer.res index b0e2e29a73..caec46a4e9 100644 --- a/scenarios/test_codegen/test/helpers/MockIndexer.res +++ b/scenarios/test_codegen/test/helpers/MockIndexer.res @@ -450,10 +450,10 @@ module Indexer = { let chainMap = chains ->Array.map(chainConfig => { - let chain = (chainConfig.chain :> int)->ChainId.fromInt - let originalChainConfig = baseConfig.chainMap->ChainMap.get(chain) + let chainId = (chainConfig.chain :> int)->ChainId.fromInt + let originalChainConfig = baseConfig.chainMap->ChainMap.get(chainId) ( - chain, + chainId, { ...originalChainConfig, sourceConfig: chainConfig.sourceConfig, @@ -824,7 +824,7 @@ module Source = { unsubscribeHeightSubscription: unit => unit, } - let make = (methods, ~chain=#1: chainId, ~sourceFor=Source.Sync, ~pollingInterval=1000) => { + let make = (methods, ~chainId=#1: chainId, ~sourceFor=Source.Sync, ~pollingInterval=1000) => { let implement = (method: method, fn) => { if methods->Array.includes(method) { fn @@ -833,7 +833,7 @@ module Source = { } } - let chain = (chain :> int)->ChainId.fromInt + let chainId = (chainId :> int)->ChainId.fromInt let getHeightOrThrowCalls = [] let getHeightOrThrowResolveFns = [] let getHeightOrThrowRejectFns = [] @@ -937,7 +937,7 @@ module Source = { name: "MockSource", sourceFor, poweredByHyperSync: false, - chain, + chainId, pollingInterval, getBlockHashes: implement(#getBlockHashes, (~blockNumbers, ~logger as _) => { getBlockHashesCalls->Array.push(blockNumbers)->ignore @@ -1029,7 +1029,7 @@ module Source = { contractName: onEventRegistration.eventConfig.contractName, eventName: onEventRegistration.eventConfig.name, params: %raw(`{}`), - chainId: chain, + chainId, srcAddress: "0x0000000000000000000000000000000000000000"->Address.unsafeFromString, logIndex: item.logIndex, block: { @@ -1044,7 +1044,7 @@ module Source = { })`) Internal.Event({ onEventRegistration, - chain, + chainId, blockNumber: item.blockNumber, logIndex: item.logIndex, transactionIndex: 0, diff --git a/scenarios/test_codegen/test/helpers/RpcSourcePins.res b/scenarios/test_codegen/test/helpers/RpcSourcePins.res index d37f38bf61..a5f3e91bb0 100644 --- a/scenarios/test_codegen/test/helpers/RpcSourcePins.res +++ b/scenarios/test_codegen/test/helpers/RpcSourcePins.res @@ -57,7 +57,7 @@ let normalizeEvent = item => switch item { | Internal.Event({ onEventRegistration, - chain, + chainId, blockNumber, logIndex, transactionIndex, @@ -66,7 +66,7 @@ let normalizeEvent = item => let payload = payload->Evm.toPayload { registrationId: onEventRegistration.eventConfig.id, - chainId: chain, + chainId, blockNumber, logIndex, transactionIndex, diff --git a/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res b/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res index 821b680494..7efeb21f2f 100644 --- a/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res +++ b/scenarios/test_codegen/test/lib_tests/CrossChainState_test.res @@ -4,7 +4,7 @@ let baseChainConfig = Config.load().chainMap->ChainMap.values->Utils.Array.first let mockEvent = (~blockNumber): Internal.item => Internal.Event({ - chain: 1->ChainId.fromInt, + chainId: 1->ChainId.fromInt, blockNumber, // Carries an `index` so the buffer's dedup key resolves; the rest of the // registration is unused by these tests. @@ -62,7 +62,7 @@ let makeChainState = ( clientFilterAddressThreshold: None, buffer: bufferBlocks->Array.map(blockNumber => mockEvent(~blockNumber)), } - let mockSource = MockIndexer.Source.make([], ~chain=#1) + let mockSource = MockIndexer.Source.make([], ~chainId=#1) ChainState.make( ~chainConfig={...baseChainConfig, id: chainId}, ~fetchState, @@ -139,7 +139,7 @@ let makeFetchingChainState = ( firstEventBlock, clientFilterAddressThreshold: None, } - let mockSource = MockIndexer.Source.make([], ~chain=#1) + let mockSource = MockIndexer.Source.make([], ~chainId=#1) ChainState.make( ~chainConfig={...baseChainConfig, id: chainId}, ~fetchState, @@ -247,8 +247,8 @@ describe("CrossChainState fetch control", () => { let cm = makeCrossChainState(~chainStatesList=[a, b], ~isRealtime=true) let dispatched = [] - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatched->Array.push((chain->ChainId.toInt, action))->ignore + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { + dispatched->Array.push((chainId->ChainId.toInt, action))->ignore Promise.resolve() }) @@ -277,8 +277,8 @@ describe("CrossChainState fetch control", () => { let cm = makeCrossChainState(~chainStatesList=[a, b], ~targetBufferSize=100) let dispatched = [] - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action as _) => { - dispatched->Array.push(chain->ChainId.toInt)->ignore + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action as _) => { + dispatched->Array.push(chainId->ChainId.toInt)->ignore Promise.resolve() }) @@ -293,10 +293,10 @@ describe("CrossChainState fetch control", () => { let cm = makeCrossChainState(~chainStatesList=[cs], ~targetBufferSize=1) let dispatched = [] - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { dispatched ->Array.push(( - chain->ChainId.toInt, + chainId->ChainId.toInt, switch action { | Ready(queries) => queries->Array.length | _ => 0 @@ -322,12 +322,12 @@ describe("CrossChainState fetch control", () => { let cm = makeCrossChainState(~chainStatesList=[buffered, fetching], ~targetBufferSize) let admitted = [] - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { switch action { | Ready(queries) => admitted ->Array.push(( - chain->ChainId.toInt, + chainId->ChainId.toInt, queries->Array.reduce(0, (sum, query: FetchState.query) => sum + query.itemsEst), )) ->ignore @@ -362,8 +362,8 @@ describe("CrossChainState fetch control", () => { ) let dispatched = [] - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { - dispatched->Array.push((chain->ChainId.toInt, action))->ignore + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { + dispatched->Array.push((chainId->ChainId.toInt, action))->ignore Promise.resolve() }) @@ -386,10 +386,10 @@ describe("CrossChainState fetch control", () => { let cm = makeCrossChainState(~chainStatesList=[first, second, buffered], ~targetBufferSize=100) let firstTickQueries = [] - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { switch action { | Ready(queries) => - firstTickQueries->Array.push((chain->ChainId.toInt, queries))->ignore + firstTickQueries->Array.push((chainId->ChainId.toInt, queries))->ignore | _ => () } Promise.resolve() @@ -410,9 +410,9 @@ describe("CrossChainState fetch control", () => { ) let secondTickChains = [] - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { switch action { - | Ready(_) => secondTickChains->Array.push(chain->ChainId.toInt)->ignore + | Ready(_) => secondTickChains->Array.push(chainId->ChainId.toInt)->ignore | _ => () } Promise.resolve() @@ -480,7 +480,7 @@ describe("CrossChainState fetch control", () => { firstEventBlock: Some(0), clientFilterAddressThreshold: None, } - let mockSource1 = MockIndexer.Source.make([], ~chain=#1) + let mockSource1 = MockIndexer.Source.make([], ~chainId=#1) let a = ChainState.make( ~chainConfig={...baseChainConfig, id: 1->ChainId.fromInt}, ~fetchState=fetchState1, @@ -508,9 +508,9 @@ describe("CrossChainState fetch control", () => { let cm = makeCrossChainState(~chainStatesList=[a, b], ~isRealtime, ~targetBufferSize=3000) let dispatchedItemsByChain = Dict.make() - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { dispatchedItemsByChain->ChainId.Dict.set( - chain, + chainId, switch action { | Ready(queries) => queries->Array.reduce(0., (acc, q: FetchState.query) => acc +. q.itemsEst->Int.toFloat) @@ -557,9 +557,9 @@ describe("CrossChainState fetch control", () => { let cm = makeCrossChainState(~chainStatesList=[a, b], ~targetBufferSize=3000) let actionsByChain = Dict.make() - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { actionsByChain->ChainId.Dict.set( - chain, + chainId, switch action { | WaitingForNewBlock => "waitingForNewBlock" | NothingToQuery => "nothingToQuery" @@ -606,9 +606,9 @@ describe("CrossChainState fetch control", () => { ) let estimatesByChain = Dict.make() - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { estimatesByChain->ChainId.Dict.set( - chain, + chainId, switch action { | Ready(queries) => queries->Array.reduce(0, (total, query: FetchState.query) => total + query.itemsEst) @@ -735,9 +735,9 @@ describe("ChainState cold start", () => { let cm = makeCrossChainState(~chainStatesList=[a, b], ~targetBufferSize=10_000) let dispatchedItemsByChain = Dict.make() - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { dispatchedItemsByChain->ChainId.Dict.set( - chain, + chainId, switch action { | Ready(queries) => queries->Array.reduce(0., (acc, q: FetchState.query) => acc +. q.itemsEst->Int.toFloat) @@ -774,9 +774,9 @@ describe("ChainState cold start", () => { let cm = makeCrossChainState(~chainStatesList=[a, b], ~targetBufferSize=10_000) let actionsByChain = Dict.make() - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { actionsByChain->ChainId.Dict.set( - chain, + chainId, switch action { | WaitingForNewBlock => "waitingForNewBlock" | NothingToQuery => "nothingToQuery" @@ -819,9 +819,9 @@ describe("ChainState cold start", () => { ) let dispatchedItemsByChain = Dict.make() - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { dispatchedItemsByChain->ChainId.Dict.set( - chain, + chainId, switch action { | Ready(queries) => queries->Array.reduce(0, (acc, q: FetchState.query) => acc + q.itemsEst) @@ -842,7 +842,7 @@ describe("ChainState cold start", () => { let cs = makeFetchingChainState(~chainId=1->ChainId.fromInt, ~knownHeight=1_000_000, ~latestFetchedBlock=0) let cm = makeCrossChainState(~chainStatesList=[cs], ~targetBufferSize) let dispatched = ref(0.) - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain as _, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId as _, ~action) => { switch action { | Ready(queries) => dispatched := @@ -906,9 +906,9 @@ describe("ChainState cold start", () => { ) let itemsByChain = Dict.make() - await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chain, ~action) => { + await cm->CrossChainState.checkAndFetch(~dispatchChain=(~chainId, ~action) => { itemsByChain->ChainId.Dict.set( - chain, + chainId, switch action { | Ready(queries) => queries->Array.reduce(0, (acc, q: FetchState.query) => acc + q.itemsEst) | _ => 0 diff --git a/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res b/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res index 1b4a7a5f43..f7d2ffae95 100644 --- a/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res +++ b/scenarios/test_codegen/test/lib_tests/DynamicContractsStartupSize_test.res @@ -21,7 +21,7 @@ describe("Dynamic contracts startup size", () => { async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let _indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res b/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res index 2c5c66fba0..b6ed84aafa 100644 --- a/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res +++ b/scenarios/test_codegen/test/lib_tests/EntityIdType_test.res @@ -196,7 +196,7 @@ chains: `, ) - let source = MockIndexer.Source.make([#getHeightOrThrow, #getItemsOrThrow], ~chain=#1337) + let source = MockIndexer.Source.make([#getHeightOrThrow, #getItemsOrThrow], ~chainId=#1337) let indexerMock = await MockIndexer.Indexer.make( ~config, ~chains=[{chain: #1337, sourceConfig: Config.CustomSources([source.source])}], diff --git a/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res b/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res index 125745a767..311d454326 100644 --- a/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res +++ b/scenarios/test_codegen/test/lib_tests/FetchState_onBlock_test.res @@ -70,7 +70,7 @@ let makeInitialWithOnBlock = (~startBlock=0, ~onBlockRegistrations) => { } let mockEvent = (~blockNumber, ~logIndex=0): Internal.item => Internal.Event({ - chain: chainId, + chainId: chainId, blockNumber, // Carries an `index` so the buffer's dedup key (blockNumber, logIndex, index) // resolves; the rest of the registration is unused by these tests. diff --git a/scenarios/test_codegen/test/lib_tests/FetchState_test.res b/scenarios/test_codegen/test/lib_tests/FetchState_test.res index 180b116c34..92d5af74e7 100644 --- a/scenarios/test_codegen/test/lib_tests/FetchState_test.res +++ b/scenarios/test_codegen/test/lib_tests/FetchState_test.res @@ -76,7 +76,7 @@ let makeConfigContract = (contractName, address): Internal.indexingAddress => { let mockEvent = (~blockNumber, ~logIndex=0, ~chainId=1->ChainId.fromInt, ~registrationIndex=0): Internal.item => Internal.Event({ - chain: chainId, + chainId: chainId, blockNumber, // Carries an `index` so the buffer's dedup key (blockNumber, logIndex, index) // resolves; the rest of the registration is unused by these tests. diff --git a/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res b/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res index 1e6f3fe531..03209a263d 100644 --- a/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res +++ b/scenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.res @@ -213,7 +213,7 @@ describe("EVM event decoding via EvmRpcClient.getLogs", () => { Internal.Event({ onEventRegistration: (MockIndexer.evmOnEventRegistration(~contractName="ERC20") :> Internal.onEventRegistration), - chain: 137->ChainId.fromInt, + chainId: 137->ChainId.fromInt, blockNumber, logIndex, transactionIndex: 0, diff --git a/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res b/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res index a8b081dc60..c5388465d6 100644 --- a/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res +++ b/scenarios/test_codegen/test/lib_tests/IndexerLoop_test.res @@ -28,7 +28,7 @@ let makeState = (~onError=errHandler => errHandler->ErrorHandling.raiseExn, ()) ~chainId=chainConfig.id, ~knownHeight=0, ) - let mockSource = MockIndexer.Source.make([], ~chain=#1) + let mockSource = MockIndexer.Source.make([], ~chainId=#1) let chainState = ChainState.make( ~chainConfig, ~fetchState, diff --git a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res index a01bcffbd6..b48ecbf555 100644 --- a/scenarios/test_codegen/test/lib_tests/PgStorage_test.res +++ b/scenarios/test_codegen/test/lib_tests/PgStorage_test.res @@ -1049,7 +1049,7 @@ describe("ecosystem.toRawEvent", () => { Internal.Event({ onEventRegistration: (MockIndexer.evmOnEventRegistration(~contractName="ERC20") :> Internal.onEventRegistration), - chain: 137->ChainId.fromInt, + chainId: 137->ChainId.fromInt, blockNumber, logIndex, transactionIndex: 0, diff --git a/scenarios/test_codegen/test/rollback/ChainMocking.res b/scenarios/test_codegen/test/rollback/ChainMocking.res index 87ac289e84..12c4a441f3 100644 --- a/scenarios/test_codegen/test/rollback/ChainMocking.res +++ b/scenarios/test_codegen/test/rollback/ChainMocking.res @@ -177,7 +177,7 @@ module Make = () => { let log = Internal.Event({ onEventRegistration: (onEventRegistration :> Internal.onEventRegistration), payload: makeEvent(~blockHash), - chain: self.chainConfig.id, + chainId: self.chainConfig.id, blockNumber, logIndex, transactionIndex, diff --git a/scenarios/test_codegen/test/rollback/Rollback_test.res b/scenarios/test_codegen/test/rollback/Rollback_test.res index 4604766796..34fdf3fe1a 100644 --- a/scenarios/test_codegen/test/rollback/Rollback_test.res +++ b/scenarios/test_codegen/test/rollback/Rollback_test.res @@ -307,11 +307,11 @@ describe("E2E rollback tests", () => { Async.it("Should stay in reorg threshold on restart when progress is past threshold", async t => { let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let chains = [ { @@ -426,7 +426,7 @@ describe("E2E rollback tests", () => { Async.it("Rollback of a single chain indexer", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -445,7 +445,7 @@ describe("E2E rollback tests", () => { Async.it("Rolls back SET -> DELETE -> SET to the deleted state", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let resolveIndexerError = ref(None) let indexerErrorPromise = Promise.make((resolve, _reject) => { @@ -600,7 +600,7 @@ describe("E2E rollback tests", () => { Async.it("Parks a reorg detected while a batch is still processing", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -669,7 +669,7 @@ describe("E2E rollback tests", () => { Async.it("Fires onRollbackCommit per affected chain after the rollback write", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let rollbackCommitCalls = [] let unregister = RollbackCommit.register(async (args: RollbackCommit.args) => { @@ -700,7 +700,7 @@ describe("E2E rollback tests", () => { async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -736,7 +736,7 @@ describe("E2E rollback tests", () => { Async.it("Shouldn't detect reorg for rollbacked block", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -813,11 +813,11 @@ describe("E2E rollback tests", () => { async t => { let sourceMock1 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock2 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -854,7 +854,7 @@ describe("E2E rollback tests", () => { Async.it("Rollback Dynamic Contract", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1105,11 +1105,11 @@ This might be wrong after we start exposing a block hash for progress block.`, Async.it("Rollback of multichain indexer (single entity id change)", async t => { let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -1563,11 +1563,11 @@ This might be wrong after we start exposing a block hash for progress block.`, async t => { let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -2000,7 +2000,7 @@ This might be wrong after we start exposing a block hash for progress block.`, Async.it("Double reorg should NOT cause negative event counter (regression test)", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -2109,7 +2109,7 @@ This might be wrong after we start exposing a block hash for progress block.`, async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( @@ -2149,11 +2149,11 @@ This might be wrong after we start exposing a block hash for progress block.`, // but the non-reorg chain's counter stays at 0 while DB still has the old checkpoints. let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -2333,15 +2333,15 @@ This might be wrong after we start exposing a block hash for progress block.`, // causing non-reorg chains to go negative on the second rollback. let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let sourceMock137 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#137, + ~chainId=#137, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -2539,7 +2539,7 @@ This might be wrong after we start exposing a block hash for progress block.`, // 1. Setup mock source and indexer let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -2644,7 +2644,7 @@ This might be wrong after we start exposing a block hash for progress block.`, // Setup mock source and indexer let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ @@ -2772,11 +2772,11 @@ This might be wrong after we start exposing a block hash for progress block.`, async t => { let sourceMock1 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock2 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) // batchSize=1 ensures that chain 100's single event fills the batch, // causing chain 1337 to be SKIPPED during batch preparation. @@ -2932,11 +2932,11 @@ This might be wrong after we start exposing a block hash for progress block.`, let sourceMock1337 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let sourceMock100 = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#100, + ~chainId=#100, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/schema_types/BigDecimal_test.res b/scenarios/test_codegen/test/schema_types/BigDecimal_test.res index 640e69c0bd..8aa58e3c24 100644 --- a/scenarios/test_codegen/test/schema_types/BigDecimal_test.res +++ b/scenarios/test_codegen/test/schema_types/BigDecimal_test.res @@ -4,7 +4,7 @@ describe("Load and save an entity with a BigDecimal from DB", () => { Async.it("be able to set and read entities with BigDecimal from DB", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ diff --git a/scenarios/test_codegen/test/schema_types/Timestamp_test.res b/scenarios/test_codegen/test/schema_types/Timestamp_test.res index 96b24682be..592a801b06 100644 --- a/scenarios/test_codegen/test/schema_types/Timestamp_test.res +++ b/scenarios/test_codegen/test/schema_types/Timestamp_test.res @@ -4,7 +4,7 @@ describe("Load and save an entity with a Timestamp from DB", () => { Async.it("be able to set and read entities with Timestamp from DB", async t => { let sourceMock = MockIndexer.Source.make( [#getHeightOrThrow, #getItemsOrThrow, #getBlockHashes], - ~chain=#1337, + ~chainId=#1337, ) let indexerMock = await MockIndexer.Indexer.make( ~chains=[ From 191eece7ea125827fd8c3f63832ecf477460c3c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 09:54:32 +0000 Subject: [PATCH 5/6] Omit chainIdMode when int32; move its codegen test to envio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Int32 is what every config predating the field implies, so serializing it changes the public config JSON — and with it the persisted envio_info fingerprint — for every existing small-id project, which would demand a reindex on upgrade for no reason. Skipping it on the default restores the six config-JSON snapshots to byte-identical with main. The generated ReScript chainId type was covered by a Rust unit test reaching into ProjectTemplate. `from_user_api` now returns the generated Indexer.res alongside the .d.ts it already returned — same parse, same `with_indexer_types` flag — so the assertion lives with the rest of the chain-id coverage in ChainIdMode_test.res and runs against the real NAPI boundary. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ECqzHdVS4cLxfCXPw8zpb6 --- .../cli/src/config_parsing/public_config.rs | 8 +++ .../src/hbs_templating/codegen_templates.rs | 46 ++---------- ...al_config_json_code_generated_for_evm.snap | 1 - ...l_config_json_code_generated_for_fuel.snap | 2 +- ...al_config_json_code_generated_for_svm.snap | 1 - ...nal_config_json_code_with_all_options.snap | 1 - ...son_code_with_lowercase_contract_name.snap | 1 - ...fig_json_code_with_multiple_contracts.snap | 2 +- ...al_config_json_code_with_no_contracts.snap | 1 - packages/cli/src/napi.rs | 18 +++-- .../test/lib_tests/ChainIdMode_test.res | 71 ++++++++++++++++++- packages/envio/src/Core.res | 1 + 12 files changed, 97 insertions(+), 56 deletions(-) diff --git a/packages/cli/src/config_parsing/public_config.rs b/packages/cli/src/config_parsing/public_config.rs index 3bd5e144f3..11f4b36ba1 100644 --- a/packages/cli/src/config_parsing/public_config.rs +++ b/packages/cli/src/config_parsing/public_config.rs @@ -20,6 +20,13 @@ fn is_false(v: &bool) -> bool { !v } +// Int32 is what every config predating the field implies, so omitting it keeps +// the JSON — and therefore the persisted envio_info fingerprint — byte-identical +// for small-id projects. +fn is_default_chain_id_mode(v: &ChainIdMode) -> bool { + matches!(v, ChainIdMode::Int32) +} + #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] pub(crate) struct PublicConfigJson<'a> { @@ -39,6 +46,7 @@ pub(crate) struct PublicConfigJson<'a> { save_full_history: bool, #[serde(skip_serializing_if = "is_false")] raw_events: bool, + #[serde(skip_serializing_if = "is_default_chain_id_mode")] chain_id_mode: ChainIdMode, storage: StorageConfig, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/packages/cli/src/hbs_templating/codegen_templates.rs b/packages/cli/src/hbs_templating/codegen_templates.rs index 7219f74b59..af9a77ab7c 100644 --- a/packages/cli/src/hbs_templating/codegen_templates.rs +++ b/packages/cli/src/hbs_templating/codegen_templates.rs @@ -977,6 +977,11 @@ impl ProjectTemplate { &self.envio_types_dts } + /// The generated `Indexer.res` contents (the project's ReScript surface). + pub fn indexer_code(&self) -> &str { + &self.indexer_code + } + pub fn generate_templates(&self, project_paths: &ParsedProjectPaths) -> Result<()> { // 1. `.envio/types.d.ts` — augments `envio` with project-derived // chains/contracts/entities/enums. @@ -3509,47 +3514,6 @@ type Vault { } } - #[test] - fn indexer_code_chain_id_type_follows_chain_id_mode() { - let yaml_for = |chain_id: &str| { - format!( - r#" -name: chain-id-mode -chains: - - id: {chain_id} - rpc: - url: https://rpc.example.test - for: sync - start_block: 0 -"# - ) - }; - let schema = "type Transfer {\n id: ID!\n}\n"; - let indexer_code_for = |chain_id: &str| { - let config = SystemConfig::parse_yaml( - &yaml_for(chain_id), - Some(schema), - &HashMap::new(), - &HashMap::new(), - false, - ) - .expect("config should parse"); - super::ProjectTemplate::from_config(&config) - .expect("project template") - .indexer_code - }; - - let int32 = indexer_code_for("2147483647"); - assert!(int32.contains("type chainId = [#2147483647]"), "{int32}"); - assert!(int32.contains("| #2147483647 => indexer.chains.\\\"2147483647\"")); - - // ReScript integer polyvariants can't hold an id above int32, so the - // wide config falls back to the opaque runtime representation. - let int64 = indexer_code_for("2494104990"); - assert!(int64.contains("type chainId = ChainId.t"), "{int64}"); - assert!(int64.contains("chainId->ChainId.toString"), "{int64}"); - } - #[test] fn internal_config_json_code_with_lowercase_contract_name() { let json = get_internal_config_json_helper("lowercase-contract-name.yaml"); diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap index 25b3fb06e2..0a52ef8444 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_evm.snap @@ -6,7 +6,6 @@ expression: json "version": "0.0.1-dev", "name": "config1", "description": "Gravatar for Ethereum", - "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap index 69d492e836..4468617d73 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_fuel.snap @@ -1,12 +1,12 @@ --- source: packages/cli/src/hbs_templating/codegen_templates.rs +assertion_line: 2761 expression: json --- { "version": "0.0.1-dev", "name": "Fuel indexer", "rollbackOnReorg": false, - "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap index 1274a4fa25..5c402b0fe5 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_generated_for_svm.snap @@ -5,7 +5,6 @@ expression: json { "version": "0.0.1-dev", "name": "Solana indexer", - "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap index 4ac8557c75..245ce17511 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_all_options.snap @@ -11,7 +11,6 @@ expression: json "rollbackOnReorg": false, "saveFullHistory": true, "rawEvents": true, - "chainIdMode": "int32", "storage": { "postgres": true, "clickhouse": true diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap index 2a3b488f6d..b814a47af4 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_lowercase_contract_name.snap @@ -6,7 +6,6 @@ expression: json "version": "0.0.1-dev", "name": "lowercase-contract-name", "description": "Test config with lowercase contract name", - "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap index 5672d187ee..07b9f8f04a 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_multiple_contracts.snap @@ -1,12 +1,12 @@ --- source: packages/cli/src/hbs_templating/codegen_templates.rs +assertion_line: 2794 expression: json --- { "version": "0.0.1-dev", "name": "config2", "description": "Gravatar for Ethereum", - "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap index aeb200df46..c56f15c982 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__internal_config_json_code_with_no_contracts.snap @@ -6,7 +6,6 @@ expression: json "version": "0.0.1-dev", "name": "config4", "description": "Gravatar for Ethereum", - "chainIdMode": "int32", "storage": { "postgres": true }, diff --git a/packages/cli/src/napi.rs b/packages/cli/src/napi.rs index 8955dd20ef..097d0601e1 100644 --- a/packages/cli/src/napi.rs +++ b/packages/cli/src/napi.rs @@ -12,8 +12,9 @@ pub struct FromUserApiOptions { pub schema: Option, pub env: Option>, pub files: Option>, - /// Also generate the `.envio/types.d.ts` contents, so a caller can - /// type-check handlers against the config's generated `indexer` surface. + /// Also generate the `.envio/types.d.ts` and `Indexer.res` contents, so a + /// caller can type-check handlers against the config's generated `indexer` + /// surface, or assert on the generated ReScript. pub with_indexer_types: Option, } @@ -24,6 +25,9 @@ pub struct FromUserApiResult { /// The generated `.envio/types.d.ts`, present only when /// `with_indexer_types` was requested. pub indexer_types: Option, + /// The generated `Indexer.res`, present only when `with_indexer_types` was + /// requested. Same production codegen output, from the same parse. + pub indexer_code: Option, } fn serialize_config_result(config: anyhow::Result) -> napi::Result { @@ -70,18 +74,22 @@ pub fn from_user_api( .to_public_config_json(false) .map_err(|e| napi::Error::from_reason(format!("Failed serializing config: {e}")))?; - let indexer_types = if options.with_indexer_types.unwrap_or(false) { + let (indexer_types, indexer_code) = if options.with_indexer_types.unwrap_or(false) { let template = ProjectTemplate::from_config(&config).map_err(|e| { napi::Error::from_reason(format!("Failed generating indexer types: {e:#}")) })?; - Some(template.indexer_types_dts().to_string()) + ( + Some(template.indexer_types_dts().to_string()), + Some(template.indexer_code().to_string()), + ) } else { - None + (None, None) }; Ok(FromUserApiResult { config: config_json, indexer_types, + indexer_code, }) } diff --git a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res index 130ab1d799..2a93a72181 100644 --- a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res +++ b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res @@ -94,6 +94,31 @@ describe("ChainIdMode resolution", () => { }) }) +describe("ChainIdMode public config JSON", () => { + let rawConfigJson = (~id) => + Core.fromUserApi( + ~schema, + ` +name: raw-json +chains: +${evmChain(~id)} +`, + ).config->JSON.parseOrThrow + + let chainIdModeKey = json => + switch json { + | JSON.Object(dict) => dict->Dict.get("chainIdMode") + | _ => None + } + + it("omits the key on Int32 so existing configs keep the same fingerprint", t => { + t.expect(( + rawConfigJson(~id="2147483647")->chainIdModeKey, + rawConfigJson(~id="2147483648")->chainIdModeKey, + )).toEqual((None, Some(JSON.String("int64")))) + }) +}) + describe("ChainIdMode Postgres schema", () => { it("keeps INTEGER chain-id columns for small-id projects", t => { t.expect(( @@ -185,6 +210,41 @@ describe("ChainId runtime representation", () => { }) }) +// The ReScript `chainId` type is the one generated surface that changes with +// the mode, so assert on the generated code rather than the config JSON. +let generatedRescript = (~id) => + Core.fromUserApi( + ~schema, + ~withIndexerTypes=true, + ` +name: generated-rescript +chains: +${evmChain(~id)} +`, + ).indexerCode->Null.getOrThrow + +describe("ChainIdMode generated ReScript surface", () => { + it("keeps the polyvariant union and its exhaustive match for int32 ids", t => { + let code = generatedRescript(~id="2147483647") + t.expect(( + code->String.includes("type chainId = [#2147483647]"), + code->String.includes("switch chainId {\n | #2147483647 => indexer.chains."), + code->String.includes("type chainId = ChainId.t"), + )).toEqual((true, true, false)) + }) + + it("falls back to ChainId.t when an id exceeds int32", t => { + // ReScript integer polyvariants are int32-bound, so a wide config can't use + // them — getChainById becomes a keyed lookup instead of an exhaustive match. + let code = generatedRescript(~id="2494104990") + t.expect(( + code->String.includes("type chainId = ChainId.t"), + code->String.includes("->Dict.get(chainId->ChainId.toString)"), + code->String.includes("type chainId = [#"), + )).toEqual((true, true, false)) + }) +}) + describe("ChainIdMode generated TypeScript surface", () => { it("keeps chain.id a number and the id union a numeric literal union", _ => InternalTestIndexer.fromUserApi( @@ -207,9 +267,14 @@ expectType(indexer.chains[2494104990].id); describe("ChainIdMode compat check", () => { it("reports a stored/current mode mismatch on its own", t => { - let stored = `{"version": "1.0.0", "chainIdMode": "int32", "name": "demo"}`->JSON.parseOrThrow - let current = `{"version": "1.0.0", "chainIdMode": "int64", "name": "demo"}`->JSON.parseOrThrow - t.expect(Config.diffPaths(~stored, ~current)).toEqual(["chainIdMode"]) + // Int32 omits the key entirely, so widening shows up as an added key. + let int32 = `{"version": "1.0.0", "name": "demo"}`->JSON.parseOrThrow + let int64 = `{"version": "1.0.0", "chainIdMode": "int64", "name": "demo"}`->JSON.parseOrThrow + t.expect(( + Config.diffPaths(~stored=int32, ~current=int64), + Config.diffPaths(~stored=int64, ~current=int32), + Config.diffPaths(~stored=int32, ~current=int32), + )).toEqual((["chainIdMode"], ["chainIdMode"], [])) }) it("fails the resume with the standard incompatible-config message", t => { diff --git a/packages/envio/src/Core.res b/packages/envio/src/Core.res index 85dfed5c1b..a266471b54 100644 --- a/packages/envio/src/Core.res +++ b/packages/envio/src/Core.res @@ -20,6 +20,7 @@ type fromUserApiOptions = { type fromUserApiResult = { config: string, indexerTypes: Null.t, + indexerCode: Null.t, } type addon = { From a8dd631c2e1f08602834e309a4971b273f9acce3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 11:45:52 +0000 Subject: [PATCH 6/6] Count skipped chains in the mode, and type context.chain.id as chainId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects from review. Mode resolution filtered out skipped chains while codegen emits a `chainId` case for every chain in config.yaml. An active chain 1 beside a skipped 2494104990 resolved Int32 and then emitted `#2494104990` — an int polyvariant ReScript can't represent — and a skipped id above MAX_SAFE_INTEGER skipped validation entirely. Resolution now covers every configured chain, which also keeps the physical column types stable when a chain is skipped and unskipped. `context.chain.id` was `int`, reached through an identity cast from the float-backed representation. ReScript ints are 32-bit, so that type was wrong for exactly the ids this branch adds support for — a handler can't even write `chain.id == 2494104990`, the literal is out of range. The generated Indexer.res now declares its own `handlerChain` using the generated `chainId`, matching `contractRegisterChain` and `indexerChain`, and `Internal.chainInfo` goes back to the internal `ChainId.t`. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ECqzHdVS4cLxfCXPw8zpb6 --- .../cli/src/config_parsing/system_config.rs | 11 ++- .../src/hbs_templating/codegen_templates.rs | 14 +++- ..._test__indexer_code_generated_for_svm.snap | 14 +++- ...de_generates_correct_types_and_values.snap | 14 +++- ...s__test__indexer_code_multiple_chains.snap | 14 +++- .../test/lib_tests/ChainIdMode_test.res | 68 +++++++++++++++++++ packages/envio/src/EventProcessing.res | 2 +- packages/envio/src/Internal.res | 3 +- scenarios/fuel_test/src/Indexer.res | 14 +++- scenarios/svm_test/src/Indexer.res | 14 +++- scenarios/test_codegen/src/Indexer.res | 14 +++- .../src/handlers/EventHandlers.res | 4 +- .../test_codegen/test/EventOrigin_test.res | 12 ++-- .../test_codegen/test/HandlerTypes_test.res | 7 +- 14 files changed, 164 insertions(+), 41 deletions(-) diff --git a/packages/cli/src/config_parsing/system_config.rs b/packages/cli/src/config_parsing/system_config.rs index 35f5fd7cb7..c7235bdb05 100644 --- a/packages/cli/src/config_parsing/system_config.rs +++ b/packages/cli/src/config_parsing/system_config.rs @@ -315,13 +315,12 @@ pub enum ChainIdMode { pub const MAX_SAFE_CHAIN_ID: u64 = 9_007_199_254_740_991; impl ChainIdMode { + /// Skipped chains count: codegen emits a `chainId` case for every chain in + /// config.yaml regardless of `skip`, so a skipped wide id still has to be + /// representable. Including them also keeps the mode — and therefore the + /// physical column types — stable when a chain is skipped and unskipped. fn resolve(chains: &ChainMap) -> Result { - let max_id = chains - .values() - .filter(|chain| !chain.skip) - .map(|chain| chain.id) - .max() - .unwrap_or(0); + let max_id = chains.values().map(|chain| chain.id).max().unwrap_or(0); if max_id > MAX_SAFE_CHAIN_ID { return Err(anyhow!( "Chain id {max_id} is above the maximum supported chain id \ diff --git a/packages/cli/src/hbs_templating/codegen_templates.rs b/packages/cli/src/hbs_templating/codegen_templates.rs index af9a77ab7c..c4dde7f176 100644 --- a/packages/cli/src/hbs_templating/codegen_templates.rs +++ b/packages/cli/src/hbs_templating/codegen_templates.rs @@ -1574,11 +1574,19 @@ type handlerEntityOperationsWithCustomId<'entity, 'id, 'getWhereFilter> = { deleteUnsafe: string => unit, }}{custom_id_handler_ops_code} +/** The chain the event being handled belongs to. */ +type handlerChain = {{ + /** The unique identifier of the blockchain network where this event occurred. */ + id: chainId, + /** Whether all chains have entered real-time indexing mode (caught up to head, or reached their configured endBlock for finite-range indexers). */ + isRealtime: bool, +}} + type handlerContext = {{ log: Envio.logger, effect: 'input 'output. (Envio.effect<'input, 'output>, 'input) => promise<'output>, isPreload: bool, - chain: Internal.chainInfo, + chain: handlerChain, {handler_context_entity_fields} }}"#, ); @@ -1825,10 +1833,10 @@ module Entities = {{ {entities_module_code} }} -{handler_context_code} - {chain_id_type} +{handler_context_code} + type contractRegisterContract = {{ add: Address.t => unit }} type contractRegisterChain = {{ diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generated_for_svm.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generated_for_svm.snap index 270192ae14..66a387096c 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generated_for_svm.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generated_for_svm.snap @@ -89,6 +89,8 @@ module Entities = { | @as("EmptyEntity") EmptyEntity: name } +type chainId = [#0] + type handlerEntityOperations<'entity, 'getWhereFilter> = { get: string => promise>, getOrThrow: (string, ~message: string=?) => promise<'entity>, @@ -98,16 +100,22 @@ type handlerEntityOperations<'entity, 'getWhereFilter> = { deleteUnsafe: string => unit, } +/** The chain the event being handled belongs to. */ +type handlerChain = { + /** The unique identifier of the blockchain network where this event occurred. */ + id: chainId, + /** Whether all chains have entered real-time indexing mode (caught up to head, or reached their configured endBlock for finite-range indexers). */ + isRealtime: bool, +} + type handlerContext = { log: Envio.logger, effect: 'input 'output. (Envio.effect<'input, 'output>, 'input) => promise<'output>, isPreload: bool, - chain: Internal.chainInfo, + chain: handlerChain, \"EmptyEntity": handlerEntityOperations, } -type chainId = [#0] - type contractRegisterContract = { add: Address.t => unit } type contractRegisterChain = { diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generates_correct_types_and_values.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generates_correct_types_and_values.snap index ad0a6c9e77..cc80e1f10e 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generates_correct_types_and_values.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_generates_correct_types_and_values.snap @@ -218,6 +218,8 @@ module Entities = { | @as("RelatedEntity") RelatedEntity: name } +type chainId = [#1] + type handlerEntityOperations<'entity, 'getWhereFilter> = { get: string => promise>, getOrThrow: (string, ~message: string=?) => promise<'entity>, @@ -227,17 +229,23 @@ type handlerEntityOperations<'entity, 'getWhereFilter> = { deleteUnsafe: string => unit, } +/** The chain the event being handled belongs to. */ +type handlerChain = { + /** The unique identifier of the blockchain network where this event occurred. */ + id: chainId, + /** Whether all chains have entered real-time indexing mode (caught up to head, or reached their configured endBlock for finite-range indexers). */ + isRealtime: bool, +} + type handlerContext = { log: Envio.logger, effect: 'input 'output. (Envio.effect<'input, 'output>, 'input) => promise<'output>, isPreload: bool, - chain: Internal.chainInfo, + chain: handlerChain, \"EmptyEntity": handlerEntityOperations, \"RelatedEntity": handlerEntityOperations, } -type chainId = [#1] - type contractRegisterContract = { add: Address.t => unit } type contractRegisterChain = { diff --git a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_multiple_chains.snap b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_multiple_chains.snap index 0c254ef8c5..e8a9ba4fd0 100644 --- a/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_multiple_chains.snap +++ b/packages/cli/src/hbs_templating/snapshots/envio__hbs_templating__codegen_templates__test__indexer_code_multiple_chains.snap @@ -206,6 +206,8 @@ module Entities = { | @as("EmptyEntity") EmptyEntity: name } +type chainId = [#1 | #2] + type handlerEntityOperations<'entity, 'getWhereFilter> = { get: string => promise>, getOrThrow: (string, ~message: string=?) => promise<'entity>, @@ -215,16 +217,22 @@ type handlerEntityOperations<'entity, 'getWhereFilter> = { deleteUnsafe: string => unit, } +/** The chain the event being handled belongs to. */ +type handlerChain = { + /** The unique identifier of the blockchain network where this event occurred. */ + id: chainId, + /** Whether all chains have entered real-time indexing mode (caught up to head, or reached their configured endBlock for finite-range indexers). */ + isRealtime: bool, +} + type handlerContext = { log: Envio.logger, effect: 'input 'output. (Envio.effect<'input, 'output>, 'input) => promise<'output>, isPreload: bool, - chain: Internal.chainInfo, + chain: handlerChain, \"EmptyEntity": handlerEntityOperations, } -type chainId = [#1 | #2] - type contractRegisterContract = { add: Address.t => unit } type contractRegisterChain = { diff --git a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res index 2a93a72181..506d9d2b71 100644 --- a/packages/envio-tests/test/lib_tests/ChainIdMode_test.res +++ b/packages/envio-tests/test/lib_tests/ChainIdMode_test.res @@ -87,6 +87,31 @@ describe("ChainIdMode resolution", () => { ) }) + it("counts skipped chains, which codegen still emits a chainId case for", t => { + let skippedWide = ` - id: 1 + rpc: https://rpc.example.test + start_block: 0 + - id: 2494104990 + skip: true + rpc: https://rpc.example.test + start_block: 0` + t.expect(parse(~name="skipped-wide", ~chains=skippedWide).chainIdMode).toEqual(ChainId.Int64) + }) + + it("validates skipped chain ids too", t => { + let skippedTooBig = ` - id: 1 + rpc: https://rpc.example.test + start_block: 0 + - id: 9007199254740992 + skip: true + rpc: https://rpc.example.test + start_block: 0` + t->toThrowErrorEqual( + () => parse(~name="skipped-too-big", ~chains=skippedTooBig)->ignore, + "Config parse error: Chain id 9007199254740992 is above the maximum supported chain id 9007199254740991 (Number.MAX_SAFE_INTEGER).", + ) + }) + it("resolves the mode from the widest chain, not the first one", t => { t.expect( parse(~name="wide-second", ~chains=evmChain(~id="1") ++ "\n" ++ evmChain(~id="2147483648")).chainIdMode, @@ -233,6 +258,31 @@ describe("ChainIdMode generated ReScript surface", () => { )).toEqual((true, true, false)) }) + it("uses ChainId.t when only a skipped chain is wide", t => { + // Skipped chains still get a `chainId` case, so `#2494104990` would be an + // out-of-range int polyvariant if the mode ignored them. + let code = + Core.fromUserApi( + ~schema, + ~withIndexerTypes=true, + ` +name: skipped-wide-rescript +chains: + - id: 1 + rpc: https://rpc.example.test + start_block: 0 + - id: 2494104990 + skip: true + rpc: https://rpc.example.test + start_block: 0 +`, + ).indexerCode->Null.getOrThrow + t.expect(( + code->String.includes("type chainId = ChainId.t"), + code->String.includes("#2494104990"), + )).toEqual((true, false)) + }) + it("falls back to ChainId.t when an id exceeds int32", t => { // ReScript integer polyvariants are int32-bound, so a wide config can't use // them — getChainById becomes a keyed lookup instead of an exhaustive match. @@ -265,6 +315,24 @@ expectType(indexer.chains[2494104990].id); ) }) +describe("ChainIdMode generated handler context", () => { + it("types context.chain.id as the generated chainId, not int", _ => + InternalTestIndexer.fromUserApi( + ~schema, + ~configYaml=` +name: wide-handler-context +${"chains:\n" ++ evmChain(~id="2494104990")} +`, + ~handlers=` +import { expectType, type TypeEqual } from "ts-expect"; +import type { EvmOnEventContext } from "envio"; + +expectType>(true); +`, + )->ignore + ) +}) + describe("ChainIdMode compat check", () => { it("reports a stored/current mode mismatch on its own", t => { // Int32 omits the key entirely, so widening shows up as an added key. diff --git a/packages/envio/src/EventProcessing.res b/packages/envio/src/EventProcessing.res index b6f47ba7e7..843b443607 100644 --- a/packages/envio/src/EventProcessing.res +++ b/packages/envio/src/EventProcessing.res @@ -15,7 +15,7 @@ let computeChainsState = (chainStates: dict): Internal.chains => { chains->Dict.set( chainId->ChainId.toString, { - Internal.id: chainId->ChainId.toInt, + Internal.id: chainId, isRealtime, }, ) diff --git a/packages/envio/src/Internal.res b/packages/envio/src/Internal.res index f430612060..3550aeaf71 100644 --- a/packages/envio/src/Internal.res +++ b/packages/envio/src/Internal.res @@ -364,8 +364,7 @@ type entityHandlerContext<'entity> = { } type chainInfo = { - // `int` rather than `ChainId.t`: this is the handler-facing `context.chain`. - id: int, + id: ChainId.t, // True once every chain has caught up to head/endBlock and entered real-time // indexing mode. False while any chain is still backfilling. isRealtime: bool, diff --git a/scenarios/fuel_test/src/Indexer.res b/scenarios/fuel_test/src/Indexer.res index 1400d05095..c3039fbf51 100644 --- a/scenarios/fuel_test/src/Indexer.res +++ b/scenarios/fuel_test/src/Indexer.res @@ -91,6 +91,8 @@ module Entities = { | @as("User") User: name } +type chainId = [#0] + type handlerEntityOperations<'entity, 'getWhereFilter> = { get: string => promise>, getOrThrow: (string, ~message: string=?) => promise<'entity>, @@ -100,16 +102,22 @@ type handlerEntityOperations<'entity, 'getWhereFilter> = { deleteUnsafe: string => unit, } +/** The chain the event being handled belongs to. */ +type handlerChain = { + /** The unique identifier of the blockchain network where this event occurred. */ + id: chainId, + /** Whether all chains have entered real-time indexing mode (caught up to head, or reached their configured endBlock for finite-range indexers). */ + isRealtime: bool, +} + type handlerContext = { log: Envio.logger, effect: 'input 'output. (Envio.effect<'input, 'output>, 'input) => promise<'output>, isPreload: bool, - chain: Internal.chainInfo, + chain: handlerChain, \"User": handlerEntityOperations, } -type chainId = [#0] - type contractRegisterContract = { add: Address.t => unit } type contractRegisterChain = { diff --git a/scenarios/svm_test/src/Indexer.res b/scenarios/svm_test/src/Indexer.res index e930923057..00d74a99e1 100644 --- a/scenarios/svm_test/src/Indexer.res +++ b/scenarios/svm_test/src/Indexer.res @@ -85,6 +85,8 @@ module Entities = { | @as("SlotPing") SlotPing: name } +type chainId = [#0] + type handlerEntityOperations<'entity, 'getWhereFilter> = { get: string => promise>, getOrThrow: (string, ~message: string=?) => promise<'entity>, @@ -94,16 +96,22 @@ type handlerEntityOperations<'entity, 'getWhereFilter> = { deleteUnsafe: string => unit, } +/** The chain the event being handled belongs to. */ +type handlerChain = { + /** The unique identifier of the blockchain network where this event occurred. */ + id: chainId, + /** Whether all chains have entered real-time indexing mode (caught up to head, or reached their configured endBlock for finite-range indexers). */ + isRealtime: bool, +} + type handlerContext = { log: Envio.logger, effect: 'input 'output. (Envio.effect<'input, 'output>, 'input) => promise<'output>, isPreload: bool, - chain: Internal.chainInfo, + chain: handlerChain, \"SlotPing": handlerEntityOperations, } -type chainId = [#0] - type contractRegisterContract = { add: Address.t => unit } type contractRegisterChain = { diff --git a/scenarios/test_codegen/src/Indexer.res b/scenarios/test_codegen/src/Indexer.res index a0cb21184c..b4ebd9d01f 100644 --- a/scenarios/test_codegen/src/Indexer.res +++ b/scenarios/test_codegen/src/Indexer.res @@ -370,6 +370,8 @@ module Entities = { | @as("User") User: name } +type chainId = [#1337 | #1 | #100 | #137] + type handlerEntityOperations<'entity, 'getWhereFilter> = { get: string => promise>, getOrThrow: (string, ~message: string=?) => promise<'entity>, @@ -388,11 +390,19 @@ type handlerEntityOperationsWithCustomId<'entity, 'id, 'getWhereFilter> = { deleteUnsafe: 'id => unit, } +/** The chain the event being handled belongs to. */ +type handlerChain = { + /** The unique identifier of the blockchain network where this event occurred. */ + id: chainId, + /** Whether all chains have entered real-time indexing mode (caught up to head, or reached their configured endBlock for finite-range indexers). */ + isRealtime: bool, +} + type handlerContext = { log: Envio.logger, effect: 'input 'output. (Envio.effect<'input, 'output>, 'input) => promise<'output>, isPreload: bool, - chain: Internal.chainInfo, + chain: handlerChain, \"A": handlerEntityOperations, \"B": handlerEntityOperations, \"BigIntIdEntity": handlerEntityOperationsWithCustomId, @@ -416,8 +426,6 @@ type handlerContext = { \"User": handlerEntityOperations, } -type chainId = [#1337 | #1 | #100 | #137] - type contractRegisterContract = { add: Address.t => unit } type contractRegisterChain = { diff --git a/scenarios/test_codegen/src/handlers/EventHandlers.res b/scenarios/test_codegen/src/handlers/EventHandlers.res index 754cec3ada..ec44e241bb 100644 --- a/scenarios/test_codegen/src/handlers/EventHandlers.res +++ b/scenarios/test_codegen/src/handlers/EventHandlers.res @@ -162,7 +162,7 @@ Indexer.indexer.onEvent({event: Indexer.Gravatar(TestEvent)}, async _ => { }) // Test chain accessibility - exposed for testing -let lastEmptyEventChain: ref> = ref(None) +let lastEmptyEventChain: ref> = ref(None) Indexer.indexer.onEvent({event: Indexer.Gravatar(EmptyEvent)}, async ({context}) => { // This handler tests that chain state is accessible in the context @@ -171,5 +171,5 @@ Indexer.indexer.onEvent({event: Indexer.Gravatar(EmptyEvent)}, async ({context}) // Log chain state for verification let status = context.chain.isRealtime ? "ready (realtime)" : "syncing (historical)" - context.log.debug(`Chain ${context.chain.id->Int.toString} status: ${status}`) + context.log.debug(`Chain ${(context.chain.id :> int)->Int.toString} status: ${status}`) }) diff --git a/scenarios/test_codegen/test/EventOrigin_test.res b/scenarios/test_codegen/test/EventOrigin_test.res index cf33aaeefb..12601e0799 100644 --- a/scenarios/test_codegen/test/EventOrigin_test.res +++ b/scenarios/test_codegen/test/EventOrigin_test.res @@ -5,7 +5,7 @@ describe("Chains State", () => { it( "should have isRealtime field set to false", t => { - let chainInfo: Internal.chainInfo = {id: 1, isRealtime: false} + let chainInfo: Internal.chainInfo = {id: 1->ChainId.fromInt, isRealtime: false} t.expect(chainInfo.isRealtime).toBe(false) }, ) @@ -13,7 +13,7 @@ describe("Chains State", () => { it( "should have isRealtime field set to true", t => { - let chainInfo: Internal.chainInfo = {id: 1, isRealtime: true} + let chainInfo: Internal.chainInfo = {id: 1->ChainId.fromInt, isRealtime: true} t.expect(chainInfo.isRealtime).toBe(true) }, ) @@ -24,8 +24,8 @@ describe("Chains State", () => { "should support multiple chains with different states", t => { let chains: Internal.chains = Dict.make() - chains->Dict.set("1", {Internal.id: 1, isRealtime: false}) - chains->Dict.set("2", {Internal.id: 2, isRealtime: true}) + chains->Dict.set("1", {Internal.id: 1->ChainId.fromInt, isRealtime: false}) + chains->Dict.set("2", {Internal.id: 2->ChainId.fromInt, isRealtime: true}) t.expect(chains->Dict.get("1")->Option.map(c => c.isRealtime)).toBe(Some(false)) t.expect(chains->Dict.get("2")->Option.map(c => c.isRealtime)).toBe(Some(true)) @@ -45,7 +45,7 @@ describe("Chains State", () => { let item = MockEvents.newGravatarLog1->MockEvents.newGravatarEventToBatchItem let chains = Dict.make() - chains->Dict.set("1337", {Internal.id: 1337, isRealtime: false}) + chains->Dict.set("1337", {Internal.id: 1337->ChainId.fromInt, isRealtime: false}) let handlerContext = UserContext.getHandlerContext({ item, @@ -63,7 +63,7 @@ describe("Chains State", () => { // Verify we can access current event's chain info t.expect(handlerContext.chain.isRealtime).toBe(false) - t.expect(handlerContext.chain.id).toBe(1337) + t.expect(handlerContext.chain.id).toBe(1337->ChainId.fromInt) }, ) }) diff --git a/scenarios/test_codegen/test/HandlerTypes_test.res b/scenarios/test_codegen/test/HandlerTypes_test.res index 1869659984..0887ac0c37 100644 --- a/scenarios/test_codegen/test/HandlerTypes_test.res +++ b/scenarios/test_codegen/test/HandlerTypes_test.res @@ -78,9 +78,10 @@ let _wildcardWithFilterConfig: Indexer.onEventOptions< // 4. handlerContext (onEvent context) has expected fields let _checkHandlerContext = (ctx: Indexer.handlerContext) => { let _: bool = ctx.isPreload - let chainInfo: Internal.chainInfo = ctx.chain - let _: int = chainInfo.id - let _: bool = chainInfo.isRealtime + let chain: Indexer.handlerChain = ctx.chain + // The generated chainId, not `int` — an id above 2^31-1 isn't a ReScript int. + let _: Indexer.chainId = chain.id + let _: bool = chain.isRealtime let _: Envio.logger = ctx.log }