Skip to content

Conversation

@skoszuta
Copy link
Contributor

@skoszuta skoszuta commented Nov 7, 2025

This is the first step in a series of changes to align with the new JIP-2 spec (#756).

  • switched to zod 4 for built-in support of base64
  • updated param schemas
  • added result schemas
  • 2-way encoding of data base64 <-> uint8array
  • improved rpc errors to match jip-2 more closely (still some work left to be done)

- switched to zod 4 for built-in support of base64
- updated param schemas
- added result schemas
- 2-way encoding of data base64 <-> uint8array
- improved rpc errors to match jip-2 more closely (still some work left to be done)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Chores

    • Updated Zod dependency to version 4.1.12
  • Bug Fixes

    • Improved error handling and responses for RPC calls with additional data fields
  • Refactor

    • RPC endpoints now return structured objects with named fields instead of positional arrays
    • Hash parameters and responses now use base64 encoding
    • Enhanced validation framework across all RPC method implementations

Walkthrough

Updates the RPC subsystem: bumps zod in package.json, introduces withValidation to wrap RPC handlers (explicit params/result schemas), replaces many RpcMethod exports with validated wrappers and changes return shapes from positional arrays to structured objects (e.g., BlockDescriptor). Adds RpcErrorCode enum and extends RpcError with optional data. Switches hash handling from Bytes.fromNumbers to Bytes.fromBlob, replaces array-based method registry initialization with a Map populated via methods.set(...), adds a notImplemented handler, changes client error propagation to forward raw error objects, and updates e2e tests accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas to focus on:

  • bin/rpc/src/types.ts — withValidation, new zod codecs (Hash, BlobArray, etc.), RpcErrorCode and RpcError.data, updated RpcMethodRepo export surface.
  • bin/rpc/src/methods/* — validation schemas, signature changes (tuple → object), error semantics (RpcError codes) and Blob vs Numbers conversions.
  • bin/rpc/src/method-loader.ts and bin/rpc/src/server.ts — Map-based method registration/lookup, params/resultSchema usage and result encoding.
  • bin/rpc/src/client.ts — change to propagating raw error objects.
  • bin/rpc/test/e2e.ts — updated expectations for BlockDescriptor-shaped responses and base64 header_hash values.

Possibly related PRs

Suggested reviewers

  • mateuszsikora
  • tomusdrw
  • DrEverr

Poem

🐇 I hopped where zod now guards each call,

header_hash as base64, tidy and tall,
Methods set in Maps, no more array scurry,
Errors bring data — not just a hurry,
A rabbit twitches whiskers: the RPCs purr bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'RPC: JIP-2 data types' directly and concisely summarizes the main change: updating RPC components to align with JIP-2 specification's data types, including zod 4 upgrade, schemas, and base64 encoding.
Description check ✅ Passed The description clearly relates to the changeset, explaining the first step in JIP-2 alignment with specific implementation details: zod upgrade, schema updates, base64 encoding, and improved error handling.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sk-jip-2-data-types

Comment @coderabbitai help to get the list of available commands and usage tips.

@skoszuta skoszuta marked this pull request as ready for review November 7, 2025 11:35
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
bin/rpc/src/methods/service-data.ts (1)

9-19: Update documentation to reflect error-throwing behavior.

The documentation comment states that "null is returned if the block's posterior state is not known" (line 11), but the implementation now throws an RpcError on line 26 when the state is not found. Please update the documentation to accurately describe the error-throwing behavior.

Apply this diff:

 /**
  * https://hackmd.io/@polkadot/jip2#serviceData
- * Returns the service data for the given service ID. The data are encoded as per the GP. `null` is
- * returned if the block's posterior state is not known. `Some(None)` is returned if there is no value
- * associated with the given service ID.
+ * Returns the service data for the given service ID. The data are encoded as per the GP. 
+ * Throws RpcError if the block's posterior state is not known. Returns `null` if there is no value
+ * associated with the given service ID.
  * @param [
  *   Hash - The header hash indicating the block whose posterior state should be used for the query.
  *   ServiceId - The ID of the service.
  * ]
- * @returns Either null or Blob
+ * @returns Blob or null
+ * @throws RpcError when the block's posterior state is not found
  */
bin/rpc/src/methods/service-preimage.ts (1)

7-18: Update documentation to reflect error-throwing behavior.

The documentation should mention that RpcError is thrown when the block's posterior state is not found (line 25) or when the service is not found (line 31). Currently, it only describes the null return case for missing preimage.

Apply this diff:

 /**
  * https://hackmd.io/@polkadot/jip2#servicePreimage
  * Returns the preimage associated with the given service ID and hash in the posterior state of the
  * block with the given header hash. `null` is returned if there is no preimage associated with the
  * given service ID and hash.
  * @param [
  *   Hash - The header hash indicating the block whose posterior state should be used for the query.
  *   ServiceId - The ID of the service.
  *   Hash - The hash.
  * ]
- * @returns Either null or Blob
+ * @returns Blob or null
+ * @throws RpcError when the block's posterior state or service is not found
  */
🧹 Nitpick comments (5)
bin/rpc/src/types.ts (1)

99-111: Simplify Uint8Array conversion in codec decode functions.

The Uint8Array.from(Buffer.from(v, "base64")) pattern can be simplified since Buffer.from() already returns a Uint8Array-compatible buffer. The extra Uint8Array.from() call creates an unnecessary copy.

Apply this diff to simplify the decode functions:

 export const Hash = z.codec(
   z.base64(),
   zUint8Array.refine((v) => v.length === HASH_SIZE, "Invalid hash length."),
   {
-    decode: (v) => Uint8Array.from(Buffer.from(v, "base64")),
+    decode: (v) => Buffer.from(v, "base64"),
     encode: (v) => Buffer.from(v).toString("base64"),
   },
 );
 export const Slot = zU32;
 export const BlobArray = z.codec(z.base64(), zUint8Array, {
-  decode: (v) => Uint8Array.from(Buffer.from(v, "base64")),
+  decode: (v) => Buffer.from(v, "base64"),
   encode: (v) => Buffer.from(v).toString("base64"),
 });
bin/rpc/src/methods/parent.ts (1)

39-39: Use RpcError for consistency with error handling pattern.

This error path uses a generic Error while other error paths in this method use RpcError. For consistency and to align with the standardized error handling approach, consider using RpcError here as well.

Apply this diff:

-    throw new Error(`Parent (${parentHash}) not found.`);
+    throw new RpcError(RpcErrorCode.Other, `Parent (${parentHash}) not found.`);
bin/rpc/src/methods/not-implemented.ts (1)

4-10: Consider z.unknown() instead of z.any() for semantic correctness.

While z.any() works for this placeholder implementation, z.unknown() would be more semantically appropriate as it better expresses that the parameters exist but are not validated, rather than bypassing the type system entirely.

Apply this diff:

 export const notImplemented = withValidation(
   () => {
     throw new RpcError(RpcErrorCode.Other, "Method not implemented");
   },
-  z.any(),
-  z.any(),
+  z.unknown(),
+  z.unknown(),
 );
bin/rpc/src/methods/service-data.ts (1)

20-39: LGTM! Consider using more specific error code.

The implementation correctly:

  • Uses Bytes.fromBlob for Uint8Array hash parameters (decoded by the Hash schema)
  • Throws RpcError for missing state with a descriptive message
  • Returns raw bytes that will be base64-encoded by the BlobArray schema
  • Defines appropriate input/output validation schemas

If RpcErrorCode includes a more specific code for unavailable blocks (e.g., BlockUnavailable), consider using it instead of Other on line 26 for clearer error semantics.

bin/rpc/src/methods/service-preimage.ts (1)

19-44: LGTM! Consider using more specific error codes.

The implementation correctly:

  • Uses Bytes.fromBlob for both hash parameters (decoded by the Hash schema)
  • Implements three-level validation: state existence, service existence, preimage existence
  • Returns preimage.raw directly, consistent with the learning that getPreimage() returns BytesBlob | null
  • Provides descriptive error messages for missing state and service
  • Returns null for missing preimage, matching the documented behavior
  • Defines appropriate validation schemas

Based on learnings

If RpcErrorCode includes more specific codes for these scenarios (e.g., BlockUnavailable, ServiceNotFound), consider using them instead of Other on lines 25 and 31 for more precise error categorization.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dabd741 and 88f8544.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (16)
  • bin/rpc/package.json (1 hunks)
  • bin/rpc/src/client.ts (1 hunks)
  • bin/rpc/src/method-loader.ts (1 hunks)
  • bin/rpc/src/methods/best-block.ts (2 hunks)
  • bin/rpc/src/methods/list-services.ts (2 hunks)
  • bin/rpc/src/methods/not-implemented.ts (1 hunks)
  • bin/rpc/src/methods/parent.ts (2 hunks)
  • bin/rpc/src/methods/service-data.ts (2 hunks)
  • bin/rpc/src/methods/service-preimage.ts (2 hunks)
  • bin/rpc/src/methods/service-request.ts (2 hunks)
  • bin/rpc/src/methods/service-value.ts (2 hunks)
  • bin/rpc/src/methods/state-root.ts (2 hunks)
  • bin/rpc/src/methods/statistics.ts (2 hunks)
  • bin/rpc/src/server.ts (5 hunks)
  • bin/rpc/src/types.ts (1 hunks)
  • bin/rpc/test/e2e.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts

⚙️ CodeRabbit configuration file

**/*.ts: rules from ./CODESTYLE.md should be adhered to.

**/*.ts: Any function whose documentation mention it must not be used in production code,
can be safely used in *.test.ts files. Other usage should be carefuly reviewed
and the comment must explain why it's safe to use there.

**/*.ts: as conversions must not be used. Suggest using tryAs conversion methods.

**/*.ts: Classes with static Codec field must have private constructor and static create method.

**/*.ts: Casting a bigint (or U64) using Number(x) must have an explanation comment why
it is safe.

**/*.ts: When making changes to code with comments containing links (in classes, constants, methods, etc.)
to graypaper.fluffylabs.dev, ensure those links point to the current version for this update.

Files:

  • bin/rpc/src/methods/not-implemented.ts
  • bin/rpc/test/e2e.ts
  • bin/rpc/src/methods/parent.ts
  • bin/rpc/src/types.ts
  • bin/rpc/src/methods/best-block.ts
  • bin/rpc/src/methods/service-request.ts
  • bin/rpc/src/methods/service-value.ts
  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/client.ts
  • bin/rpc/src/methods/service-preimage.ts
  • bin/rpc/src/methods/list-services.ts
  • bin/rpc/src/method-loader.ts
  • bin/rpc/src/server.ts
  • bin/rpc/src/methods/state-root.ts
  • bin/rpc/src/methods/statistics.ts
🧠 Learnings (3)
📚 Learning: 2025-05-22T18:08:38.725Z
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 381
File: bin/rpc/src/server.ts:99-112
Timestamp: 2025-05-22T18:08:38.725Z
Learning: RPC methods should implement strict parameter validation, including checking the exact number of parameters and their types. This helps catch errors early when users confuse similar methods (e.g., serviceData vs serviceValue) and prevents cryptic errors from undefined parameters being passed to internal code.

Applied to files:

  • bin/rpc/src/types.ts
  • bin/rpc/src/methods/service-request.ts
  • bin/rpc/src/methods/service-value.ts
  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/methods/service-preimage.ts
  • bin/rpc/src/methods/list-services.ts
  • bin/rpc/src/method-loader.ts
  • bin/rpc/src/server.ts
📚 Learning: 2025-06-10T12:04:56.072Z
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 419
File: packages/jam/transition/accumulate/accumulate.ts:193-193
Timestamp: 2025-06-10T12:04:56.072Z
Learning: In the typeberry codebase, the Service.getPreimage() method was updated to return BytesBlob | null directly instead of PreimageItem | null, so the returned value is already a BytesBlob and doesn't need .blob property access.

Applied to files:

  • bin/rpc/src/methods/service-value.ts
  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/methods/service-preimage.ts
📚 Learning: 2025-06-10T12:10:10.532Z
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 419
File: packages/jam/state-merkleization/serialize-update.ts:115-126
Timestamp: 2025-06-10T12:10:10.532Z
Learning: In packages/jam/state-merkleization/serialize-update.ts, service removal is handled separately from service updates. The UpdateServiceKind enum does not include a Remove variant. Service removals are handled via the servicesRemoved parameter in serializeUpdate() which is processed by serializeRemovedServices(), while service updates/creations are handled via servicesUpdates parameter processed by serializeServiceUpdates().

Applied to files:

  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/methods/list-services.ts
🧬 Code graph analysis (14)
bin/rpc/src/methods/not-implemented.ts (1)
bin/rpc/src/types.ts (2)
  • withValidation (84-92)
  • RpcError (62-70)
bin/rpc/test/e2e.ts (2)
bin/rpc/src/types.ts (1)
  • BlockDescriptor (115-118)
bin/rpc/src/methods/best-block.ts (1)
  • bestBlock (11-31)
bin/rpc/src/methods/parent.ts (2)
bin/rpc/src/types.ts (4)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
  • BlockDescriptor (115-118)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
bin/rpc/src/types.ts (4)
packages/jam/database/blocks.ts (1)
  • BlocksDb (9-30)
packages/jam/database/states.ts (1)
  • StatesDb (31-51)
packages/jam/state/state.ts (2)
  • State (52-210)
  • EnumerableState (37-45)
packages/jam/config/chain-spec.ts (1)
  • ChainSpec (41-106)
bin/rpc/src/methods/best-block.ts (1)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
  • NoArgs (114-114)
  • BlockDescriptor (115-118)
bin/rpc/src/methods/service-request.ts (4)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • Hash (99-106)
  • ServiceId (112-112)
  • PreimageLength (113-113)
  • Slot (107-107)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
packages/core/numbers/index.ts (1)
  • tryAsU32 (45-48)
bin/rpc/src/methods/service-value.ts (3)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
  • ServiceId (112-112)
  • BlobArray (108-111)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
bin/rpc/src/methods/service-data.ts (6)
packages/jam/state-merkleization/serialize.ts (1)
  • serviceData (185-190)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
  • ServiceId (112-112)
  • BlobArray (108-111)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
packages/core/codec/encoder.ts (1)
  • Encoder (73-480)
packages/jam/state/service.ts (1)
  • ServiceAccountInfo (82-154)
bin/rpc/src/methods/service-preimage.ts (3)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
  • ServiceId (112-112)
  • BlobArray (108-111)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
bin/rpc/src/methods/list-services.ts (2)
bin/rpc/src/types.ts (3)
  • withValidation (84-92)
  • Hash (99-106)
  • ServiceId (112-112)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
bin/rpc/src/method-loader.ts (2)
bin/rpc/src/types.ts (1)
  • RpcMethodRepo (94-94)
bin/rpc/src/methods/not-implemented.ts (1)
  • notImplemented (4-10)
bin/rpc/src/server.ts (1)
bin/rpc/src/types.ts (1)
  • DatabaseContext (79-82)
bin/rpc/src/methods/state-root.ts (2)
bin/rpc/src/types.ts (3)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
bin/rpc/src/methods/statistics.ts (5)
packages/jam/state-merkleization/serialize.ts (1)
  • statistics (148-152)
packages/jam/state-merkleization/serialized-state.ts (1)
  • statistics (163-165)
bin/rpc/src/types.ts (4)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
  • BlobArray (108-111)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/state/statistics.ts (1)
  • StatisticsData (271-291)
🔇 Additional comments (16)
bin/rpc/src/client.ts (1)

110-110: LGTM: Error propagation now preserves structured error data.

The change from wrapping the error in a new Error object to passing the raw response.error object preserves the structured error information (code, message, and data fields), which aligns with the new RpcError type and enables clients to access error codes and additional data.

bin/rpc/package.json (1)

36-36: LGTM: Zod 4 upgrade aligns with new validation patterns.

The upgrade to zod ^4.1.12 enables the new z.codec and z.base64() features used throughout the codebase for type-safe encoding/decoding. The caret range is appropriate for managing minor and patch updates.

bin/rpc/src/types.ts (3)

66-66: LGTM: Error data field enables structured error context.

Adding the optional data parameter to RpcError aligns with JSON-RPC 2.0 specification and enables methods to include additional context (e.g., encoded hash values) in error responses.


72-77: LGTM: Application-specific error codes improve error handling.

The RpcErrorCode enum provides typed, domain-specific error codes that enable clients to handle different error scenarios programmatically. The values (1, 2, 3) are appropriately distinct from JSON-RPC standard error codes (negative values).


84-92: LGTM: withValidation standardizes RPC method definitions.

The withValidation wrapper provides a type-safe pattern for defining RPC methods with explicit parameter and result schemas, enabling consistent validation and encoding across all methods.

bin/rpc/src/methods/parent.ts (2)

21-21: LGTM: Bytes.fromBlob is the correct method for Uint8Array input.

The change from Bytes.fromNumbers to Bytes.fromBlob correctly handles the Uint8Array input from the validated hash parameter.


42-45: LGTM: Structured return type improves API clarity.

Returning a BlockDescriptor object instead of a tuple provides better clarity and type safety for API consumers.

bin/rpc/src/server.ts (3)

32-32: LGTM: Error response now includes data field.

Adding error.data to the error response structure enables methods to provide additional context (such as encoded hash values) in error responses, consistent with JSON-RPC 2.0 specification.


168-168: LGTM: Default empty array prevents undefined params.

Using params ?? [] ensures that the validation function always receives a defined value, which is correct for methods that accept NoArgs (empty tuple) and prevents potential validation errors from undefined values.

Also applies to: 180-180


204-211: LGTM: Result encoding ensures proper data serialization.

The addition of resultSchema.encode() is essential for converting internal types (e.g., Uint8Array) to wire formats (e.g., base64 strings) before sending responses. This aligns with the new codec-based schema definitions.

bin/rpc/src/methods/best-block.ts (1)

11-31: LGTM: Consistent implementation of withValidation pattern.

The bestBlock method correctly implements the new validation pattern with proper error handling, structured return type, and encoded error data. The implementation is consistent with other migrated methods.

bin/rpc/test/e2e.ts (3)

9-13: LGTM: Temporary type definition is appropriate.

The local BlockDescriptor type definition is a reasonable temporary workaround until client-side validation and typing are implemented. The TODO comment clearly indicates this is technical debt to be addressed.


93-105: LGTM: Error assertions validate structured error format.

The test correctly validates the new error structure including the code, message, and data fields, ensuring that encoded hash values are properly included in error responses.


64-68: LGTM: Tests properly validate base64-encoded hash values.

The test assertions correctly use base64-encoded hash values, which is consistent with the new Hash codec implementation. This ensures the encoding/decoding logic is properly exercised in the test suite.

Also applies to: 75-79, 86-90

bin/rpc/src/methods/service-data.ts (1)

7-7: LGTM!

The imports correctly bring in the new validation utilities and type definitions needed for the withValidation wrapper pattern.

bin/rpc/src/methods/service-preimage.ts (1)

5-5: LGTM!

The imports are consistent with the new validation pattern and include all necessary utilities.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
bin/rpc/src/methods/parent.ts (1)

7-18: Update documentation to reflect the new implementation.

The documentation comment describes behavior that no longer matches the implementation:

  • States the method can return null, but the new implementation throws RpcError instead
  • Describes the return value as a tuple [Hash, Slot], but it now returns an object with header_hash and slot fields

Please update the documentation to accurately describe the new error-throwing behavior and the BlockDescriptor object return type.

🧹 Nitpick comments (1)
bin/rpc/src/methods/parent.ts (1)

38-43: Consider using BlockUnavailable for consistency.

When the parent header is missing from the database (lines 38-43), the code uses RpcErrorCode.Other. For consistency with the earlier error handling (lines 24-28), consider using RpcErrorCode.BlockUnavailable here as well, since this is also a case of an unavailable block.

Apply this diff if you agree:

     if (parentHeader === null) {
       throw new RpcError(
-        RpcErrorCode.Other,
+        RpcErrorCode.BlockUnavailable,
         `The hash of parent was found (${parentHash}) but its header doesn't exist in the database.`,
       );
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88f8544 and ddbe6f6.

📒 Files selected for processing (2)
  • bin/rpc/src/methods/list-services.ts (2 hunks)
  • bin/rpc/src/methods/parent.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • bin/rpc/src/methods/list-services.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts

⚙️ CodeRabbit configuration file

**/*.ts: rules from ./CODESTYLE.md should be adhered to.

**/*.ts: Any function whose documentation mention it must not be used in production code,
can be safely used in *.test.ts files. Other usage should be carefuly reviewed
and the comment must explain why it's safe to use there.

**/*.ts: as conversions must not be used. Suggest using tryAs conversion methods.

**/*.ts: Classes with static Codec field must have private constructor and static create method.

**/*.ts: Casting a bigint (or U64) using Number(x) must have an explanation comment why
it is safe.

**/*.ts: When making changes to code with comments containing links (in classes, constants, methods, etc.)
to graypaper.fluffylabs.dev, ensure those links point to the current version for this update.

Files:

  • bin/rpc/src/methods/parent.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 381
File: bin/rpc/src/server.ts:99-112
Timestamp: 2025-05-22T18:08:38.725Z
Learning: RPC methods should implement strict parameter validation, including checking the exact number of parameters and their types. This helps catch errors early when users confuse similar methods (e.g., serviceData vs serviceValue) and prevents cryptic errors from undefined parameters being passed to internal code.
🧬 Code graph analysis (1)
bin/rpc/src/methods/parent.ts (2)
bin/rpc/src/types.ts (4)
  • withValidation (84-92)
  • RpcError (62-70)
  • Hash (99-106)
  • BlockDescriptor (115-118)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: run (22.x)
  • GitHub Check: run (22.x)
  • GitHub Check: run (22.x)
  • GitHub Check: benchmarks (22.x)
  • GitHub Check: jam-e2e (22.x)
  • GitHub Check: test (22.x)
  • GitHub Check: e2e (22.x)
🔇 Additional comments (2)
bin/rpc/src/methods/parent.ts (2)

19-52: LGTM! Excellent use of withValidation for type safety.

The refactoring to use withValidation provides strong input/output validation:

  • Input schema z.tuple([Hash]) ensures exactly one Hash parameter is provided (addressing strict parameter validation requirements)
  • Output schema BlockDescriptor guarantees consistent return shape
  • Improved error handling with structured RpcError instances

This aligns well with the JIP-2 migration objectives.

Based on learnings


21-21: LGTM! Correct usage of Bytes.fromBlob.

The change from Bytes.fromNumbers to Bytes.fromBlob is appropriate since headerHash is now a Uint8Array after being decoded from base64 by the Hash codec.

@skoszuta skoszuta requested a review from tomusdrw November 7, 2025 17:12
Copy link
Contributor

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
bin/rpc/src/methods/service-value.ts (1)

26-28: Use BlockUnavailable (with encoded hash) for missing posterior state.

We still throw RpcErrorCode.Other when the posterior state is absent, which hides the actionable signal clients expect for unavailable blocks and omits the encoded hash payload. Please switch to RpcErrorCode.BlockUnavailable and include the base64 header hash, consistent with the other RPCs and earlier feedback.

-    if (state === null) {
-      throw new RpcError(RpcErrorCode.Other, `State not found for block: ${hashOpaque.toString()}`);
-    }
+    if (state === null) {
+      throw new RpcError(
+        RpcErrorCode.BlockUnavailable,
+        `State not found for block: ${hashOpaque.toString()}`,
+        Hash.encode(hashOpaque.raw),
+      );
+    }
bin/rpc/src/methods/statistics.ts (1)

24-24: Use RpcErrorCode.BlockUnavailable for missing state.

When the posterior state is not found, the error code should be RpcErrorCode.BlockUnavailable to allow clients to distinguish block-not-found from generic errors.

Apply this diff:

-    throw new RpcError(RpcErrorCode.Other, `State not found for block: ${hashOpaque.toString()}`);
+    throw new RpcError(
+      RpcErrorCode.BlockUnavailable,
+      `State not found for block: ${hashOpaque.toString()}`,
+      hashOpaque.raw,
+    );
bin/rpc/src/methods/list-services.ts (1)

22-22: Use RpcErrorCode.BlockUnavailable for missing state.

The error code should be BlockUnavailable rather than Other to provide clients with actionable error information.

Apply this diff:

-    throw new RpcError(RpcErrorCode.Other, `Posterior state not found for block: ${hashOpaque.toString()}`);
+    throw new RpcError(
+      RpcErrorCode.BlockUnavailable,
+      `Posterior state not found for block: ${hashOpaque.toString()}`,
+      hashOpaque.raw,
+    );
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef0dbc6 and 5fe5312.

📒 Files selected for processing (12)
  • bin/rpc/src/methods/best-block.ts (2 hunks)
  • bin/rpc/src/methods/list-services.ts (2 hunks)
  • bin/rpc/src/methods/not-implemented.ts (1 hunks)
  • bin/rpc/src/methods/parent.ts (2 hunks)
  • bin/rpc/src/methods/service-data.ts (2 hunks)
  • bin/rpc/src/methods/service-preimage.ts (2 hunks)
  • bin/rpc/src/methods/service-request.ts (2 hunks)
  • bin/rpc/src/methods/service-value.ts (2 hunks)
  • bin/rpc/src/methods/state-root.ts (2 hunks)
  • bin/rpc/src/methods/statistics.ts (2 hunks)
  • bin/rpc/src/types.ts (1 hunks)
  • bin/rpc/test/e2e.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • bin/rpc/src/methods/not-implemented.ts
  • bin/rpc/src/methods/best-block.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.ts

⚙️ CodeRabbit configuration file

**/*.ts: rules from ./CODESTYLE.md should be adhered to.

**/*.ts: Any function whose documentation mention it must not be used in production code,
can be safely used in *.test.ts files. Other usage should be carefuly reviewed
and the comment must explain why it's safe to use there.

**/*.ts: as conversions must not be used. Suggest using tryAs conversion methods.

**/*.ts: Classes with static Codec field must have private constructor and static create method.

**/*.ts: Casting a bigint (or U64) using Number(x) must have an explanation comment why
it is safe.

**/*.ts: When making changes to code with comments containing links (in classes, constants, methods, etc.)
to graypaper.fluffylabs.dev, ensure those links point to the current version for this update.

Files:

  • bin/rpc/test/e2e.ts
  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/methods/service-preimage.ts
  • bin/rpc/src/methods/statistics.ts
  • bin/rpc/src/methods/state-root.ts
  • bin/rpc/src/methods/service-request.ts
  • bin/rpc/src/methods/parent.ts
  • bin/rpc/src/methods/list-services.ts
  • bin/rpc/src/types.ts
  • bin/rpc/src/methods/service-value.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 381
File: bin/rpc/src/server.ts:99-112
Timestamp: 2025-05-22T18:08:38.725Z
Learning: RPC methods should implement strict parameter validation, including checking the exact number of parameters and their types. This helps catch errors early when users confuse similar methods (e.g., serviceData vs serviceValue) and prevents cryptic errors from undefined parameters being passed to internal code.
📚 Learning: 2025-05-22T18:08:38.725Z
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 381
File: bin/rpc/src/server.ts:99-112
Timestamp: 2025-05-22T18:08:38.725Z
Learning: RPC methods should implement strict parameter validation, including checking the exact number of parameters and their types. This helps catch errors early when users confuse similar methods (e.g., serviceData vs serviceValue) and prevents cryptic errors from undefined parameters being passed to internal code.

Applied to files:

  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/methods/service-preimage.ts
  • bin/rpc/src/methods/service-request.ts
  • bin/rpc/src/methods/list-services.ts
  • bin/rpc/src/types.ts
  • bin/rpc/src/methods/service-value.ts
📚 Learning: 2025-06-10T12:10:10.532Z
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 419
File: packages/jam/state-merkleization/serialize-update.ts:115-126
Timestamp: 2025-06-10T12:10:10.532Z
Learning: In packages/jam/state-merkleization/serialize-update.ts, service removal is handled separately from service updates. The UpdateServiceKind enum does not include a Remove variant. Service removals are handled via the servicesRemoved parameter in serializeUpdate() which is processed by serializeRemovedServices(), while service updates/creations are handled via servicesUpdates parameter processed by serializeServiceUpdates().

Applied to files:

  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/methods/list-services.ts
📚 Learning: 2025-06-10T12:04:56.072Z
Learnt from: tomusdrw
Repo: FluffyLabs/typeberry PR: 419
File: packages/jam/transition/accumulate/accumulate.ts:193-193
Timestamp: 2025-06-10T12:04:56.072Z
Learning: In the typeberry codebase, the Service.getPreimage() method was updated to return BytesBlob | null directly instead of PreimageItem | null, so the returned value is already a BytesBlob and doesn't need .blob property access.

Applied to files:

  • bin/rpc/src/methods/service-data.ts
  • bin/rpc/src/methods/service-preimage.ts
  • bin/rpc/src/methods/service-value.ts
🧬 Code graph analysis (10)
bin/rpc/test/e2e.ts (2)
bin/rpc/src/types.ts (1)
  • BlockDescriptor (115-118)
bin/rpc/src/methods/best-block.ts (1)
  • bestBlock (11-27)
bin/rpc/src/methods/service-data.ts (4)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • Hash (99-106)
  • ServiceId (112-112)
  • BlobArray (108-111)
  • RpcError (62-70)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
packages/core/codec/encoder.ts (1)
  • Encoder (73-480)
bin/rpc/src/methods/service-preimage.ts (3)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • Hash (99-106)
  • ServiceId (112-112)
  • BlobArray (108-111)
  • RpcError (62-70)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
bin/rpc/src/methods/statistics.ts (4)
bin/rpc/src/types.ts (4)
  • withValidation (84-92)
  • Hash (99-106)
  • BlobArray (108-111)
  • RpcError (62-70)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/core/codec/encoder.ts (1)
  • Encoder (73-480)
packages/jam/state/statistics.ts (1)
  • StatisticsData (271-291)
bin/rpc/src/methods/state-root.ts (2)
bin/rpc/src/types.ts (3)
  • withValidation (84-92)
  • Hash (99-106)
  • RpcError (62-70)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
bin/rpc/src/methods/service-request.ts (4)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • Hash (99-106)
  • ServiceId (112-112)
  • PreimageLength (113-113)
  • Slot (107-107)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
packages/core/numbers/index.ts (1)
  • tryAsU32 (45-48)
bin/rpc/src/methods/parent.ts (2)
bin/rpc/src/types.ts (4)
  • withValidation (84-92)
  • Hash (99-106)
  • BlockDescriptor (115-118)
  • RpcError (62-70)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
bin/rpc/src/methods/list-services.ts (2)
bin/rpc/src/types.ts (4)
  • withValidation (84-92)
  • Hash (99-106)
  • ServiceId (112-112)
  • RpcError (62-70)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
bin/rpc/src/types.ts (4)
packages/jam/database/blocks.ts (1)
  • BlocksDb (9-30)
packages/jam/database/states.ts (1)
  • StatesDb (31-51)
packages/jam/state/state.ts (2)
  • State (52-210)
  • EnumerableState (37-45)
packages/jam/config/chain-spec.ts (1)
  • ChainSpec (41-106)
bin/rpc/src/methods/service-value.ts (3)
bin/rpc/src/types.ts (5)
  • withValidation (84-92)
  • Hash (99-106)
  • ServiceId (112-112)
  • BlobArray (108-111)
  • RpcError (62-70)
packages/core/bytes/bytes.ts (1)
  • Bytes (177-243)
packages/jam/block/common.ts (1)
  • tryAsServiceId (28-28)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: benchmarks (22.x)
  • GitHub Check: e2e (22.x)
  • GitHub Check: run (22.x)
  • GitHub Check: run (22.x)
  • GitHub Check: run (22.x)
  • GitHub Check: test (22.x)
🔇 Additional comments (7)
bin/rpc/src/methods/service-request.ts (1)

21-46: LGTM! Well-structured validation and error handling.

The implementation correctly uses withValidation with explicit parameter and result schemas, aligning with the learning that RPC methods should implement strict parameter validation. The consistent null-return pattern for missing state/service/slots provides clear semantics.

bin/rpc/src/types.ts (5)

62-70: LGTM! Extended error handling.

The optional data parameter allows attaching context (like the hash that wasn't found) to error responses, improving debugging for clients.


72-77: LGTM! Well-defined error codes.

The RpcErrorCode enum provides semantic error categories aligned with JIP-2 requirements, enabling clients to distinguish missing blocks from missing work reports or DA segments.


84-92: LGTM! Clean validation wrapper.

The withValidation function properly couples schemas with methods and follows the parameter ordering suggested in past review comments. This provides the strict parameter validation recommended in learnings.

Based on learnings


115-118: LGTM! Structured block descriptor.

The BlockDescriptor object with header_hash and slot provides a cleaner API surface than positional arrays, aligning with JIP-2 structured response format.


99-106: No changes required—base64 validation is working as intended.

The z.base64() validator checks input with an internal base64 checker and rejects invalid input, producing a Zod issue with code "invalid_format". This validation occurs before the decode function executes, so the code already ensures invalid base64 inputs are caught by Zod's validation layer.

bin/rpc/src/methods/service-preimage.ts (1)

19-44: Confirm inconsistent error-handling strategy requires JIP-2 spec alignment.

The codebase shows a mix of error-handling approaches:

  • service-request.ts consistently returns null for all missing entities (state, service, slots)
  • service-preimage.ts throws RpcError for missing state and service but returns null for missing preimage
  • service-data.ts throws for missing state but returns null for missing service
  • service-value.ts throws for missing state but returns null for service and storage values

The mixed approach in service-preimage.ts (lines 27, 33 throw; line 39 returns null) is confirmed. Verify this aligns with the JIP-2 spec requirements referenced in the docstring, as the inconsistency across similar methods suggests either a bug or intentional differentiation that needs documenting.

@github-actions
Copy link

View all
File Benchmark Ops
bytes/hex-to.ts[0] number toString + padding 306619.14 ±0.46% fastest ✅
bytes/hex-to.ts[1] manual 15959.53 ±0.64% 94.79% slower
codec/encoding.ts[0] manual encode 2965718.78 ±1.44% 20.66% slower
codec/encoding.ts[1] int32array encode 3738057.91 ±0.36% fastest ✅
codec/encoding.ts[2] dataview encode 3715220.97 ±0.38% 0.61% slower
math/count-bits-u32.ts[0] standard method 82763222.68 ±2.1% 66.58% slower
math/count-bits-u32.ts[1] magic 247675472.3 ±5.39% fastest ✅
math/count-bits-u64.ts[0] standard method 1314393.16 ±0.75% 87.04% slower
math/count-bits-u64.ts[1] magic 10143521.12 ±0.95% fastest ✅
math/mul_overflow.ts[0] multiply and bring back to u32 230974478.97 ±7.01% 2.96% slower
math/mul_overflow.ts[1] multiply and take modulus 238018276.38 ±6.41% fastest ✅
math/switch.ts[0] switch 235448913.7 ±5.47% 5.39% slower
math/switch.ts[1] if 248858352.23 ±6.27% fastest ✅
codec/decoding.ts[0] manual decode 22206129.45 ±0.57% 85.67% slower
codec/decoding.ts[1] int32array decode 154921521.36 ±3.14% fastest ✅
codec/decoding.ts[2] dataview decode 152318490.41 ±3.08% 1.68% slower
collections/map-set.ts[0] 2 gets + conditional set 96967.88 ±1.79% fastest ✅
collections/map-set.ts[1] 1 get 1 set 56874.88 ±0.94% 41.35% slower
hash/index.ts[0] hash with numeric representation 173.83 ±1.31% 24.78% slower
hash/index.ts[1] hash with string representation 102.43 ±1.57% 55.68% slower
hash/index.ts[2] hash with symbol representation 161.1 ±1.57% 30.29% slower
hash/index.ts[3] hash with uint8 representation 148.36 ±1.66% 35.81% slower
hash/index.ts[4] hash with packed representation 231.11 ±1.53% fastest ✅
hash/index.ts[5] hash with bigint representation 160.75 ±5.44% 30.44% slower
hash/index.ts[6] hash with uint32 representation 180.9 ±1.53% 21.73% slower
math/add_one_overflow.ts[0] add and take modulus 239321527.87 ±6.03% 6.8% slower
math/add_one_overflow.ts[1] condition before calculation 256781467.23 ±5.24% fastest ✅
codec/bigint.compare.ts[0] compare custom 241018682.2 ±5.49% 5.47% slower
codec/bigint.compare.ts[1] compare bigint 254955883.21 ±4.31% fastest ✅
codec/bigint.decode.ts[0] decode custom 157932263.75 ±3.59% fastest ✅
codec/bigint.decode.ts[1] decode bigint 82645052.52 ±8.2% 47.67% slower
bytes/hex-from.ts[0] parse hex using Number with NaN checking 118906.17 ±1.31% 84.51% slower
bytes/hex-from.ts[1] parse hex from char codes 767490.22 ±1.19% fastest ✅
bytes/hex-from.ts[2] parse hex from string nibbles 477794.98 ±1.15% 37.75% slower
logger/index.ts[0] console.log with string concat 7132463.18 ±35.07% fastest ✅
logger/index.ts[1] console.log with args 983769.67 ±88.57% 86.21% slower
codec/view_vs_collection.ts[0] Get first element from Decoded 17137.55 ±10.31% 62.07% slower
codec/view_vs_collection.ts[1] Get first element from View 45181.36 ±1.4% fastest ✅
codec/view_vs_collection.ts[2] Get 50th element from Decoded 18929.13 ±2.82% 58.1% slower
codec/view_vs_collection.ts[3] Get 50th element from View 21611.34 ±1.98% 52.17% slower
codec/view_vs_collection.ts[4] Get last element from Decoded 18701.41 ±2.04% 58.61% slower
codec/view_vs_collection.ts[5] Get last element from View 13192.91 ±2.07% 70.8% slower
codec/view_vs_object.ts[0] Get the first field from Decoded 312982.21 ±1.71% 5.88% slower
codec/view_vs_object.ts[1] Get the first field from View 69459.69 ±1.37% 79.11% slower
codec/view_vs_object.ts[2] Get the first field as view from View 66155.45 ±1.64% 80.11% slower
codec/view_vs_object.ts[3] Get two fields from Decoded 303725.29 ±1.7% 8.66% slower
codec/view_vs_object.ts[4] Get two fields from View 52595.74 ±1.51% 84.18% slower
codec/view_vs_object.ts[5] Get two fields from materialized from View 115190.78 ±1.19% 65.36% slower
codec/view_vs_object.ts[6] Get two fields as views from View 55057.04 ±1.34% 83.44% slower
codec/view_vs_object.ts[7] Get only third field from Decoded 332535.1 ±2.26% fastest ✅
codec/view_vs_object.ts[8] Get only third field from View 68831.9 ±1.75% 79.3% slower
codec/view_vs_object.ts[9] Get only third field as view from View 72137.94 ±2.44% 78.31% slower
collections/map_vs_sorted.ts[0] Map 244766.3 ±1.05% fastest ✅
collections/map_vs_sorted.ts[1] Map-array 87624.22 ±1.43% 64.2% slower
collections/map_vs_sorted.ts[2] Array 26586.41 ±5.33% 89.14% slower
collections/map_vs_sorted.ts[3] SortedArray 170032.33 ±1.77% 30.53% slower
bytes/compare.ts[0] Comparing Uint32 bytes 13972.97 ±3.26% 5.46% slower
bytes/compare.ts[1] Comparing raw bytes 14780.05 ±3.23% fastest ✅
hash/blake2b.ts[0] our hasher 1.76 ±14.99% fastest ✅
hash/blake2b.ts[1] blake2b js 0.04 ±3.59% 97.73% slower
crypto/ed25519.ts[0] native crypto 3.81 ±21.6% 86.58% slower
crypto/ed25519.ts[1] wasm lib 9.79 ±1.88% 65.53% slower
crypto/ed25519.ts[2] wasm lib batch 28.4 ±1.66% fastest ✅

Benchmarks summary: 63/63 OK ✅

@skoszuta skoszuta merged commit 4809f38 into main Nov 12, 2025
16 checks passed
@skoszuta skoszuta deleted the sk-jip-2-data-types branch November 12, 2025 09:20
@tomusdrw tomusdrw mentioned this pull request Nov 13, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants