Skip to content

Conversation

@andr-dev
Copy link
Contributor

@andr-dev andr-dev commented Oct 12, 2025

This change is a continuation of #2454 to prevent building a serde_json::Value when deserializing the JSONRPC request id.

Originally, this was solved more cleanly using #[serde(untagged)]. Unfortunately, serde uses a custom deserializer for untagged enums which results in almost the same number of allocations when deserializing using serde_json as before, leading to only a 15-20% reduction in time for the id attacks. The current solution builds a RequestRaw which internally has a RawValue which is manually parsed to reduce this attack's effectiveness by 85% as shown below.

The following benchmark change shows that the effectiveness of both of the remaining DOS vectors (attack_large_id_array and attack_large_id_dict) are reduced by about 85% while only slightly negatively impacting some of the other scenarios. Interestingly, the attack_large_payload_array_with_params and attack_large_payload_dict_with_params scenarios consistently performs better and by a similar margin to the regressions so these are all likely just noise, especially given that the payload attacks should perform similarly given the payload deserialization dominates the benchmark time and it did not change in this PR.

deserialize/eth_call    time:   [1.0819 µs 1.0824 µs 1.0830 µs]
                        thrpt:  [140.90 MiB/s 140.97 MiB/s 141.03 MiB/s]
                 change:
                        time:   [+1.2986% +1.3543% +1.4075%] (p = 0.00 < 0.05)
                        thrpt:  [-1.3880% -1.3362% -1.2819%]
                        Performance has regressed.

deserialize/eth_call-batch
                        time:   [2.4851 µs 2.4861 µs 2.4874 µs]
                        thrpt:  [494.20 MiB/s 494.46 MiB/s 494.67 MiB/s]
                 change:
                        time:   [+8.0921% +8.2988% +8.4607%] (p = 0.00 < 0.05)
                        thrpt:  [-7.8007% -7.6629% -7.4863%]
                        Performance has regressed.

deserialize/attack_large_id_array
                        time:   [4.3166 ms 4.3184 ms 4.3204 ms]
                        thrpt:  [405.07 MiB/s 405.25 MiB/s 405.42 MiB/s]
                 change:
                        time:   [-85.127% -85.088% -85.049%] (p = 0.00 < 0.05)
                        thrpt:  [+568.87% +570.59% +572.37%]
                        Performance has improved.

deserialize/attack_large_id_dict
                        time:   [4.7016 ms 4.7040 ms 4.7065 ms]
                        thrpt:  [398.39 MiB/s 398.60 MiB/s 398.81 MiB/s]
                 change:
                        time:   [-85.953% -85.916% -85.879%] (p = 0.00 < 0.05)
                        thrpt:  [+608.15% +610.02% +611.88%]
                        Performance has improved.

deserialize/attack_large_payload_array_without_params
                        time:   [4.0662 ms 4.0677 ms 4.0694 ms]
                        thrpt:  [430.06 MiB/s 430.24 MiB/s 430.40 MiB/s]
                 change:
                        time:   [+5.7669% +5.8819% +5.9794%] (p = 0.00 < 0.05)
                        thrpt:  [-5.6420% -5.5552% -5.4525%]
                        Performance has regressed.

deserialize/attack_large_payload_dict_without_params
                        time:   [4.6494 ms 4.6509 ms 4.6527 ms]
                        thrpt:  [403.01 MiB/s 403.16 MiB/s 403.29 MiB/s]
                 change:
                        time:   [+2.0851% +2.1348% +2.1854%] (p = 0.00 < 0.05)
                        thrpt:  [-2.1386% -2.0902% -2.0425%]
                        Performance has regressed.

deserialize/attack_large_payload_array_with_params
                        time:   [3.8648 ms 3.8666 ms 3.8685 ms]
                        thrpt:  [452.39 MiB/s 452.62 MiB/s 452.82 MiB/s]
                 change:
                        time:   [-7.7098% -7.5431% -7.3729%] (p = 0.00 < 0.05)
                        thrpt:  [+7.9598% +8.1585% +8.3539%]
                        Performance has improved.

deserialize/attack_large_payload_dict_with_params
                        time:   [4.6463 ms 4.6477 ms 4.6493 ms]
                        thrpt:  [403.30 MiB/s 403.44 MiB/s 403.56 MiB/s]
                 change:
                        time:   [-6.0306% -5.8925% -5.7510%] (p = 0.00 < 0.05)
                        thrpt:  [+6.1019% +6.2614% +6.4176%]
                        Performance has improved.

@andr-dev andr-dev force-pushed the andre/rpc-deserialize-raw-value branch from 790f05d to ca780a5 Compare October 13, 2025 17:13
@andr-dev andr-dev marked this pull request as draft October 13, 2025 17:39
@andr-dev andr-dev force-pushed the andre/rpc-deserialize-id branch from 63abd4a to 35b4bd4 Compare October 13, 2025 17:41
philipglazman
philipglazman previously approved these changes Oct 13, 2025
@andr-dev andr-dev force-pushed the andre/rpc-deserialize-raw-value branch from ca780a5 to 179e534 Compare October 13, 2025 22:10
@andr-dev andr-dev force-pushed the andre/rpc-deserialize-id branch 2 times, most recently from 419e9d0 to 82cfbcc Compare October 13, 2025 22:16
@andr-dev andr-dev force-pushed the andre/rpc-deserialize-raw-value branch from 179e534 to 5ca6192 Compare October 14, 2025 15:31
@andr-dev andr-dev force-pushed the andre/rpc-deserialize-id branch from 82cfbcc to 622537c Compare October 14, 2025 15:35
@andr-dev andr-dev changed the title Simplify RequestId deserialization in RPC Use RawValue for RPC id Oct 14, 2025
@andr-dev andr-dev marked this pull request as ready for review October 14, 2025 16:35
@andr-dev andr-dev force-pushed the andre/rpc-deserialize-raw-value branch from 5ca6192 to c46a2d9 Compare October 15, 2025 01:29
@andr-dev andr-dev force-pushed the andre/rpc-deserialize-id branch from 622537c to 6b41606 Compare October 20, 2025 18:07
Base automatically changed from andre/rpc-deserialize-raw-value to master October 21, 2025 22:29
@andr-dev andr-dev dismissed philipglazman’s stale review October 21, 2025 22:29

The base branch was changed.

@andr-dev andr-dev force-pushed the andre/rpc-deserialize-id branch from 6b41606 to 6e63095 Compare October 22, 2025 17:54
Copilot AI review requested due to automatic review settings October 22, 2025 17:54
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR optimizes JSONRPC request deserialization by using RawValue for the request id field instead of building a full serde_json::Value. This reduces the effectiveness of DOS attacks targeting large IDs by ~90% while maintaining acceptable performance for normal operations.

Key changes:

  • Introduced Request::from_raw_value() method that manually parses the id field from RawValue
  • Removed custom Deserialize implementation for RequestId in favor of standard serde derivation
  • Updated all call sites to use the new from_raw_value() method instead of serde_json::from_str()

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
monad-rpc/src/jsonrpc.rs Added from_raw_value() method to manually parse request ID, removed custom deserializers
monad-rpc/src/websocket/handler.rs Updated to use new to_request() signature and from_raw_value()
monad-rpc/src/handlers/mod.rs Replaced serde_json::from_str() calls with Request::from_raw_value()
monad-rpc/benches/deserialize.rs Updated benchmark to use new deserialization method

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@andr-dev andr-dev force-pushed the andre/rpc-deserialize-id branch from 6e63095 to a1e21a5 Compare October 22, 2025 18:03
@andr-dev andr-dev enabled auto-merge October 22, 2025 21:22
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