Skip to content

Per chain rpc settings#6459

Open
dimitrovmaksim wants to merge 3 commits intographprotocol:masterfrom
dimitrovmaksim:feat/per-chain-rpc-settings
Open

Per chain rpc settings#6459
dimitrovmaksim wants to merge 3 commits intographprotocol:masterfrom
dimitrovmaksim:feat/per-chain-rpc-settings

Conversation

@dimitrovmaksim
Copy link
Copy Markdown
Member

@dimitrovmaksim dimitrovmaksim commented Mar 26, 2026

Per-chain RPC settings via TOML config

What this PR does:

  • Introduce a ChainSettings struct that holds all per-chain tuning parameters, parsed from each [chains.] TOML section. Settings not specified in the config fall back to the existing environment variables.
[chains.mainnet]
shard = "primary"
json_rpc_timeout = 300      # seconds; falls back to GRAPH_ETHEREUM_JSON_RPC_TIMEOUT
request_retries = 15
max_block_range_size = 2000
provider = [...]

[chains.polygon]
shard = "primary"
polling_interval = 1000     # ms; falls back to ETHEREUM_POLLING_INTERVAL
block_batch_size = 20
provider = [...]

Backwards compatibility

Fully backwards compatible. All 11 settings default to their corresponding environment variable values, so no TOML changes are required. The env vars remain the global baseline.

Introduce a ChainSettings struct that holds Ethereum RPC tuning
parameters (timeouts, retries, batch sizes, block ranges, etc.)
per chain instead of reading them from global ENV_VARS. Settings
are parsed from [chains.<name>] TOML sections with serde defaults
falling back to the existing environment variables, preserving full
backwards compatibility.

Thread ChainSettings through Chain, EthereumAdapter, and the
free receipt-fetching functions so each chain can be independently
tuned.
Add the full list of per-chain Ethereum RPC tuning settings to
docs/config.md with env var fallback defaults, and add a
cross-reference note in docs/environment-variables.md.
Reject invalid ChainSettings values at startup: max_block_range_size
and max_event_only_range must be positive (i32), and block_batch_size,
block_ptr_batch_size, block_ingestor_max_concurrent_json_rpc_calls,
and get_logs_max_contracts must be non-zero (used as .buffered() args
or filter batch limits where 0 would panic or silently break).
@dimitrovmaksim dimitrovmaksim marked this pull request as ready for review March 26, 2026 16:30
@dimitrovmaksim dimitrovmaksim changed the title Feat/per chain rpc settings Per chain rpc settings Mar 27, 2026
Comment on lines +150 to +180
impl From<ConfigChainSettings> for ChainSettings {
fn from(c: ConfigChainSettings) -> Self {
let ConfigChainSettings {
polling_interval,
json_rpc_timeout,
request_retries,
max_block_range_size,
block_batch_size,
block_ptr_batch_size,
max_event_only_range,
target_triggers_per_block_range,
get_logs_max_contracts,
block_ingestor_max_concurrent_json_rpc_calls,
genesis_block_number,
} = c;
ChainSettings {
polling_interval,
json_rpc_timeout,
request_retries,
max_block_range_size,
block_batch_size,
block_ptr_batch_size,
max_event_only_range,
target_triggers_per_block_range,
get_logs_max_contracts,
block_ingestor_max_concurrent_json_rpc_calls,
genesis_block_number,
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor question: ethereum::ChainSettings and config::ChainSettings seems to be exactly the same thing, could we reuse the struct? since this chain settings is very specific to ethereum should we just use the from ethereum crate. what do you think? ChainSettings seems to be just ethereum specific.

Copy link
Copy Markdown
Member Author

@dimitrovmaksim dimitrovmaksim Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't like this as well, but I left them separate for separation of concern. The config:: one to deal with the parsing, etc, the ethereum:: one for all the rest. To combine them i need to move all the serde stuff and defaults in ethereum:: which is new concern and not related to it's logic. The other way around will create a circular dependency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, alright. I was thinking maybe moving it to graph crate but that also doesnt work. Lets keep it as it is then

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving it to graph crate could work, I'll look into it.

Comment on lines +714 to +716
self.settings
.validate()
.map_err(|e| anyhow!("chain {}: {}", name, e))?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Currently this runs for near too. Should just be ethereum ?

Copy link
Copy Markdown
Member Author

@dimitrovmaksim dimitrovmaksim Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it will run for all chains regardless of protocol, but validations should pass as the values will default to the ENV var defaults, so unless someone manually sets any of the setting to 0 for non ethereum chains it should be fine. But I can wrap it in

if self.protocol == BlockchainKind::Ethereum {
...
}

#[serde(default = "default_blockchain_kind")]
pub protocol: BlockchainKind,
pub struct ChainSettings {
/// Set by `polling_interval` (milliseconds). Defaults to `GRAPH_ETHEREUM_POLLING_INTERVAL`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Set by `polling_interval` (milliseconds). Defaults to `GRAPH_ETHEREUM_POLLING_INTERVAL`.
/// Set by `polling_interval` (milliseconds). Defaults to `ETHEREUM_POLLING_INTERVAL`.

details):

- `polling_interval`: block ingestor polling interval in milliseconds.
Default: `ETHEREUM_POLLING_INTERVAL` (500ms).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: Default is actually 1000ms, its a pre-existing doc bug

Comment on lines +654 to +676
fn validate(&self) -> Result<()> {
anyhow::ensure!(
self.max_block_range_size > 0,
"max_block_range_size must be > 0"
);
anyhow::ensure!(
self.max_event_only_range > 0,
"max_event_only_range must be > 0"
);
anyhow::ensure!(self.block_batch_size > 0, "block_batch_size must be > 0");
anyhow::ensure!(
self.block_ptr_batch_size > 0,
"block_ptr_batch_size must be > 0"
);
anyhow::ensure!(
self.block_ingestor_max_concurrent_json_rpc_calls > 0,
"block_ingestor_max_concurrent_json_rpc_calls must be > 0"
);
anyhow::ensure!(
self.get_logs_max_contracts > 0,
"get_logs_max_contracts must be > 0"
);
Ok(())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're at it, request_retries, json_rpc_timeout, target_triggers_per_block_range, and polling_interval also need zero-value validation. These weren't validated before either, but since validate() exists now it'd be good to add it too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants