fix(node): handle skipped Solana slots as unavailable blocks#35
fix(node): handle skipped Solana slots as unavailable blocks#35OkekeChristian24 wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughThis PR adds handling for skipped Solana slots: ChangesSkipped slot handling
Estimated code review effort: 2 (Simple) | ~15 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/node/src/solana/block.solana.spec.ts (2)
21-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssertion on Line 25 doesn't test what it claims.
Object.getOwnPropertyNamesnever returns Symbol keys regardless of whether the property is enumerable, so this assertion would pass even if the SLOT property were enumerable/writable. It doesn't actually verify the "hidden" storage behavior.✅ Suggested fix using the actual Symbol/serialization checks
expect(solanaBlockToHeader(block).blockHeight).toBe(15); - expect(Object.getOwnPropertyNames(block)).not.toContain('slot'); + expect(JSON.stringify(block)).not.toContain('"slot"'); + expect(Object.keys(block)).not.toContain('slot');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/node/src/solana/block.solana.spec.ts` around lines 21 - 26, The test in solanaBlockToHeader/transformBlock is only checking string-keyed property names, so it does not validate that the SLOT storage is truly hidden. Update the spec around transformBlock and solanaBlockToHeader to assert the actual Symbol-based behavior for SLOT, and verify serialization/own-property visibility using symbol-aware checks rather than Object.getOwnPropertyNames. Make sure the test explicitly proves the slot is preserved in the transformed block while remaining non-enumerable/non-string-keyed.
16-19: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMissing coverage for the
parentSlot + 1fallback branch.This new spec file covers the explicit-slot and stored-SLOT-symbol branches of
solanaBlockToHeader, but not the original fallback (Number(block.parentSlot) + 1) that still runs when no slot is available at all. Since this is a new file, that branch currently has zero coverage.✅ Suggested additional test
it('uses the fetched slot as the header height', () => { expect(solanaBlockToHeader(mockBlock, 15).blockHeight).toBe(15); }); + + it('falls back to parentSlot + 1 when no slot is available', () => { + expect(solanaBlockToHeader(mockBlock).blockHeight).toBe(13); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/node/src/solana/block.solana.spec.ts` around lines 16 - 19, Add a test in solanaBlockToHeader’s spec to cover the fallback path where no slot is available and blockHeight is derived from Number(block.parentSlot) + 1. Extend the Solana block utils describe block with a case that omits both the explicit slot argument and the stored SLOT symbol, then assert the header height uses parentSlot plus one. Use solanaBlockToHeader and the mockBlock setup already in this spec to keep the test aligned with the existing branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/node/src/solana/api.solana.ts`:
- Line 31: Replace the brittle skipped-slot string check with Solana’s
structured error handling in api.solana.ts. Update the logic around the
skipped-slot handling to use the typed error path from
`@solana/kit/`@solana/errors, using isSolanaError(...) and the dedicated
skipped-slot error code instead of checking e.message.includes('was skipped').
Keep the SKIPPED_SLOT_ERROR symbol only if it is still needed for the structured
comparison, and make the retry/skip decision based on the typed error fields.
---
Nitpick comments:
In `@packages/node/src/solana/block.solana.spec.ts`:
- Around line 21-26: The test in solanaBlockToHeader/transformBlock is only
checking string-keyed property names, so it does not validate that the SLOT
storage is truly hidden. Update the spec around transformBlock and
solanaBlockToHeader to assert the actual Symbol-based behavior for SLOT, and
verify serialization/own-property visibility using symbol-aware checks rather
than Object.getOwnPropertyNames. Make sure the test explicitly proves the slot
is preserved in the transformed block while remaining
non-enumerable/non-string-keyed.
- Around line 16-19: Add a test in solanaBlockToHeader’s spec to cover the
fallback path where no slot is available and blockHeight is derived from
Number(block.parentSlot) + 1. Extend the Solana block utils describe block with
a case that omits both the explicit slot argument and the stored SLOT symbol,
then assert the header height uses parentSlot plus one. Use solanaBlockToHeader
and the mockBlock setup already in this spec to keep the test aligned with the
existing branches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 99c0b80f-e745-4e98-a763-1acd2de71155
📒 Files selected for processing (5)
packages/node/src/solana/api.solana.spec.tspackages/node/src/solana/api.solana.tspackages/node/src/solana/api.solana.unit.spec.tspackages/node/src/solana/block.solana.spec.tspackages/node/src/solana/block.solana.ts
| export type SolanaSafeApi = undefined; | ||
|
|
||
| const REQUEST_TIMEOUT = 30_000; | ||
| const SKIPPED_SLOT_ERROR = 'was skipped'; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== target file: packages/node/src/solana/api.solana.ts ==\n'
wc -l packages/node/src/solana/api.solana.ts
sed -n '1,340p' packages/node/src/solana/api.solana.ts
printf '\n== search for skip-slot and solana error helpers ==\n'
rg -n "was skipped|SKIPPED_SLOT_ERROR|isSolanaError|SolanaError|context\.statusCode|context|code\)" packages/node/src packages -g '!**/dist/**' -g '!**/build/**' || trueRepository: subquery/subql-solana
Length of output: 9473
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== unit spec ==\n'
wc -l packages/node/src/solana/api.solana.unit.spec.ts
sed -n '1,120p' packages/node/src/solana/api.solana.unit.spec.ts
printf '\n== dependency versions ==\n'
rg -n '"`@solana/`(kit|rpc|rpc-api|addresses)"|solana' package.json packages/*/package.json packages/*/*/package.json -g 'package.json' || true
printf '\n== lockfile/package manager hints ==\n'
rg -n '"`@solana/kit`"|`@solana/rpc`|`@solana/rpc-api`' package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json || trueRepository: subquery/subql-solana
Length of output: 6624
🌐 Web query:
@solana/kit SolanaError isSolanaError skip slot error code message stripped __DEV__
💡 Result:
In the context of the @solana/kit and @solana/errors libraries, the SolanaError class and the isSolanaError type guard are used to handle typed errors [1][2][3]. When interacting with the Solana JSON-RPC, you may encounter server errors related to skipped or missing slots, such as: SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED [4] SOLANA_ERRORJSON_RPCSERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED [4] These errors often map to underlying JSON-RPC responses (like codes -32007 or -32009) [5][6][7] which indicate that a block was either explicitly skipped by the network due to leader/validator lag or is unavailable due to ledger data gaps [6]. Regarding your specific query components: 1. isSolanaError: This is a type guard function used to check if a caught error is a SolanaError and, optionally, to assert its specific error code [2][3]. When used with a code, it refines the TypeScript type of the error, allowing you to safely access the error's context (e.g., e.context) [2][8]. 2. SolanaError: A class that encapsulates an error's message, stack trace, and a specific numeric Solana error code [1][9]. 3. Slot skipped errors: These indicate that the requested slot does not contain block data, either because it was skipped during consensus or the data is missing from the node's local storage [6]. 4. DEV: In development environments, the @solana/errors library may include additional error context or more descriptive messages that are sometimes stripped or minimized in production builds to reduce bundle size. To handle these errors, you should use the type guard to check for specific error codes rather than string-matching the raw JSON-RPC error message [10][11]: import { isSolanaError, SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED } from '@solana/kit'; try { //... rpc call } catch (e) { if (isSolanaError(e, SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED)) { // Handle skipped slot specifically using e.context.serverMessage } }
Citations:
- 1: https://www.solanakit.com/docs/advanced-guides/errors
- 2: https://registry.npmjs.org/@solana/errors
- 3: https://www.solanakit.com/api/functions/isSolanaError
- 4: anza-xyz/kit@23d2fa1
- 5: https://solana.stackexchange.com/questions/13485/code-32007-and-32009
- 6: https://support.quicknode.com/articles/3817237462-why-are-slots-blocks-missing-on-solana
- 7: RPC: Blocks go missing solana-labs/solana#17000
- 8: https://www.npmjs.com/package/@solana/errors
- 9: https://www.solanakit.com/api/classes/SolanaError
- 10: https://solana.com/docs/payments/production-readiness
- 11: https://www.npmjs.com/package/@solana/kit
🌐 Web query:
site:docs.solana.com @solana/kit skipped slot error code isSolanaError
💡 Result:
There is no publicly documented error code specifically named skipped slot error code or isSolanaError associated with a package named @solana/kit in the provided technical documentation. It is possible that the package name or error nomenclature is non-standard, unofficial, or refers to a internal/deprecated library. In Solana development, the term skipped slot refers to a slot in the blockchain where no block was produced, which is a normal occurrence in the cluster [1]. Developers often encounter slot-related concepts when using commitment levels (such as processed, where a block may still be skipped) or via WebSocket subscriptions like slotSubscribe or slotsUpdatesSubscribe [1][2]. If you are encountering an error with a specific library, ensure you are using the official Solana Web3.js SDK or standard RPC methods, as these are the primary interfaces for interacting with the Solana network [1][2]. If isSolanaError is part of a custom error-handling utility in your project, it is likely a helper function designed to identify error types conforming to a specific Solana error interface.
Citations:
🌐 Web query:
@solana/kit SolanaError isSolanaError skipped slot error code message stripped __DEV__
💡 Result:
In the @solana/kit ecosystem, SolanaError is a structured error class used to standardize error handling across the library [1][2]. Key components and usage: 1. Error Codes and Identification: Solana errors are identified by specific, unique error codes (e.g., SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED) [3][1]. You should never rely on string matching to identify errors; instead, use the isSolanaError helper function, which acts as a TypeScript type guard [4][5]. 2. Handling Skipped Slots: A "skipped slot" occurs when a slot does not produce a block, typically because the leader was offline or the fork was abandoned [6]. When this happens, an RPC node may return a JSON-RPC error. - In @solana/kit, this is represented by the error code SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED [3]. - This error code corresponds to the underlying JSON-RPC error code -32007 [7][8]. - When using isSolanaError(error, SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED), the error context provides access to the serverMessage returned by the node [3]. 3. DEV and Message Stripping: In production-optimized builds, error messages are often "stripped" or replaced with generic placeholders to reduce bundle size and protect against information leakage [3][2]. The DEV environment variable is typically used by library internals to determine whether to include detailed, developer-friendly error messages or minimal, production-safe messages in the SolanaError object [2]. Example pattern for catching these errors: import { isSolanaError, SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED } from '@solana/kit'; try { //... operation that might trigger a slot error } catch (e) { if (isSolanaError(e, SOLANA_ERRORJSON_RPCSERVER_ERROR_SLOT_SKIPPED)) { console.error('The requested slot was skipped:', e.context.serverMessage); } else { throw e; } }
Citations:
- 1: https://www.solanakit.com/docs/advanced-guides/errors
- 2: https://www.solanakit.com/api/classes/SolanaError
- 3: anza-xyz/kit@23d2fa1
- 4: https://registry.npmjs.org/@solana/errors
- 5: https://solana.com/docs/payments/production-readiness
- 6: https://solana.com/docs/references/terminology
- 7: https://github.com/solana-foundation/solana-web3.js/blob/5b4e63daed1561ce58585a639041732c04aa354a/src/errors.ts
- 8: RPC: Blocks go missing solana-labs/solana#17000
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== yarn lock entries for `@solana/errors` and `@solana/kit` ==\n'
rg -n '"`@solana/errors`@|`@solana/errors`"| "`@solana/kit`@|`@solana/kit`"' yarn.lock || true
printf '\n== package manifests mentioning `@solana/errors` or `@solana/kit` ==\n'
rg -n '"`@solana/errors`"| "`@solana/kit`"' packages/node/package.json packages/types/package.json package.json yarn.lock || trueRepository: subquery/subql-solana
Length of output: 3158
Use Solana’s typed skipped-slot error instead of matching the message string. e.message?.includes('was skipped') is brittle; @solana/kit/@solana/errors already expose isSolanaError(...) and a dedicated skipped-slot code, so this should use the structured error path to avoid a silent regression if the text changes or is stripped.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/node/src/solana/api.solana.ts` at line 31, Replace the brittle
skipped-slot string check with Solana’s structured error handling in
api.solana.ts. Update the logic around the skipped-slot handling to use the
typed error path from `@solana/kit/`@solana/errors, using isSolanaError(...) and
the dedicated skipped-slot error code instead of checking
e.message.includes('was skipped'). Keep the SKIPPED_SLOT_ERROR symbol only if it
is still needed for the structured comparison, and make the retry/skip decision
based on the typed error fields.
Description
This PR fixes an issue where the Solana node can get stuck retrying a skipped slot.
Solana slots can be skipped as part of normal chain behavior. When the node attempts to fetch one of those slots, the Solana RPC may return an error like:
Fixes # 34
Type of change
Checklist
Summary by CodeRabbit
Bug Fixes
Tests