feat(rpc): paginate EXPERIMENTAL_view_state queries#15743
Conversation
| fn parse(value: Value) -> Result<Self, RpcParseError> { | ||
| Params::parse(value) | ||
| let request: Self = Params::parse(value)?; | ||
| // TODO(#15612): a resumed page seeks with AccessOptions::NO_SIDE_EFFECTS, so it |
There was a problem hiding this comment.
needs issue number replaced here
Trisfald
left a comment
There was a problem hiding this comment.
Thanks for the PR, looks solid overall.
There are just a few comments I'd like you to take a look at before merging.
| }); | ||
| } | ||
| let paginated = limit.is_some() || from_key.is_some(); | ||
| debug_assert!(!(paginated && include_proof), "include_proof unsupported with pagination"); |
There was a problem hiding this comment.
The debug_assert! here is a no-op in release.
I think an user can hit this condition from any query request with include_proof: true and from_key_base64 set.
I suggest replacing the assert with a real error (ViewStateError::ProofUnsupportedWithPagination or similar), to avoid the panic in debug and the broken proof in release.
Worth adding a regression test too. Similar to test_experimental_view_state_proof_with_pagination_rejected but using client.query(...) with QueryRequest::ViewState.
| Some(from_key) => { | ||
| let mut full = query[..acc_sep_len].to_vec(); | ||
| full.extend_from_slice(from_key); | ||
| iter.seek(Bound::Excluded(full))?; |
There was a problem hiding this comment.
If a caller passes from_key < prefix, the seek may land on a key below the prefix region, the starts_with(&query) check fails, and we return an empty page even when matching keys exist.
Two possible improvements:
- Validating
from_key.starts_with(prefix)and returning an error - Documenting the precondition with a
///doc comment on thefrom_keyfield inRpcViewStateRequestandQueryRequest::ViewState
There was a problem hiding this comment.
added a dedicated error
|
Could you also add a |
There was a problem hiding this comment.
Pull request overview
Adds pagination to the view_state / EXPERIMENTAL_view_state JSON-RPC queries to address the 50 KB state-size limit that prevents inspecting larger accounts. The request gains optional from_key_base64 and limit fields, and the response returns a next_key cursor to fetch the following page. Paginated callers opt out of the legacy per-account size gate and instead get per-page item and byte caps (10,000 items / 50 KB). Combining pagination with include_proof is rejected for now because the resumed seek does not record the trie path needed for a proof.
Changes:
- New optional
from_key/limitrequest fields andnext_keyresponse field onViewStateResult/QueryRequest::ViewState, threaded through the trie viewer and runtime adapter. - Per-page item/byte caps in
TrieViewer::view_state, with seek-based resume that strips out-of-prefix or out-of-account rows, plus two newViewStateErrorvariants. - JSON-RPC validation rejecting
include_proof+ pagination andfrom_keynot starting withprefix; OpenAPI/OpenRPC specs updated and integration/e2e tests added.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| runtime/runtime/src/state_viewer/mod.rs | Implements paginated iteration with item/byte caps and cursor production. |
| runtime/runtime/src/state_viewer/errors.rs | Adds ProofUnsupportedWithPagination and FromKeyOutsidePrefix error variants. |
| runtime/runtime/src/adapter.rs | Extends ViewRuntimeAdapter::view_state with from_key/limit. |
| chain/chain/src/runtime/mod.rs | Threads new params through NightshadeRuntime::view_state and QueryRequest::ViewState handling. |
| chain/chain/src/runtime/errors.rs | Maps the new viewer errors into QueryError (currently as InternalError). |
| chain/jsonrpc-primitives/src/types/view_state.rs | Adds from_key/limit fields on RpcViewStateRequest. |
| chain/jsonrpc/src/api/mod.rs | New validate_view_state_pagination helper shared by RPC parsers. |
| chain/jsonrpc/src/api/view_state.rs | Calls the shared validator from EXPERIMENTAL_view_state parsing. |
| chain/jsonrpc/src/api/query.rs | Calls the shared validator for query's ViewState variant; structured path uses None/None. |
| chain/jsonrpc/src/lib.rs | Forwards from_key/limit from request DTO into QueryRequest::ViewState. |
| core/primitives/src/views.rs | Adds next_key to ViewStateResult and from_key/limit to the ViewState query variant. |
| chain/jsonrpc/openapi/openapi.json / openrpc.json | Spec updates for new request/response fields. |
| chain/jsonrpc/jsonrpc-tests/tests/rpc_query.rs | Integration tests covering paginated success and validation rejections. |
| integration-tests/src/tests/runtime/state_viewer.rs | Extensive trie-viewer pagination test suite (boundaries, prefix, byte cap, isolation, size-limit bypass). |
| integration-tests/src/user/{rpc_user,runtime_user}.rs, integration-tests/src/env/test_env.rs, test-loop-tests/src/tests/sharded_rpc.rs | Pass None/None for the new fields at existing call sites. |
| CHANGELOG.md | Documents the new pagination feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #15743 +/- ##
=======================================
Coverage 69.92% 69.92%
=======================================
Files 939 939
Lines 197783 197879 +96
Branches 197783 197879 +96
=======================================
+ Hits 138290 138360 +70
- Misses 54871 54888 +17
- Partials 4622 4631 +9
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jancionear
left a comment
There was a problem hiding this comment.
LGTM, just left some nits, feel free to ignore them.
Thanks for the contribution!
| let request: Self = Params::parse(value)?; | ||
| super::validate_view_state_pagination( | ||
| request.prefix.as_slice(), | ||
| request.from_key.as_ref().map(|k| k.as_slice()), | ||
| request.limit, | ||
| request.include_proof, | ||
| )?; | ||
| Ok(request) |
There was a problem hiding this comment.
I wonder if parsing is the right place for this validation code. We already validate in the query handler itself. Doesn't really hurt to do it though.
| err @ (node_runtime::state_viewer::errors::ViewStateError::ProofUnsupportedWithPagination | ||
| | node_runtime::state_viewer::errors::ViewStateError::FromKeyOutsidePrefix) => { | ||
| Self::InternalError { error_message: err.to_string(), block_height, block_hash } | ||
| } |
There was a problem hiding this comment.
Could be nice to have separate error types for that, but not strictly necessary. And I guess this should get caught earlier by the parsing code, so those errors won't really be visible to users.
| for i in 0..5 { | ||
| entries.push(( | ||
| alice_account(), | ||
| format!("akey{i}").into_bytes(), |
There was a problem hiding this comment.
cspell complains that akey is an unknown word. Please add an ignore statement (something like // cspell:ignore akey).
We need the CI green before merging
Closes #15612.
Adds
from_keyandlimitrequest fields and anext_keyresponse field toview_stateto support pagination. AddedMAX_VIEW_STATE_PAGE_ITEMSandMAX_VIEW_STATE_PAGE_BYTESnear usage, so this might be moved somewhere if needed.Follow-up issue
include_proofcan't be combined with pagination, so that request is rejected. A proof has to cover the trie path from the root down to the cursor, but a resumed page jumps to the cursor with aNO_SIDE_EFFECTSseek that doesn't record that path. Recording it means changing the trie layer, which is out of scope here. Needs a separate issue and update TODO(issue_number) here after creation.