graphql: fix block.account and account.storage resolvers (#11662)#20389
Conversation
Implement the missing GraphQL resolvers for Block.account(address) and Account.storage(slot), which previously returned null causing "the requested element is null which the schema does not allow". - Add GetAccountInfo and GetAccountStorage to GraphQLAPI interface and GraphQLAPIImpl, reading balance/nonce/code/storage from state at the requested block - Wire Block.account and Account.storage as proper resolvers in generated.go (IsResolver: true) instead of returning nil struct fields - Implement blockResolver.Account() and accountResolver.Storage() in schema.resolvers.go; propagate block number via Account.BlockNum for use by the storage resolver - Fix convertDataToStringP in helpers.go to handle accounts.Address type, which caused unhandled/string log spam for transaction from fields - Add --graphql flag to startRpcDaemon.sh (required to enable the endpoint) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dcfb55f to
e92a02d
Compare
After rebasing on main (gqlgen v0.17.83 → v0.17.89), the executableSchema field was renamed from resolvers to Resolvers. Update the two new resolver dispatch calls accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes GraphQL schema violations by implementing missing resolvers for Block.account(address) and Account.storage(slot) so queries no longer return null for non-nullable schema fields, and adds JSON-RPC backing methods to read account/state data at a given block.
Changes:
- Extend the JSON-RPC GraphQL API with
GetAccountInfoandGetAccountStorageand implement them via state reads at a resolved block number. - Switch
Block.accountandAccount.storageto real gqlgen resolvers (instead of struct-field fallbacks) and implement the resolver methods. - Update helper conversion to handle
accounts.Addressvalues without falling into the “unhandled/string” path.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
rpc/jsonrpc/graphql_api.go |
Adds state-backed account info & storage read methods used by GraphQL resolvers. |
cmd/rpcdaemon/graphql/graph/schema.resolvers.go |
Implements Block.account and Account.storage resolver logic and passes block number through. |
cmd/rpcdaemon/graphql/graph/model/models_gen.go |
Adds an internal BlockNum field to Account for resolver use. |
cmd/rpcdaemon/graphql/graph/helpers.go |
Extends convertDataToStringP to stringify accounts.Address. |
cmd/rpcdaemon/graphql/graph/generated.go |
Regenerated gqlgen output wiring account/storage as resolvers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add WithTemporalOverlay in GetAccountInfo and GetAccountStorage to handle background commits correctly - Validate storage slot before opening DB transaction (fail-fast, and consistent with GetStorageAt in eth_accounts.go) - Validate address with IsHexAddress in blockResolver.Account - Propagate BlockNum to all Account stubs (Miner, tx From/To, log Account) so storage(slot) queries target the correct block - Move Account model out of generated models_gen.go into account.go and bind it in gqlgen.yml so BlockNum survives regeneration - Add NewAccountAtBlock constructor to encapsulate BlockNum assignment - Add unit tests for slot and address validation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func (api *GraphQLAPIImpl) GetAccountStorage(ctx context.Context, address common.Address, slot string, blockNumber rpc.BlockNumber) (string, error) { | ||
| slotBytes, decErr := hexutil.FromHexWithValidation(slot) | ||
| if decErr != nil { | ||
| return zeroStorageHash, &rpc.InvalidParamsError{Message: "unable to decode storage slot: " + hexutil.ErrHexStringInvalid.Error()} | ||
| } | ||
| if len(slotBytes) > 32 { | ||
| return zeroStorageHash, &rpc.InvalidParamsError{Message: hexutil.ErrTooBigHexString.Error()} | ||
| } |
There was a problem hiding this comment.
GetAccountStorage slot validation currently uses hexutil.FromHexWithValidation, which will accept inputs without a 0x prefix and will also accept odd-length hex by left-padding a nibble. This can make malformed Bytes32 inputs silently resolve to an unintended slot key, which is at odds with the GraphQL schema docs for Bytes32 (0x-prefixed 32-byte hex). Consider explicitly validating the raw slot string format (0x prefix + even number of nibbles, and max 32 bytes) and using the underlying decode error message (decErr) in the InvalidParamsError to make failures actionable.
There was a problem hiding this comment.
The lenient behavior is intentional and consistent with GetStorageAt in eth_accounts.go, which uses the same hexutil.FromHexWithValidation and explicitly documents the choice:
// Validation for index i.e. storage slot is non-standard: it can be interpreted
// as QUANTITY (stricter) or as DATA (like Hive tests do).
// Waiting for a spec, we choose the latter because it's more general,
// but we check that the length is not greater than 64 hex-digits.
Implement the missing GraphQL resolvers for Block.account(address) and Account.storage(slot), which previously returned null causing "the requested element is null which the schema does not allow".
closes #11662