Skip to content

Conversation

@hanabi1224
Copy link
Contributor

@hanabi1224 hanabi1224 commented Jan 28, 2026

Summary of changes

Changes introduced in this pull request:

Reference issue to close (if applicable)

Closes

Other information and links

Change checklist

  • I have performed a self-review of my own code,
  • I have made corresponding changes to the documentation. All new code adheres to the team's documentation standards,
  • I have added tests that prove my fix is effective or that my feature works (if possible),
  • I have made sure the CHANGELOG is up-to-date. All user-facing changes should be reflected in this document.

Outside contributions

  • I have read and agree to the CONTRIBUTING document.
  • I have read and agree to the AI Policy document. I understand that failure to comply with the guidelines will lead to rejection of the pull request.

Summary by CodeRabbit

  • New Features

    • RPC server supports graceful shutdown for reboot/shutdown and offline sessions.
  • New APIs

    • Admin token creation available via RPC.
  • Infrastructure

    • Offline server and RPC startup flows unified for consistent lifecycle, cleanup, and shutdown signaling.
    • Centralized background task handling with a shared service collector.
  • Tests

    • Expanded in-process RPC tests covering auth, version, and graceful shutdown.
  • Chores

    • Improved logging when admin tokens are written to disk.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

Walkthrough

Wires a graceful shutdown path for the JSON-RPC server: RPC startup now accepts a pre-bound TcpListener and a jsonrpsee::server::StopHandle, and the stop handle is threaded through daemon/service startup to allow stopping the RPC server on reboot/shutdown.

Changes

Cohort / File(s) Summary
Daemon Service Lifecycle
src/daemon/mod.rs
Thread rpc_stop_handle: jsonrpsee::server::StopHandle through start_services and maybe_start_rpc_service; stop RPC via StopHandle on reboot/shutdown; minor formatting.
RPC Server Core & Tests
src/rpc/mod.rs
start_rpc signature changed to accept rpc_listener: tokio::net::TcpListener and stop_handle: StopHandle; removed internal stop_channel creation; accept loop updated; tests updated to use in-process listener and exercise graceful shutdown and auth flows.
RPC Auth & Methods
src/rpc/methods/auth.rs
Added AuthNew::create_token(keystore, token_exp, permissions) and updated RPC handler to delegate token generation to it; reorganized imports.
Offline Server Integration
src/tool/offline_server/server.rs
Added offline_rpc_state<DB>() -> (RPCState, shutdown_rx); refactored start_offline_server/start_offline_rpc to bind a TcpListener, create a stop channel, call start_rpc with listener + stop_handle; removed duplicated keystore/token setup; updated DB-related imports.
Tool Module Exposure
src/tool/mod.rs
Made offline_server module public (pub mod offline_server;).
Subcommands: Test Snapshot
src/tool/subcommands/api_cmd/generate_test_snapshot.rs, src/tool/subcommands/api_cmd/test_snapshot.rs
Replaced ephemeral JoinSet usage with a shared JoinSet passed into MessagePool::new; added background task to drain the shared JoinSet.
Daemon Context Logging
src/daemon/context.rs
Removed immediate admin token log; added logs when token file is successfully written and narrowed directory-creation condition for save path.

Sequence Diagram

sequenceDiagram
    participant Daemon as Daemon
    participant Service as Service Lifecycle
    participant RPC as RPC Server
    participant Stop as StopHandle

    Daemon->>Service: start_services(rpc_stop_handle)
    Service->>Service: maybe_start_rpc_service(..., rpc_stop_handle)
    Service->>RPC: provide TcpListener + StopHandle
    RPC->>RPC: accept connections, handle requests
    Note over Daemon,Service: Shutdown/Reboot initiated
    Daemon->>Stop: call stop()
    Stop->>RPC: graceful shutdown signal
    RPC->>RPC: finish in-flight requests, close listener
    RPC-->>Service: stopped
    Service-->>Daemon: cleanup complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

RPC

Suggested reviewers

  • LesnyRumcajs
  • akaladarshi
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'unit test for start_rpc', but the changeset includes significant architectural changes beyond just testing: graceful shutdown mechanisms, RPC server lifecycle management refactoring, new public APIs (offline_rpc_state), and multiple signature updates across daemon, RPC, and tool modules. Update the title to reflect the primary change: introducing graceful shutdown for RPC servers and refactoring RPC lifecycle management, with supporting unit tests. Example: 'feat: add graceful shutdown for RPC servers with lifecycle management'
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@hanabi1224 hanabi1224 marked this pull request as ready for review January 28, 2026 15:26
@hanabi1224 hanabi1224 requested a review from a team as a code owner January 28, 2026 15:26
@hanabi1224 hanabi1224 requested review from LesnyRumcajs and akaladarshi and removed request for a team January 28, 2026 15:26
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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/tool/offline_server/server.rs (1)

219-239: Propagate start_rpc errors instead of discarding them.

The select branch currently ignores Err from start_rpc, so failures return Ok(()).

🐛 Proposed fix
-        _ = start_rpc(state, rpc_listener,stop_handle, None) => Ok(()),
+        result = start_rpc(state, rpc_listener, stop_handle, None) => result,
🤖 Fix all issues with AI agents
In `@src/daemon/mod.rs`:
- Around line 405-410: The code currently binds the RPC listener with
tokio::net::TcpListener::bind(rpc_address) and then calls .unwrap(), which will
panic; instead propagate the error using ? and add context: replace the
map_err/unwrap chain on the bind call that constructs rpc_listener with a call
that uses .with_context(...) (from anyhow::Context) and then ? so the enclosing
async block (which returns anyhow::Result<()>) returns the error instead of
panicking; target the rpc_listener binding expression and rpc_address to apply
this change.

In `@src/tool/offline_server/server.rs`:
- Around line 81-87: The JoinSet is being created as a temporary (&mut
JoinSet::new()) which is dropped immediately cancelling background tasks spawned
by MessagePool::new; fix by creating a persistent mutable binding (e.g., let mut
join_set = JoinSet::new()) and pass &mut join_set into MessagePool::new so the
join set lives for the required lifetime of the pool; update the surrounding
scope to retain join_set (and return or store it if necessary) so it is not
dropped, and apply the same change in the other occurrences in test_snapshot.rs
and generate_test_snapshot.rs.
🧹 Nitpick comments (2)
src/rpc/methods/auth.rs (1)

4-29: Add rustdoc + error context for the new public helper.

This new public helper lacks rustdoc, and adding context to keystore/token failures will improve diagnosability.

♻️ Proposed fix
-use anyhow::Result;
+use anyhow::{Context as _, Result};
 impl AuthNew {
+    /// Create a JWT token using the keystore's JWT_IDENTIFIER key.
     pub fn create_token(
         keystore: &KeyStore,
         token_exp: Duration,
         permissions: Vec<String>,
     ) -> anyhow::Result<String> {
-        let ki = keystore.get(JWT_IDENTIFIER)?;
-        Ok(create_token(permissions, ki.private_key(), token_exp)?)
+        let ki = keystore
+            .get(JWT_IDENTIFIER)
+            .context("failed to load JWT key from keystore")?;
+        create_token(permissions, ki.private_key(), token_exp)
+            .context("failed to create JWT token")
     }
 }

As per coding guidelines: Document all public functions and structs with rustdoc comments; Use anyhow::Result for most operations and add context with .context() method.

src/tool/offline_server/server.rs (1)

47-52: Add rustdoc for the new public offline_rpc_state helper.

Public API should be documented.

♻️ Proposed fix
+/// Build the offline RPC state and return the shutdown receiver.
 pub async fn offline_rpc_state<DB>(
     chain: NetworkChain,
     db: Arc<DB>,
     genesis_fp: Option<&Path>,
     save_jwt_token: Option<&Path>,
 ) -> anyhow::Result<(RPCState<DB>, mpsc::Receiver<()>)>

As per coding guidelines: Document all public functions and structs with rustdoc comments.

@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 68.86228% with 52 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.63%. Comparing base (0066ae3) to head (47dbb06).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/tool/offline_server/server.rs 62.62% 29 Missing and 8 partials ⚠️
src/daemon/mod.rs 25.00% 11 Missing and 1 partial ⚠️
src/rpc/methods/auth.rs 66.66% 1 Missing and 2 partials ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
src/rpc/mod.rs 80.08% <100.00%> (+55.60%) ⬆️
src/rpc/methods/auth.rs 32.55% <66.66%> (+32.55%) ⬆️
src/daemon/mod.rs 29.34% <25.00%> (-0.03%) ⬇️
src/tool/offline_server/server.rs 31.79% <62.62%> (+31.79%) ⬆️

... and 19 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0066ae3...47dbb06. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

🤖 Fix all issues with AI agents
In `@src/tool/offline_server/server.rs`:
- Around line 100-107: Do not log the raw admin JWT — remove or redact the
info!("Admin token: {token}") call in the block that creates the token (the code
that calls crate::auth::create_token with crate::auth::ADMIN and
ki.private_key()). Replace it with a safe message that does not include the full
token (e.g., log that an admin token was created and its expiry or that it was
saved to save_jwt_token when present), or log a redacted token (only show token
prefix/suffix) if you must indicate its value; ensure create_token, ADMIN,
ki.private_key(), and save_jwt_token usage remain unchanged except for removing
sensitive logging.
- Around line 226-228: The select! branch currently discards start_rpc's return
value which hides failures; change the select! arm to capture the result from
start_rpc (e.g., using `res = start_rpc(...)`) and then propagate any error (by
mapping the join to res and returning Err or using `res?`) so that a failing
start_rpc returns its anyhow::Error instead of Ok(())—update the tokio::select!
arm that references start_rpc and ensure the surrounding logic (the `result`
handling) propagates that error.
- Around line 82-90: The current background task drops JoinSet results silently;
change the spawn closure that consumes services to use while let Some(res) =
services.join_next().await and handle Err(res) instead of ignoring it: inspect
the Result returned by JoinSet::join_next (the res from services.join_next())
and log or propagate panics/cancellations (use the existing logger or return an
error) so task panics/cancellations are not silently swallowed; update the same
pattern in the other instances mentioned (test_snapshot.rs and
generate_test_snapshot.rs) referencing JoinSet, services, and join_next to match
the standard error-checking used in chain_sync/ and daemon/mod.rs.
🧹 Nitpick comments (3)
src/tool/subcommands/api_cmd/generate_test_snapshot.rs (1)

133-141: LGTM — JoinSet drainer pattern is idiomatic.

The implementation correctly creates a shared JoinSet, passes it to MessagePool::new, and spawns a background task to drain completed tasks. This aligns with the AI summary's goal of centralized task management.

One optional consideration: join_next() returns Result<T, JoinError>, so panicking tasks are currently silently ignored. For test infrastructure this is likely fine, but if visibility into task failures becomes useful, you could log errors:

♻️ Optional: Log errors from joined tasks
-    tokio::spawn(async move { while services.join_next().await.is_some() {} });
+    tokio::spawn(async move {
+        while let Some(result) = services.join_next().await {
+            if let Err(e) = result {
+                tracing::warn!("Background service task failed: {e}");
+            }
+        }
+    });
src/tool/offline_server/server.rs (2)

47-52: Add rustdoc for the new public API offline_rpc_state.
This is a public function and currently undocumented.

📘 Suggested doc comment
+/// Build an offline RPCState and return the shutdown receiver.
+///
+/// Initializes chain state, message pool, keystore, and networking context.
 pub async fn offline_rpc_state<DB>(

As per coding guidelines, Document all public functions and structs with rustdoc comments.


107-109: Use async write with error context.
std::fs::write blocks the async runtime and lacks helpful context on failure. The codebase uses tokio::fs for async file operations throughout.

⚙️ Suggested async write with context
-    if let Some(path) = save_jwt_token {
-        std::fs::write(path, token)?;
-    }
+    if let Some(path) = save_jwt_token {
+        tokio::fs::write(path, token)
+            .await
+            .with_context(|| format!("failed to write JWT to {}", path.display()))?;
+    }

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: 2

🤖 Fix all issues with AI agents
In `@src/tool/offline_server/server.rs`:
- Around line 47-53: Add a rustdoc comment for the new public function
offline_rpc_state that explains its purpose, parameters, and return values;
specifically state that it initializes and returns an RPCState<DB> for the given
NetworkChain and database (Arc<DB>), describe what genesis_fp and save_jwt_token
do (optional file paths) and that the function returns a Result containing the
(RPCState<DB>, mpsc::Receiver<()>) on success or an anyhow::Error on failure;
include any concurrency or lifetime implications and note that DB is generic and
must satisfy the trait bounds used in the function signature.
- Around line 118-123: Replace the direct use of std::fs::write when persisting
the admin JWT with the project's sensitive-file helper to ensure secure
permissions and safe overwrite semantics: in the block that checks
save_jwt_token and writes token (references: save_jwt_token, token,
std::fs::write), call the existing sensitive-file utility function (e.g.,
sensitive_file::write_sensitive or whatever helper is exported in your crate) to
create the file with restrictive permissions and proper atomic/overwrite
behavior, handling and propagating errors the same way so the info!("Admin token
is saved...") branch remains unchanged.

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