Status: Draft
Type: Standards Track
Category: Core (RPC)
Created: 2026-05-11
Requires: CIP-4 (State & Merkle Proofs)
Required by: CIP-15 v2.r2 §6 (Gateway routes-table fetch), CIP-15 gateway-implementation r2 §2.2 (Phase 1 routes resolver), CIP-19 §10.1 (MCP
tools/list derivation)
Companions: CIP-14 v2.r2 §5 (read_handler RPC — complementary, distinct purpose)1. Abstract
This CIP specifiesGET_STATE — a verifiable single-key state-read RPC exposed by Cowboy full nodes and Runners. Given an actor_address and a storage key, it returns the current value plus a Merkle inclusion proof against the actor’s state_root at a known block height. Clients (Gateways, off-chain indexers, light clients) verify the proof locally before trusting the value.
GET_STATE is not a handler-invocation RPC — that role belongs to CIP-14 v2.r2 §5 read_handler. GET_STATE is a raw, deterministic, proof-attached KV read. It is the building block CIP-15 v2.r2 needs to fetch a target actor’s __cowboy/routes table without invoking the actor (no PVM cycles), and that CIP-19 §10.1 needs to derive tools/list deterministically from on-chain routes.
The current RPC layer (node/rpc/src/rpc.rs:168-213) exposes /actor/{address} and /actors/{address}/storage for unverified state reads. GET_STATE adds the Merkle-proof attachment that closes the trust model: a Gateway no longer has to trust a single Runner — it verifies the response against the actor’s state_root from a recent block.
2. Motivation
CIP-15 v2.r2 Gateway implementation depends on a per-actorRoutes table stored at the actor’s KV key __cowboy/routes. The Gateway fetches this table on every block of activity, caches it, and resolves incoming HTTP requests against it. Without a verifiable read:
- Single point of trust. A Gateway fetching from one Runner has no way to detect a malicious or stale response. The Runner could return a forged routes table redirecting traffic.
- Cache invalidation theory only. CIP-15 v2.r2 §6 says “poll
manifest_rooteveryMANIFEST_POLL_INTERVALblocks.” But polling forstate_rootchanges requires a verifiable read in the first place — otherwise the polled value is itself a trust assumption. - CIP-19
tools/listconsequence. CIP-19 §10.1 step 1 reads the routes table identically. The same trust gap applies.
/actor/{address} and /actors/{address}/storage endpoints (node/rpc/src/rpc.rs:168-213) return raw KV values with no proof. CIP-15 gateway-implementation r2 §2.2 explicitly flags this as the single hardest blocker for Phase 1 shipping.
The fix is a small, well-scoped addition: take the existing KV-read path, attach the Merkle proof against the actor’s storage trie root (already maintained per CIP-4), and expose the bundle through a new RPC endpoint.
3. Design Goals
- Verifiable. Response carries everything a client needs to reconstruct the actor’s state-root commitment for the read leaf, with no further round-trip.
- Cheap on the server. The proof is a single Merkle path; node maintains the storage trie anyway (CIP-4) so generation is microseconds, not milliseconds.
- Stateless on the client. No subscription, no session, no pagination. One KV → one response.
- Reusable. Applicable to any system actor or user actor; not specialized to Gateway routes.
- Compatible with light clients. A future Cowboy light client (per CIP-25 §1.4 native-light-client backend) consumes the same proofs.
4. Non-goals
- Handler invocation. That is CIP-14 v2.r2 §5
read_handler. - Streaming subscriptions. A future CIP may add
SUBSCRIBE_STATE(push notifications when a key changes); v1 here is pull-only. - Multi-key proofs. v1 returns one proof per call. Batch reads with a combined proof are a future optimization.
- Cross-actor proofs. v1 proves inclusion of
(key, value)within one actor’s storage trie. Cross-actor relations require multipleGET_STATEcalls. - Historical reads. v1 reads at the latest committed block. A future revision may add
at_block: u64for historical Merkle reads (CIP-25 §1.4 cross-chain backends would need this).
5. RPC Endpoint
5.1 HTTP route
actor_address— 20-byte hex-encoded address with0xprefix (e.g.0x0000...000D)key_hex— hex-encoded raw KV key bytes with0xprefix; same encoding as used by the actor when writing viastate_set
prove: boolean, defaulttrue. Iffalse, behaves identically to the existing/actors/{address}/storagelookup — value only, no proof. Provided for parity with cheap unverified reads.
5.2 Response
value— raw bytes of the KV value, hex-encoded with0xprefix.nullif the key does not exist (seeabsentbelow).state_root— the actor’s storage trie root atblock_height. Matchesaccount_state.state_rootforactor_addressin the block’s account trie.block_height— the block height at which the proof was generated; always the latest committed block at the time of RPC handling.block_hash— block-header hash for cross-verification with the node’s block-explorer view.proof.siblings— ordered Merkle path siblings from leaf to root, per the existing CIP-4 / MPT primitives Cowboy uses for actor storage.proof.leaf_hash— the leaf hashkeccak256(key || value)(or whichever convention the existing MPT impl uses; pinned by the implementation, not redefined here).proof.path_nibbles— the nibble path used to navigate the MPT; redundant givenkeybut explicit for proof-verifier ergonomics.absent—trueif the key does not exist in the actor’s storage atblock_height. The proof is then an exclusion proof (the path to where the key would be, terminating in a divergent node). Clients verify exclusion identically to inclusion.
5.3 Proof verification (client-side)
state_root claim matches the block’s account trie. v1 expects the Gateway to obtain the account trie root through the existing block-header subscription (which a Gateway already maintains for CIP-14 v2.r2 cache invalidation) and trust it equally with block_hash. A v2 revision may bundle the account-trie path into the same response.
5.4 Error responses
value: null and absent: true plus a valid exclusion proof.
6. Use Cases
6.1 CIP-15 v2.r2 Gateway routes fetch (primary motivator)
Percip-15-gateway-implementation.md r2 §2.3, the Gateway’s poll loop calls:
6.2 CIP-19 tools/list derivation
The MCP tools/list generator (CIP-19 §10.1 step 1) uses the same call to fetch the routes table; the rest of §10.1 is independent of how the table was fetched.
6.3 Off-chain indexer audit
Indexers building cross-actor views (token balance trackers, governance vote tallies) issueGET_STATE calls against the canonical balance / vote keys and verify each proof before incorporating values into their derived state. Eliminates the “indexer trusts a single node” failure mode.
6.4 Light client support (future)
A Cowboy light client (per CIP-25 §1.4 “native light client” backend on a destination chain) consumesGET_STATE responses verbatim. The destination-chain verifier code is just the §5.3 procedure compiled into the destination’s VM.
7. Implementation Sketch (non-normative)
The node side requires:- One new HTTP route in
node/rpc/src/rpc.rs(after the existing/actors/{address}/storagehandler at line 168-213):GET /state/{actor_address}/{key_hex}. - One new method on the existing storage interface that returns
(value, proof)instead of justvalue. The MPT crate Cowboy uses for actor storage already maintains the proof primitives — exposing them is a few-line API surface addition. - One new struct in
node/types/src/rpc.rs(or equivalent) for the response envelope.
- Actor storage trie maintained per
node/storage/src/blockchain_storage.rs(per CIP-4). - Block header / state-root view via
node/chain/src/engine.rs. - HTTP route boilerplate per other endpoints in
node/rpc/src/rpc.rs.
8. Relationship to Other CIPs
| CIP | Relationship |
|---|---|
| CIP-4 (State & Merkle Proofs) | Source of the MPT primitives GET_STATE exposes. CIP-17 does not redefine the trie shape; it only adds the RPC surface. |
CIP-14 v2.r2 §5 (read_handler) | Complementary, distinct. read_handler invokes the actor’s PVM in read-only mode; GET_STATE reads raw KV with proof. A Gateway needs both: read_handler for GET /api/users/{id}-style dispatched logic, GET_STATE for __cowboy/routes fetch. Different latency, different trust model. |
| CIP-15 v2.r2 | Primary consumer. CIP-15-gateway-implementation r2 §2.2 lists GET_STATE as the single hardest Phase 1 prerequisite. |
| CIP-19 | Secondary consumer. §10.1 step 1. |
| CIP-25 §1.4 (native light client) | Future consumer; GET_STATE responses are exactly the leaf primitive a cross-chain light client verifies. |
9. Security Considerations
- Trust model. A Gateway / indexer / light client verifies the Merkle proof locally before trusting the value. A malicious node returning a forged
(value, proof)either trips proof verification (proof doesn’t reconstruct to the claimedstate_root) or returns astate_rootthat doesn’t match the block header the client has from a separate trusted source (e.g. another node, or its own block subscription). - Stale reads. Responses always reflect the latest committed block at RPC handling time. Clients that need monotonic reads should compare
block_heightacross successive calls and reject regression. - DoS surface. Proof generation is cheap (single Merkle path), but a flood of
GET_STATEcalls against large actor states could pressure a node. Mitigation: standard rate-limit middleware (already deployed for/actors/{address}/storageat 100 req/s default pernode/rpc/src/rpc.rs). - Absent-key fingerprinting. Returning an exclusion proof reveals “this key does not exist” deterministically. This is the same information already exposed by
/actors/{address}/storage; no new leak. - Key encoding edge cases. Implementations MUST decode
key_hexstrictly (reject non-hex chars, odd length, etc.) to prevent ambiguity between, e.g.,0x__cowboy/routes(literal nine-byte string) and a typoed escape sequence.
10. Backwards Compatibility
Fully additive. New HTTP route; no existing RPC, on-chain state, or PVM syscall is modified. Clients that don’t speakGET_STATE continue to use /actor/{address} and /actors/{address}/storage unchanged.
If GET_STATE lands in node code, the cip-15-gateway-implementation companion’s §9 open-question item 1 is resolved.
11. Future Work
at_block: u64query param for historical reads (needed by CIP-25 §1.4 native light client backend).- Batch reads.
POST /state/batchwith multiple(actor, key)pairs returning combined proofs. - Subscribe API. WebSocket-based push when a watched key changes — useful for Gateway cache invalidation tighter than CIP-15’s
MANIFEST_POLL_INTERVAL. - Bundled account-trie proof. Resolve §5.3 limitation by returning both the actor’s storage proof AND the account-trie proof for
actor_address.state_rootin one response. - CIP-25 cross-chain extension. A cross-chain L1 anchor (CIP-25 §1) can carry
GET_STATE-style proofs over the L1 mailbox, allowing destination-chain L3 apps to verify Cowboy state directly.
12. References
node/rpc/src/rpc.rs:168-213— existing unverified state endpointsnode/storage/src/blockchain_storage.rs— actor storage trie- CIP-4 — MPT / Merkle proof primitives
- CIP-14 v2.r2 §5 —
read_handler(complementary RPC) - CIP-15 v2.r2 §6 +
cip-15-gateway-implementation.mdr2 §2.2 — primary use case - CIP-19 §10.1 — secondary use case
- CIP-25 §1.4 — native light client future use case

