Conversation
WalkthroughThis refactor removes the HTTP endpoints and models, consolidating the API around JSON-RPC. It introduces a new JsonRpcHandler with write endpoints moved to a dedicated module, reorganizes models into api::models with explicit JsonRpcRequest/Response types, and adds account helpers for balance queries. DevnetConfig is relocated to config.rs. Error handling is unified under ApiError with new variants and RPC mapping. WebSocket endpoints adjust imports. Various modules are path-migrated (spec_reader, subscriptions, CLI). Predeployed accounts and account balance retrieval are reimplemented via internal Starknet state, returning standardized AccountBalance/SerializableAccount responses. Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 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 Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/starknet-devnet-server/src/api/endpoints_ws.rs (1)
44-47: Do not hold self.api.sockets MutexGuard across .await — refactor required.
- Problem: code locks self.api.sockets.lock().await, calls sockets.get_mut(&socket_id) and then awaits on socket_context (subscribe/notify/unsubscribe) while the guard is held — deadlock risk and unnecessary global serialisation of socket ops.
- Affected locations (verified):
- crates/starknet-devnet-server/src/api/endpoints_ws.rs — locks at 44,110,161,195,211,257; get_mut at 45,111,162,196,212,258; socket ops at 46,113,130,165,199,215,224,264,279.
- crates/starknet-devnet-server/src/api/json_rpc_handler.rs — locks/awaits at 122,145,245,307,359,568; get_mut at 571 (notify_subscribers calls at 245/307/359).
- crates/starknet-devnet-server/src/api/write_endpoints.rs — lock at 275.
- Fix (pick one): return/clone a shareable handle (Arc) from the map and drop the sockets guard before awaiting, or add short-lock helper methods on the sockets owner that perform minimal locking and run awaited work after the guard is dropped.
crates/starknet-devnet-server/src/api/error.rs (1)
309-339: Fix non-compiling match pattern for tuple variant ApiError::GeneralError
GeneralErroris a tuple variant; use_instead of{..}.- Self::GeneralError {..} + Self::GeneralError(_) | Self::StarknetDevnetError(_) | Self::NoTraceAvailable
🧹 Nitpick comments (17)
crates/starknet-devnet-server/src/api/endpoints_ws.rs (3)
120-131: Don’t hold two locks while notifying old blocks.subscribe_new_heads locks sockets, then locks starknet and loops while notifying — this multiplies contention and can deadlock if lock ordering in other code is starknet→sockets. Gather old headers first (only starknet lock), then subscribe and send notifications without holding starknet, and avoid holding the sockets map lock during awaits as noted above.
81-83: Confirm off-by-one policy for “too many blocks back”.Using “> 1024” allows exactly 1024 blocks back. If the intended limit is “at most 1024 back,” this is correct; if it should be “strictly less than 1024,” change to “>= 1024”.
155-159: Sender address defaulting may unintentionally filter to zero address.unwrap_or_default() likely yields 0x0-like default, which applies an address filter instead of “no filter.” Prefer passing Option and letting AddressFilter::new(None) mean “no filter.”
Can you confirm AddressFilter::new’s semantics for a “zero” address vs None?
Also applies to: 189-193
crates/starknet-devnet/src/main.rs (1)
339-341: Consider constructing a valid loopback URL instead of using the bound address string.If the server binds to 0.0.0.0 or ::,
http://{address}is not a routable/client URL (and IPv6 needs brackets). Prefer forminghttp://127.0.0.1:{port}orhttp://[::1]:{port}accordingly, or build aUrlfromIpAddr+ port.crates/starknet-devnet-server/src/subscribe.rs (1)
387-395: Verify error variant stability.
unsubscribedepends onApiError::InvalidSubscriptionId. If that variant was renamed during the refactor, this will change error semantics.If renamed, update the match site accordingly or add a compatibility alias.
crates/starknet-devnet-server/src/config.rs (1)
18-23: Derive Debug/Clone on DevnetConfig for symmetry and ergonomicsHelps logging and passing config around without re-borrowing.
-#[derive(Serialize)] +#[derive(Debug, Clone, Serialize)] pub struct DevnetConfig {crates/starknet-devnet-server/src/api/error.rs (1)
284-299: Add tests for new error variants and GeneralError mappingNo tests cover DumpError/MessagingError/InvalidValueError/GeneralError → wildcard RPC code. Please add focused tests similar to existing ones.
crates/starknet-devnet-server/src/api/mod.rs (1)
23-25: Surface looks good; consider guarding RPC spec version driftIf other modules reference the spec version, consider centralizing or adding a quick test asserting it matches the spec reader.
crates/starknet-devnet-server/src/api/account_helpers.rs (1)
71-83: Don’t downgrade structured errors from get_balance into GeneralErrorYou’re converting
ApiErrorintoGeneralError(String), losing fidelity. Propagate as-is.- let amount = - get_balance(starknet, address, erc20_address, BlockId::Tag(BlockTag::PreConfirmed)) - .map_err(|e| ApiError::GeneralError(e.to_string()))?; + let amount = get_balance( + starknet, + address, + erc20_address, + BlockId::Tag(BlockTag::PreConfirmed), + )?;crates/starknet-devnet-server/src/api/endpoints.rs (2)
650-673: Reduce lock hold time when building and enriching predeployed accounts.You hold the Starknet mutex across the whole accounts build and per-account balance reads. Release it after fetching the list, then reacquire only for the balance loop to lower contention.
Apply this diff:
- let mut starknet = self.api.starknet.lock().await; - let mut predeployed_accounts: Vec<_> = starknet - .get_predeployed_accounts() - .into_iter() - .map(|acc| SerializableAccount { - initial_balance: acc.initial_balance.to_string(), - address: acc.account_address, - public_key: acc.keys.public_key, - private_key: acc.keys.private_key, - balance: None, - }) - .collect(); + let mut predeployed_accounts: Vec<_> = { + let starknet = self.api.starknet.lock().await; + starknet + .get_predeployed_accounts() + .into_iter() + .map(|acc| SerializableAccount { + initial_balance: acc.initial_balance.to_string(), + address: acc.account_address, + public_key: acc.keys.public_key, + private_key: acc.keys.private_key, + balance: None, + }) + .collect() + }; @@ - if let Some(true) = - params.unwrap_or(PredeployedAccountsQuery { with_balance: Option::None }).with_balance - { - for account in predeployed_accounts.iter_mut() { + if let Some(true) = + params.unwrap_or(PredeployedAccountsQuery { with_balance: Option::None }).with_balance + { + let mut starknet = self.api.starknet.lock().await; + for account in predeployed_accounts.iter_mut() { let eth = get_balance_unit(&mut starknet, account.address, FeeUnit::WEI)?; let strk = get_balance_unit(&mut starknet, account.address, FeeUnit::FRI)?; account.balance = Some(AccountBalancesResponse { eth, strk }); } }
663-672: Normalize error mapping for balance reads.Here errors from get_balance_unit bubble up as-is, while devnet_getAccountBalance maps to ApiError::GeneralError. Consider consistent mapping to avoid divergent RPC error surfaces.
crates/starknet-devnet-server/src/api/models/json_rpc_request.rs (4)
243-279: Fix clippy lint attribute placement in method scope.Inside functions, prefer inner attributes (
#![...]) over outer (#[...]) on expressions to avoid attribute placement issues. Make this consistent with requires_notifying above.- pub fn is_forwardable_to_origin(&self) -> bool { - #[warn(clippy::wildcard_enum_match_arm)] + pub fn is_forwardable_to_origin(&self) -> bool { + #![warn(clippy::wildcard_enum_match_arm)] match self {
281-317: Same here: use inner attribute form.Align is_dumpable with the inner-attribute style used elsewhere.
- pub fn is_dumpable(&self) -> bool { - #[warn(clippy::wildcard_enum_match_arm)] + pub fn is_dumpable(&self) -> bool { + #![warn(clippy::wildcard_enum_match_arm)] match self {
387-401: And here for JsonRpcRequest methods.Use inner attribute form to avoid attaching lints to expressions.
- pub fn is_forwardable_to_origin(&self) -> bool { - #[warn(clippy::wildcard_enum_match_arm)] + pub fn is_forwardable_to_origin(&self) -> bool { + #![warn(clippy::wildcard_enum_match_arm)] match self {
459-481: Avoid brittle string matching for “method not found” vs “invalid params”.Relying on error message substrings is fragile. Consider checking the method against the known rename set to decide Method Not Found; otherwise return Invalid Params.
- serde_json::from_value::<D>(deserializable_call).map_err(|err| { - let err = err.to_string(); - // since JSON-RPC specification requires returning a Method Not Found error, - // we apply a hacky way to decide - checking the stringified error message - if err.contains("Invalid method") || err.contains(&format!("unknown variant `{}`", call.method)) { - error!(target: "rpc", method = ?call.method, "failed to deserialize RPC call: unknown method"); - RpcError::method_not_found() - } else { - error!(target: "rpc", method = ?call.method, ?err, "failed to deserialize RPC call: invalid params"); - RpcError::invalid_params(err) - } - }) + serde_json::from_value::<D>(deserializable_call).map_err(|err| { + let known = crate::api::models::JsonRpcRequest::all_variants_serde_renames(); + if !known.iter().any(|m| m == &call.method) { + error!(target: "rpc", method = ?call.method, "failed to deserialize RPC call: unknown method"); + RpcError::method_not_found() + } else { + let msg = err.to_string(); + error!(target: "rpc", method = ?call.method, err = %msg, "failed to deserialize RPC call: invalid params"); + RpcError::invalid_params(msg) + } + })crates/starknet-devnet-server/src/api/write_endpoints.rs (2)
166-221: Tighten dry-run handling and clarify provider label.Minor polish: compute is_dry_run via map/unwrap_or; set a clearer l1_provider label (e.g., "dry-run"). No behavior change.
- pub async fn postman_flush(&self, data: Option<FlushParameters>) -> StrictRpcResult { - let is_dry_run = if let Some(params) = data { params.dry_run } else { false }; + pub async fn postman_flush(&self, data: Option<FlushParameters>) -> StrictRpcResult { + let is_dry_run = data.map(|p| p.dry_run).unwrap_or(false); @@ - let l1_provider = if is_dry_run { - "dry run".to_string() + let l1_provider = if is_dry_run { + "dry-run".to_string() } else {
310-344: Mint revert reason matching is brittle.String-matching "u256_add Overflow" may vary across toolchains/contracts. Consider normalizing with a helper that recognizes common overflow patterns or return the raw reason without rewriting.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (35)
crates/starknet-devnet-server/src/api/account_helpers.rs(2 hunks)crates/starknet-devnet-server/src/api/endpoints.rs(3 hunks)crates/starknet-devnet-server/src/api/endpoints_ws.rs(1 hunks)crates/starknet-devnet-server/src/api/error.rs(6 hunks)crates/starknet-devnet-server/src/api/http/endpoints/accounts.rs(0 hunks)crates/starknet-devnet-server/src/api/http/endpoints/dump_load.rs(0 hunks)crates/starknet-devnet-server/src/api/http/endpoints/mod.rs(0 hunks)crates/starknet-devnet-server/src/api/http/endpoints/postman.rs(0 hunks)crates/starknet-devnet-server/src/api/http/endpoints/time.rs(0 hunks)crates/starknet-devnet-server/src/api/http/error.rs(0 hunks)crates/starknet-devnet-server/src/api/http/mod.rs(0 hunks)crates/starknet-devnet-server/src/api/http/models.rs(0 hunks)crates/starknet-devnet-server/src/api/json_rpc/write_endpoints.rs(0 hunks)crates/starknet-devnet-server/src/api/json_rpc_handler.rs(1 hunks)crates/starknet-devnet-server/src/api/mod.rs(1 hunks)crates/starknet-devnet-server/src/api/models/json_rpc_request.rs(3 hunks)crates/starknet-devnet-server/src/api/models/json_rpc_response.rs(1 hunks)crates/starknet-devnet-server/src/api/models/mod.rs(2 hunks)crates/starknet-devnet-server/src/api/spec_reader/mod.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/all_of_schema.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/array_primitive.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/boolean_primitive.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/integer_primitive.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/object_primitive.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/one_of_schema.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/ref_schema.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/string_primitive.rs(1 hunks)crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/tuple_schema.rs(1 hunks)crates/starknet-devnet-server/src/api/write_endpoints.rs(1 hunks)crates/starknet-devnet-server/src/config.rs(2 hunks)crates/starknet-devnet-server/src/restrictive_mode.rs(1 hunks)crates/starknet-devnet-server/src/server.rs(1 hunks)crates/starknet-devnet-server/src/subscribe.rs(1 hunks)crates/starknet-devnet/src/cli.rs(1 hunks)crates/starknet-devnet/src/main.rs(1 hunks)
💤 Files with no reviewable changes (9)
- crates/starknet-devnet-server/src/api/http/endpoints/time.rs
- crates/starknet-devnet-server/src/api/http/mod.rs
- crates/starknet-devnet-server/src/api/http/endpoints/accounts.rs
- crates/starknet-devnet-server/src/api/http/endpoints/dump_load.rs
- crates/starknet-devnet-server/src/api/http/error.rs
- crates/starknet-devnet-server/src/api/http/endpoints/mod.rs
- crates/starknet-devnet-server/src/api/json_rpc/write_endpoints.rs
- crates/starknet-devnet-server/src/api/http/endpoints/postman.rs
- crates/starknet-devnet-server/src/api/http/models.rs
🔇 Additional comments (24)
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/integer_primitive.rs (1)
4-4: Import path update looks correct.Matches the module reorg to api::spec_reader. No functional change.
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/ref_schema.rs (1)
4-4: Consistent path migration.Acceptor/Visitor now come from api::spec_reader::data_generator; aligned with the refactor.
crates/starknet-devnet-server/src/api/endpoints_ws.rs (1)
7-7: WS imports updated to new API surface.Importing JsonRpcHandler from super and JsonRpcSubscriptionRequest from api::models matches the new layout.
Also applies to: 13-13
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/array_primitive.rs (1)
4-4: Path update aligns with spec_reader move.No behavior change; consistent with sibling schema modules.
crates/starknet-devnet-server/src/server.rs (1)
17-17: Server now imports re-exported JsonRpcHandler.Clean public surface via crate::api::JsonRpcHandler. Looks good.
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/all_of_schema.rs (1)
4-4: Spec reader import migrated.Consistent with the module reorganization.
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/tuple_schema.rs (1)
4-4: Tuple schema import path updated correctly.No functional differences.
crates/starknet-devnet/src/cli.rs (1)
5-5: Approve: import path switch to server::api::models::JsonRpcRequest is correct.Search found no remaining references to the old paths; JsonRpcRequest is defined at crates/starknet-devnet-server/src/api/models/json_rpc_request.rs and is used (e.g., crates/starknet-devnet/src/cli.rs). DEFAULT_RESTRICTED_JSON_RPC_METHODS is defined at crates/starknet-devnet-server/src/restrictive_mode.rs and remains referenced from the CLI.
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/one_of_schema.rs (1)
4-4: Import path realignment looks good.Matches the new module layout; no behavior change.
crates/starknet-devnet/src/main.rs (1)
11-11: Grouped import is correct and consistent with the refactor.Assuming
server::apire-exportsJsonRpcHandlerandRPC_SPEC_VERSION, this is fine.Please run a quick repo-wide check to ensure no stale imports of the old paths remain (script provided in another comment).
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/object_primitive.rs (1)
6-6: Import path update LGTM.Consistent with the spec_reader move; no functional changes.
crates/starknet-devnet-server/src/restrictive_mode.rs (1)
22-22: Test import path updated correctly.
JsonRpcRequestnow lives underapi::models; tests remain equivalent.Please confirm there are no remaining references to
crate::api::json_rpc::JsonRpcRequest.crates/starknet-devnet-server/src/subscribe.rs (1)
20-21: Imports aligned with new API surfaces.
ApiErrorandSubscriptionIdsources match the reorg.Double-check that
ApiError::InvalidSubscriptionIdandApiError::StarknetDevnetErrorstill exist undercrate::api::error, since they’re used below (e.g., Line 388).crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/string_primitive.rs (1)
4-4: Import path update LGTM.No behavior change.
crates/starknet-devnet-server/src/api/spec_reader/spec_schemas/boolean_primitive.rs (1)
4-4: Import path update LGTM.Consistent with the module move.
crates/starknet-devnet-server/src/api/spec_reader/mod.rs (1)
173-177: Test imports repointed correctly — verification complete. No staleapi::json_rpcimports found;ApiError::InvalidSubscriptionIdis defined and referenced (crates/starknet-devnet-server/src/api/error.rs);SubscriptionIdis defined asstruct SubscriptionId(u64)(crates/starknet-devnet-server/src/api/models/mod.rs) while tests use a separate alias (tests/integration/common/utils.rs) — no further changes required.crates/starknet-devnet-server/src/config.rs (1)
18-23: Are crate-private fields intentional for a public DevnetConfig?If external consumers read fields (not just serialize),
pub(crate)will be a breaking change. Confirm intent.crates/starknet-devnet-server/src/api/account_helpers.rs (1)
29-59: Balance join path looks correctSelector, calldata, and (low, high) → BigUint join are consistent.
crates/starknet-devnet-server/src/api/json_rpc_handler.rs (2)
154-173: Forking guard requires both URL and block_numberIf a URL without block number should still enable origin calls (using latest), this conditional prevents it. Confirm desired behavior.
53-103: Request handling pipeline reads wellMethod restriction, origin fallback, dump, and broadcast ordering are sound.
crates/starknet-devnet-server/src/api/endpoints.rs (1)
679-699: LGTM: strict address/unit validation and defaults.Nice: address validated to ContractAddress with precise InvalidValueError mapping; unit defaults to FRI; block_id defaults to Latest. Response amount serialized as decimal string is consistent with other endpoints.
crates/starknet-devnet-server/src/api/models/json_rpc_response.rs (1)
25-33: LGTM: unified JsonRpcResponse envelope with Empty variant.The untagged enums and From conversions are clean, and the Empty serialization test is a nice guard.
crates/starknet-devnet-server/src/api/models/mod.rs (2)
373-392: Serialization shape for balances looks good.SerializableAccount plus AccountBalancesResponse/AccountBalanceResponse are clean and spec-aligned. Decimal strings prevent precision loss.
16-17: Unify MessageToL1/MessageToL2 type source across modules.Mixed imports found — pick one canonical path and update all occurrences; confirm the two paths resolve to the same type (or re-export one canonical module) to avoid FlushedMessages mismatches.
starknet_types::messaging::{MessageToL1, MessageToL2}
- crates/starknet-devnet-server/src/api/write_endpoints.rs:4
- crates/starknet-devnet-server/src/api/models/json_rpc_request.rs:5
starknet_types::rpc::messaging::{MessageToL1, MessageToL2}
- crates/starknet-devnet-server/src/api/models/mod.rs:16
- crates/starknet-devnet-core/src/messaging/mod.rs:38
- crates/starknet-devnet-core/src/messaging/ethereum.rs:13
FabijanC
left a comment
There was a problem hiding this comment.
It would be useful to briefly describe (in two or three bullet points) the changes in the PR description (probably under Development related changes).
Usage related changes
N/A
Development related changes
Module reorganisation, some of the imports changed. After removal of HTTP API (#842), there is no need for separate module for JSON RPC. Briefly changes are:
httpsubmodule fromapimodulejson_rpcsubmodule and moving that logic directly inapimoduleapi/http/endpointswas moved directly to the implementation ofJsonRpcHandlerinendpoints.rsandwrite_endpoints.rsas needed.mod.rsinjson_rpcwas split and moved into newmodelsmoduleChecklist:
./scripts/format.sh./scripts/clippy_check.sh./scripts/check_unused_deps.sh./scripts/check_spelling.sh./website/README.mdSummary by CodeRabbit
New Features
Refactor
Breaking Changes
Error Handling