feat(chain): cap JSON-RPC response size before lowering into the guest#354
Merged
Conversation
#154) Adds a configurable `[limits.chain] response_max_bytes` knob (default 1 MiB) that is enforced host-side in `chain::request()` before the response string is copied into the guest heap. Oversized responses are rejected with an `InvalidInput` fault, a `WARN` log, and a dedicated `shepherd_chain_response_capped_total` metric.
Review follow-ups on the chain response cap: - request_batch: the per-entry cap bounds each body, but the aggregate Vec<RpcResult> lowered into guest memory was unbounded - ~64 entries just under the cap is ~64 MiB, exactly the guest-heap saturation issue #154 exists to prevent, reachable via the block-range chunking the issue itself recommends. A running total now converts entries past the cap into typed invalid-input results. - config: renamed [limits.chain].response_max_bytes to response_body_max_bytes (u64) for exact symmetry with the [limits.http] sibling; a degenerate 0 saturates to 1, matching the logs/poison zero handling. - new harness test drives the cap through the real path - config -> ModuleLimits -> HostState -> chain::request - with the price-alert module: an over-cap oracle response surfaces to the guest as the typed fault and never reaches classify. The existing unit tests only covered the free function, so a deleted and_then would have passed the suite. - TOML parse tests for [limits.chain] (absent -> 1 MiB default, override, zero saturation), mirroring the HTTP section's tests. - engine.example.toml documents the new section; warn message dash matches house style.
d497832 to
41a50d5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a host-side cap on chain JSON-RPC response bodies so a misbehaving or adversarial node cannot saturate the guest heap via a single large reply.
engine_config.rsgains a new[limits.chain]TOML section with aresponse_body_max_bytesknob (default 1 MiB), resolved throughModuleLimits::chain_response_max_bytes()with the same zero-saturates-to-one handling as thehttp/logs/poisonsections.HostStatecarries achain_response_max_bytes: usize, threaded from config at boot insupervisor.rsand preserved across restarts viaLoadedModule.host/impls/chain.rsappliescheck_response_cap()torequest()after receiving the provider response; a rejection logs a warning, increments theshepherd_chain_response_capped_totalmetric, and returnsChainError::Fault(InvalidInput). Therequest-batchpath also tracks a running aggregate total against the same cap, so a wide batch of individually-legal bodies cannot lower an unboundedVec<RpcResult>into guest memory in one go.Subscription-delivered payloads (
dispatch_chain_log) are not covered by this cap; that path lowerseth_subscribenotifications rather thanchain::requestresponses, and bounding it is left as a follow-up rather than widened into this PR.Why
A chain node (or anything on-path to it) that returns an oversized JSON-RPC response body can otherwise force the guest to buffer an arbitrarily large payload, giving an adversarial or misbehaving node a way to exhaust guest memory. Capping the response host-side, before it is copied into the guest, closes that gap the same way the existing
[limits.http]cap does for outbound HTTP.Testing
chain_limits_default_when_absent,chain_limits_parse_with_override,chain_limits_saturate_degenerate_zeroinengine_config.rscover config resolution.response_at_cap_is_acceptedandresponse_over_cap_returns_invalid_inputinhost::impls::chain::testscover the per-entry cap boundary.test_utils/harness.rsdrives the cap through the realconfig -> HostState -> chain::requestwiring with the price-alert module.engine.example.tomldocuments the new[limits.chain]section.Closes #154