feat: v0.17.0 — Security & Efficiency Hardening#22
Merged
Conversation
P1 - Architecture (background agent): - Split node/node.rs (4575 lines) into node/ module: mod.rs, event_loop.rs, block_producer.rs, block_importer.rs, p2p_handlers.rs, dev_rpc.rs - Split rpc/handler.rs (4762 lines) into handler/ module: mod.rs, eth.rs, evm.rs, shell_api.rs, net.rs, debug.rs, admin.rs - Fix production unwrap() in cli/commands/run.rs and historical_sync.rs P2 - Core types: with direct &buf[1..] slice (buf.is_empty() already checked above) P4 - Consensus safety: - Add HashSet<Address> slashed field to PoaConfig - Modify is_authority() to exclude slashed addresses - Add slash_authority() to PoaConfig and PoaEngine - Wire equivocation slashing in event_loop.rs: replace TODO log-only with consensus.write().slash_authority(&equivocation.offender) P7 - Network efficiency: - Add send_to_peer() to NetworkService trait with default broadcast fallback - Fix BodyResponse amplification: change broadcast() to send_to_peer(&peer) preventing O(n) data amplification to all connected peers - Fix Libp2pNetwork::broadcast() missing BodyRequest/BodyResponse match arms (compile error when libp2p feature enabled) P9 - Memory safety: - Change tx_broadcast from unbounded_channel to channel(4096) - Use try_send (non-blocking) in RPC handler to avoid blocking on full channel - Update RPC server and handler types: UnboundedSender -> Sender Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- P8 RPC: default CORS from '*' → None (same-origin only) - P8 RPC: cap eth_call/eth_estimateGas gas at 50M (DoS prevention) - P8 RPC: internal_err() logs details server-side, returns generic message to caller (prevent implementation leakage) - P10 CLI: reject world-readable keystore on Unix (mode & 0o077 != 0) - P10 CLI: reject --storage-profile archive + --pruning >0 conflict - P11: RegistryError + WindowError now implement std::error::Error - P15 CI: add supply-chain job with cargo-deny + cargo-audit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add not_found_err() helper (code -32001) for resource-not-found responses — these are valid user-facing messages, not internal errors - lookup_tx_with_block: use not_found_err() for tx/block/receipt misses - resolve_block: use not_found_err() for missing blocks - send_raw_transaction: use invalid_params_err() for bad RLP/JSON format (user input error, not internal server error) - Fixes: debug_trace_transaction_not_found, trace_oe_transaction_not_found, m6b1_send_raw_transaction_invalid_format_rejected Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR targets the v0.17.0 release by hardening RPC/network/node behavior for security and operational efficiency, while also splitting large modules into smaller ones to improve maintainability.
Changes:
- Tighten RPC defaults and inputs (CORS default off,
eth_callgas cap, reduce error leakage, bound tx broadcast channel). - Reduce network amplification by introducing a unicast API surface for
BodyResponseand wiring the node to use it. - Refactor large RPC handler and node modules into focused submodules; add CI supply-chain checks.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/rpc/src/server.rs | RPC config hardening (CORS default) and bounded tx broadcast channel type. |
| crates/rpc/src/handler/mod.rs | RPC handler refactor + helper/error semantics changes + tx broadcast backpressure + eth_call gas cap. |
| crates/rpc/src/handler/shell_api.rs | New shell RPC namespace module (governance/node stats/witness APIs). |
| crates/rpc/src/handler/net.rs | New web3/net/debug RPC implementations (sha3 limits, peer count). |
| crates/rpc/src/handler/evm.rs | Extracted dev EVM RPC endpoints (mine/snapshot/etc). |
| crates/rpc/src/handler/eth.rs | Extracted ETH RPC endpoints; adjusts error classification in some paths. |
| crates/rpc/src/handler/debug.rs | Extracted trace RPC endpoints (OE-compatible traces). |
| crates/rpc/src/handler/admin.rs | Extracted admin RPC endpoints (node_info/peers/add_peer stub). |
| crates/node/src/node/mod.rs | Split monolithic node implementation into submodules and re-exports. |
| crates/node/src/node/event_loop.rs | Event loop extracted; bounded tx broadcast channel; attempts unicast BodyResponse; slashing application. |
| crates/node/src/node/block_producer.rs | Block production logic extracted. |
| crates/node/src/node/block_importer.rs | Block import/validation logic extracted. |
| crates/node/src/node/dev_rpc.rs | Dev RPC control logic extracted. |
| crates/node/src/node/p2p_handlers.rs | P2P transaction/attestation handlers extracted. |
| crates/node/src/historical_sync.rs | Minor control-flow simplification in historical body sync. |
| crates/network/src/service.rs | Adds send_to_peer API to network trait (intended unicast). |
| crates/network/src/libp2p_service.rs | Adds missing topic match arms for body messages under libp2p. |
| crates/core/src/account.rs | Removes production unwrap() from RLP decoding logic. |
| crates/consensus/src/window.rs | Implements std::error::Error for WindowError. |
| crates/consensus/src/prover_registry.rs | Implements std::error::Error for RegistryError. |
| crates/consensus/src/poa.rs | Adds slashing tracking and enforcement (slashed set + slash_authority). |
| crates/cli/src/commands/run.rs | Enforces keystore permissions on Unix; rejects archive+pruning conflict; improves retention banner output. |
| Cargo.toml | Bumps workspace version to 0.17.0. |
| CHANGELOG.md | Adds v0.17.0 release notes. |
| .github/workflows/ci.yml | Adds supply-chain CI job (cargo deny + cargo audit). |
Comments suppressed due to low confidence (6)
crates/rpc/src/handler/mod.rs:672
validate_block_is_latestreports malformed block tags viainternal_err(-32603). Since this is pure user input validation, preferinvalid_params_errso clients get -32602 and internal error logs stay reserved for actual server issues.
crates/rpc/src/handler/mod.rs:595parse_addressmaps user input errors tointernal_err, which now logs server-side and returns a generic -32603. Invalid addresses are caller mistakes and should useinvalid_params_errso clients get -32602 without polluting internal error logs.
crates/rpc/src/handler/mod.rs:608parse_hex_u64converts parse failures intointernal_err(-32603). Since this is user-controlled input, it should returninvalid_params_err(-32602) instead.
crates/rpc/src/handler/mod.rs:617parse_hex_u256rejects oversized inputs viainternal_err(-32603). Oversized/invalid hex is an invalid-params condition; useinvalid_params_errso clients can correct the request and the server doesn’t log it as an internal error.
crates/rpc/src/handler/mod.rs:646parse_block_tagis used for user-supplied block identifiers, but its error paths returninternal_err(-32603). These should beinvalid_params_err(-32602) so callers can distinguish invalid input from server faults and so the server doesn’t log bad input as an internal error.
crates/rpc/src/handler/mod.rs:602parse_hex_hashtreats malformed/incorrect-length hashes asinternal_err(-32603). These are invalid params; returninginvalid_params_errwill preserve the caller-visible error class and avoid logging as an internal server error.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+122
to
+124
|
|
||
| Ok(serde_json::json!({ | ||
| "version": "ShellChain/v0.6.0/rust", |
| async fn send_raw_transaction(&self, data: String) -> Result<ShellHash, ErrorObjectOwned> { | ||
| // Decode hex payload: "0x" + hex-encoded transaction bytes. | ||
| let raw = data.strip_prefix("0x").unwrap_or(&data); | ||
| let bytes = hex::decode(raw).map_err(|e| internal_err(format!("invalid hex: {e}")))?; |
Comment on lines
+583
to
+586
| "L4: serving BodyResponse via unicast to requesting peer" | ||
| ); | ||
| let _ = network.send_to_peer(&peer, NetworkMessage::BodyResponse { blocks }).await; | ||
| } |
Comment on lines
+353
to
+354
| .map(|r| format!("0x{}", hex::encode(r.as_bytes()))) | ||
| .unwrap_or_else(|| "null".into()); |
Comment on lines
+12
to
+16
| const MAX_HEX_LEN: usize = 32 * 1024 * 2; // 32 KB decoded = 64 KB hex | ||
| if raw.len() > MAX_HEX_LEN { | ||
| return Err(internal_err("input too large (max 32 KB)")); | ||
| } | ||
| let bytes = hex::decode(raw).map_err(|e| internal_err(format!("invalid hex: {e}")))?; |
Comment on lines
+21
to
+24
| /// Send a message to a specific peer only (unicast). | ||
| /// | ||
| /// Default implementation falls back to broadcast. Implementations that | ||
| /// support direct peer messaging should override this to avoid amplification. |
Comment on lines
+359
to
+363
| "blockHash": block_hash, | ||
| "witnessRoot": witness_root, | ||
| "witnessCount": null, | ||
| "witnesses": null, | ||
| "error": "witness store not available on this node", |
| #[jsonrpsee::core::async_trait] | ||
| impl<S: KvStore + 'static> Web3ApiServer for RpcHandler<S> { | ||
| async fn client_version(&self) -> Result<String, ErrorObjectOwned> { | ||
| Ok("shell-chain/0.6.0".to_string()) |
- net.rs: web3_sha3 returns invalid_params_err for oversized/invalid hex input - shell_api.rs: witness_root uses serde_json::Value::Null instead of string 'null' - eth.rs: send_raw_transaction hex decode error uses invalid_params_err - service.rs: send_to_peer default impl has explicit broadcast-fallback comment - event_loop.rs: BodyResponse unicast call site documents fallback behavior - deny.toml: unmaintained = 'none' (valid cargo-deny >=0.16 enum value) - cargo fmt: remove trailing blank lines across handler files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Update web3_client_version and get_node_info tests to use - Remove [licenses] deny array from deny.toml (removed in cargo-deny per PR #611; the allow list implicitly denies everything else) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- cargo update: rand 0.8.5->0.8.6 (fixes RUSTSEC-2026-0097 unsound) - cargo update: rustls-webpki 0.103.10->0.103.13 (fixes RUSTSEC-2026-0098/0099) - deny.toml: remove exceptions=[] (invalid in cargo-deny >=0.16) - deny.toml: remove syn skip entries (unmatched in tree; cargo-deny 0.16 promotes unmatched-skip to error level) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add license.workspace = true to shell-e2e-tests, shell-load-test, shell-multi-prover, shell-stark-bench (were unlicensed) - Add Unicode-3.0 and CDLA-Permissive-2.0 to [licenses] allow list (required by icu_collections and related transitive deps) - Change wildcards = 'deny' -> 'warn' (workspace = true deps in member Cargo.toml files are flagged as wildcards by cargo-deny 0.19) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR brings v0.17.0 security and efficiency hardening across the full shell-chain codebase — result of a 16-phase dual-axis (security × efficiency) code review.
Security Fixes
*→None(same-origin only)eth_callgas capped at 50 M (was unbounded)internal_err()logs server-side, returns generic message to callerchmod 600enforced)slash_authority()now mutatesPoaConfig.slashed,is_authority()excludes slashedBodyResponseunicast to requesting peer (was broadcast → O(n) amplification)tx_broadcastchannel bounded to 4096 +try_sendbackpressureReliability
--storage-profile archive+--pruning Nnow fails early with a clear error (was silently ignored)RegistryErrorandWindowErrornow implementstd::error::ErrorCode Quality
crates/node/src/node.rs(4 575 lines) split into 6 modulescrates/rpc/src/handler.rs(4 762 lines) split into 7 modulesunwrap()calls eliminatedLibp2pNetwork::broadcast()missing match arms forBodyRequest/BodyResponse(compile error withlibp2pfeature)CI / Supply Chain
New
supply-chainCI job:cargo deny check+cargo auditon every push and PR.Test Results
(3 pre-existing failures in RPC handler tests were also fixed as part of this PR)