Skip to content

feat: event feed [skip-line-limit]#1475

Merged
ctrlc03 merged 54 commits into
mainfrom
ry/1468-event-feed
Apr 8, 2026
Merged

feat: event feed [skip-line-limit]#1475
ctrlc03 merged 54 commits into
mainfrom
ry/1468-event-feed

Conversation

@ryardley

@ryardley ryardley commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Closes: #1468

In order to better test this I added new config command to extract relevant config information:

enclave config get quic_port
9091

Can also pass --name and other cli args:

 cd tests/integration
 ../../target/debug/enclave --config ./enclave.config.yaml --name cn4 config get ctrl_port
50504
../../target/debug/enclave --config ./enclave.config.yaml --name cn4 events query
{"payload":{"HistoricalEvmSyncStart":{"evm_config":{"config":{"31337":{"deploy_block":1}}}}},"ctx":{"id":"0x8b26b731e9e24fb57dbe4da4cbe61c807de82021329a6e11837c91ae34b90217","causation_id":"0x8b26b731e9e24fb57dbe4da4cbe61c807de82021329a6e11837c91ae34b90217","origin_id":"0x8b26b731e9e24fb57dbe4da4cbe61c807de82021329a6e11837c91ae34b90217","seq":1,"ts":32750184476743752752765064395235073,"aggregate_id":0,"block":null,"source":"Local"}}
{"payload":{"NetReady":{"correlation_id":{"id":4}}},"ctx":{"id":"0x275855bf297a99c1b8f8472bf8fe2afae9a1f349f2036d2071dff43f3087a8f4","causation_id":"0x275855bf297a99c1b8f8472bf8fe2afae9a1f349f2036d2071dff43f3087a8f4","origin_id":"0x275855bf297a99c1b8f8472bf8fe2afae9a1f349f2036d2071dff43f3087a8f4","seq":2,"ts":32750184476964800087000325952249601,"aggregate_id":0,"block":null,"source":"Local"}}
{"payload":{"HistoricalNetSyncStart":{"since":{"0":0}}},"ctx":{"id":"0x10ef59e77c2543b3ce7504266a273a5defa433dfeae367908af75da00caa688a","causation_id":"0x10ef59e77c2543b3ce7504266a273a5defa433dfeae367908af75da00caa688a","origin_id":"0x10ef59e77c2543b3ce7504266a273a5defa433dfeae367908af75da00caa688a","seq":3,"ts":32750184477568156192163217966505729,"aggregate_id":0,"block":null,"source":"Local"}}
{"payload":{"HistoricalNetSyncEventsReceived":{"events":[{"payload":{"HistoricalNetSyncEventsReceived":{"events":[]}},"ctx":{"id":"0x457f64c5fdcab36b391a2fcdc43cfbe1015786fc362a4ed04db272246beceb8e","causation_id":"0x457f64c5fdcab36b391a2fcdc43cfbe1015786fc362a4ed04db272246beceb8e","origin_id":"0x10ef59e77c2543b3ce7504266a273a5defa433dfeae367908af75da00caa688a","seq":null,"ts":32750184477313259082552699411169349,"aggregate_id":0,"block":null,"source":"Net"}}]}},"ctx":{"id":"0x8ef61f12fe08aa0b17a3044d62c8854feb02b8daf63f9fdc9d4826669caf29c0","causation_id":"0x10ef59e77c2543b3ce7504266a273a5defa433dfeae367908af75da00caa688a","origin_id":"0x10ef59e77c2543b3ce7504266a273a5defa433dfeae367908af75da00caa688a","seq":4,"ts":32750184477749690600592593663958785,"aggregate_id":0,"block":null,"source":"Net"}}
{"payload":{"EffectsEnabled":{"correlation_id":{"id":47}}},"ctx":{"id":"0xc30815bbd741b9a19b7d8870ac3461230676f3911a2bd28f7265ac21f609ed88","causation_id":"0xc30815bbd741b9a19b7d8870ac3461230676f3911a2bd28f7265ac21f609ed88","origin_id":"0xc30815bbd741b9a19b7d8870ac3461230676f3911a2bd28f7265ac21f609ed88","seq":5,"ts":32750184477780828704589015387086593,"aggregate_id":0,"block":null,"source":"Local"}}
{"payload":{"HistoricalNetSyncEventsReceived":{"events":[]}},"ctx":{"id":"0x457f64c5fdcab36b391a2fcdc43cfbe1015786fc362a4ed04db272246beceb8e","causation_id":"0x457f64c5fdcab36b391a2fcdc43cfbe1015786fc362a4ed04db272246beceb8e","origin_id":"0x10ef59e77c2543b3ce7504266a273a5defa433dfeae367908af75da00caa688a","seq":6,"ts":32750184477313259082552699411169349,"aggregate_id":0,"block":null,"source":"Net"}}
{"payload":{"ConfigurationUpdated":{"parameter":"ticketPrice","old_value":"0x0","new_value":"0x989680","chain_id":31337}},"ctx":{"id":"0x8797d73ea66a3ec4b5f741d87331fa155006d9f4055bd784aac2e9f30f7ce64f","causation_id":"0x8797d73ea66a3ec4b5f741d87331fa155006d9f4055bd784aac2e9f30f7ce64f","origin_id":"0x8797d73ea66a3ec4b5f741d87331fa155006d9f4055bd784aac2e9f30f7ce64f","seq":7,"ts":32750184477429824058354470038937345,"aggregate_id":0,"block":15,"source":"Evm"}}
{"payload":{"ConfigurationUpdated":{"parameter":"licenseRequiredBond","old_value":"0x0","new_value":"0x56bc75e2d63100000","chain_id":31337}},"ctx":{"id":"0xf72e8150a2c853004a88e5adbb6db94072e5c9cb7254f55b31c642b390b1c6e2","causation_id":"0xf72e8150a2c853004a88e5adbb6db94072e5c9cb7254f55b31c642b390b1c6e2","origin_id":"0xf72e8150a2c853004a88e5adbb6db94072e5c9cb7254f55b31c642b390b1c6e2","seq":8,"ts":32750184477430986203231113740689153,"aggregate_id":0,"block":15,"source":"Evm"}}
{"payload":{"ConfigurationUpdated":{"parameter":"minTicketBalance","old_value":"0x0","new_value":"0x1","chain_id":31337}},"ctx":{"id":"0x96244bcc590d168617d112e5d697fe88e17e5dc748e2fcf535d37d1166032306","causation_id":"0x96244bcc590d168617d112e5d697fe88e17e5dc748e2fcf535d37d1166032306","origin_id":"0x96244bcc590d168617d112e5d697fe88e17e5dc748e2fcf535d37d1166032306","seq":9,"ts":32750184477431336691368514222169857,"aggregate_id":0,"block":15,"source":"Evm"}}
{"payload":{"ConfigurationUpdated":{"parameter":"exitDelay","old_value":"0x0","new_value":"0x93a80","chain_id":31337}},"ctx":{"id":"0xf292c657ad25ee1fbab57742659d1b9a2831a7990dcad9d505b00177161448a9","causation_id":"0xf292c657ad25ee1fbab57742659d1b9a2831a7990dcad9d505b00177161448a9","origin_id":"0xf292c657ad25ee1fbab57742659d1b9a2831a7990dcad9d505b00177161448a9","seq":10,"ts":32750184477431668732761840994098945,"aggregate_id":0,"block":15,"source":"Evm"}}
{"Next":10}

This is available as a REST API when the node is running

curl -X POST http://127.0.0.1:50505 \
  -H "Content-Type: application/json" \
  -d '{"command":{"EventsQuery":{"since":0,"limit":100}}}'
  • Dummy event feed to test events
  • Using the feed was excessively verbose when using json serialization so here we provide AsBytesSerde and BytesSerde trait and macro combination so that bytes like values such as ArcBytes Seed and EventId serialize in concise human readable ways for json but compact ways for binary.
  • Replaced all Vec<u8> within events with ArcBytes to take advantage of serialization memory and debugging advantages
  • Setup REST interface
  • Added enclave --name ag config get ctrl_port and other config params to aid bash script configuration.
  • Test REST interface in integration test
  • Remove dummy data

Summary by CodeRabbit

  • New Features

    • CLI: added Events (paginated event queries) and Config (retrieve config) subcommands.
    • Local HTTP daemon control channel for CLI remote execution.
  • Improvements

    • Increased CI job timeout to 60 minutes.
    • Added compact byte-serialization derive and utilities for fixed-size binary types.
    • Added cursor-based event pagination support.
  • Tests / Integration

    • Tests and integration scripts updated to allow custom backend path and to query daemon events.

@vercel

vercel Bot commented Mar 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crisp Ready Ready Preview, Comment Apr 8, 2026 2:33pm
enclave-docs Ready Ready Preview, Comment Apr 8, 2026 2:33pm

Request Review

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replace TCP socket-server with an HTTP-based daemon-server; add a BytesSerde/AsBytesSerde proc-macro and migrate many binary fields to ArcBytes; add CLI Events and Config commands and daemon dispatch; update workspace manifests, Docker/CI, tests, and related callsites.

Changes

Cohort / File(s) Summary
Daemon Server
crates/daemon-server/Cargo.toml, crates/daemon-server/src/lib.rs
Add HTTP local control API: connect_daemon, run_on_daemon, start_daemon_server, and ServerInfo; implements probe POST/HEAD, minimal HTTP parsing, per-connection handler dispatch and Console line emission.
Socket Server Removal
crates/socket-server/src/lib.rs
Remove legacy TCP socket-server crate and its public APIs (connect_socket, run_on_socket, start_socket_server).
CLI: commands & transport
crates/cli/src/main.rs, crates/cli/src/start.rs, crates/cli/src/cli.rs, crates/cli/src/config.rs, crates/cli/src/events.rs
Add Events and Config subcommands and modules; switch remote transport from socket-server to daemon-server and adjust remote CLI defaults and dispatch conversions.
Bytes Serde Proc-macro & trait
crates/utils-derive/Cargo.toml, crates/utils-derive/src/lib.rs, crates/utils/src/serde_bytes.rs, crates/utils/src/lib.rs
Add AsBytesSerde trait and #[derive(BytesSerde)] proc-macro generating Serialize/Deserialize implementations (hex string for human-readable formats or raw bytes), and re-export derive/trait in e3_utils.
Utility types & migrations
crates/utils/src/utility_types.rs, crates/events/src/event_id.rs, crates/events/src/seed.rs
Migrate ArcBytes, EventId, and Seed to AsBytesSerde/BytesSerde pattern; replace Serde derives with bytes-based ser/de and add try_from_bytes/as_bytes implementations.
Event byte fields
crates/events/src/enclave_event/...
crates/events/src/enclave_event/accusation_vote.rs, .../committee_published.rs, .../plaintext_output_published.rs, .../proof_failure_accusation.rs
Change signature/public_key/proof/plaintext fields from Vec<u8> to ArcBytes and update imports/usages.
Event cursor & pagination
crates/events/src/cursor.rs, crates/events/src/eventstore.rs, crates/events/src/lib.rs
Add SeqCursor and compute_seq_cursor, expose via events crate; add pagination boundary unit tests for timestamp and sequence queries.
Ciphernode builder
crates/ciphernode-builder/src/ciphernode_builder.rs
Apply .with_global_shared_eventstore(...) in all EventSystem build branches.
Workspace & manifests
Cargo.toml, crates/cli/Cargo.toml, crates/entrypoint/Cargo.toml, crates/utils/Cargo.toml, crates/utils-derive/Cargo.toml
Replace socket-server with daemon-server in workspace and deps; add utils-derive crate and pin proc-macro deps (proc-macro2, quote, syn, etc.); add/adjust CLI and entrypoint deps.
Docker & CI
crates/Dockerfile, examples/CRISP/server/Dockerfile, .github/workflows/ci.yml
Copy daemon-server and utils-derive manifests into build contexts; adjust stub generation for utils-derive; increase CI build_ciphernode_image timeout 30→60 minutes.
Tests & integration scripts
crates/tests/tests/integration.rs, crates/zk-prover/src/backend/tests.rs, crates/zk-prover/tests/common/helpers.rs, tests/integration/base.sh, tests/integration/fns.sh
Introduce BBPath::check() and E3_CUSTOM_BB gating in tests; add daemon event-query helper and output validation in integration scripts; adjust test setups to return Result.
Crypto & callsite updates
crates/crypto/src/sensitive.rs, crates/evm/src/slashing_manager_sol_writer.rs, crates/zk-prover/src/actors/accusation_manager.rs
Add SensitiveBytes::from_encrypted(), use ArcBytes/extract_bytes() callsites, adapt signature handling and ABI encoding callsite.
Socket→Daemon callsite edits
crates/cli/src/main.rs, crates/cli/src/start.rs, crates/entrypoint/Cargo.toml, crates/Dockerfile, examples/CRISP/server/Dockerfile
Replace socket connect/run/start calls with daemon equivalents; rename variables and adjust handler semantics to use body-based request/response.
Test-only additions
crates/events/src/*, crates/net/src/net_event_batch.rs
Add unit tests validating pagination/cursor behavior and batched event fetching semantics.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Client
    participant Daemon as Daemon HTTP Server
    participant EventActor as EventStore Actor

    rect rgba(100, 150, 255, 0.5)
    Note over CLI,EventActor: Daemon Event Query Flow
    CLI->>Daemon: POST / (EventsQuery JSON: agg,since,limit)
    Daemon->>Daemon: Parse HTTP request (Content-Length, body)
    Daemon->>EventActor: Send EventStoreQueryBy<SeqAgg> via actor
    EventActor-->>Daemon: EventStoreQueryResponse (events)
    Daemon->>Daemon: Serialize events + cursor to JSON lines
    Daemon-->>CLI: HTTP 200 OK with JSON lines
    CLI->>CLI: Parse & display each EnclaveEvent
    end
Loading
sequenceDiagram
    participant CLI as CLI Client
    participant Daemon as Daemon HTTP Server
    participant Config as AppConfig

    rect rgba(100, 150, 255, 0.5)
    Note over CLI,Config: Daemon Config Query Flow
    CLI->>Daemon: POST / (ConfigGet JSON: param)
    Daemon->>Daemon: Parse HTTP request body
    Daemon->>Config: Lookup requested parameter
    Config-->>Daemon: Return parameter value(s)
    Daemon-->>CLI: HTTP 200 OK (JSON formatted value)
    CLI->>CLI: Display config value
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ciphernode

Suggested reviewers

  • cedoor
  • 0xjei
  • hmzakhalid
  • ctrlc03

Poem

🐰 I swapped sockets for an HTTP door,
Bytes wear hex coats, and events hop more.
Configs answer calls with a cheery ping,
ArcBytes bundle bytes; the derives now sing.
A rabbit claps — the code takes one big spring!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: event feed' clearly and concisely summarizes the main feature addition of implementing an event feed API for querying events from the eventlog.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #1468: provides an API to connect to the event log (via REST EventsQuery endpoint and CLI commands), exposes an event feed for external consumption, and supports integration with related work.
Out of Scope Changes check ✅ Passed All changes are within scope: daemon-server replaces socket-server for REST communication, utils-derive and AsBytesSerde enable efficient serialization, and Vec conversions to ArcBytes support the event feed requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ry/1468-event-feed

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.

❤️ Share

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

@ryardley ryardley changed the title feat: event feed feat: event feed [skip-line-limit] Mar 24, 2026
@vercel vercel Bot temporarily deployed to Preview – enclave-docs March 24, 2026 04:26 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp March 24, 2026 04:26 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (8)
crates/zk-prover/tests/common/helpers.rs (1)

142-142: Consider using .expect() for clearer panic messages.

While .unwrap() is acceptable in test code, using .expect() provides better diagnostics when E3_CUSTOM_BB is set to an invalid path.

Suggested improvement
-    let bb_binary = BBPath::check(noir_dir.join("bin").join("bb")).unwrap();
+    let bb_binary = BBPath::check(noir_dir.join("bin").join("bb"))
+        .expect("Failed to resolve bb binary path (check E3_CUSTOM_BB if set)");

Also note that test_backend() (lines 162-165) manually handles E3_CUSTOM_BB rather than using BBPath::check(). Consider unifying these for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/zk-prover/tests/common/helpers.rs` at line 142, The current call let
bb_binary = BBPath::check(noir_dir.join("bin").join("bb")).unwrap(); should use
.expect(...) to give a clearer panic when E3_CUSTOM_BB points to an invalid
path—replace the .unwrap() with .expect("invalid E3_CUSTOM_BB or bb binary not
found: ...") to include context; additionally, update the manual handling in
test_backend() to reuse BBPath::check(...) (and propagate its expect message) so
both bb_binary initialization and test_backend() consistently validate
E3_CUSTOM_BB using BBPath::check.
crates/cli/src/main.rs (1)

74-80: Optional: update stale “socket” phrasing in the branch comment.

The condition now checks daemon availability, so the comment should match current behavior.

📝 Proposed wording tweak
-    // If the socket exists and the command can be parsed as remote
+    // If the daemon is available and the command can be parsed as remote
@@
-        // Run the command over the socket
+        // Run the command over the daemon
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/main.rs` around lines 74 - 80, Comment above the conditional
still refers to "socket" but the code checks for daemon availability via
maybe_server and maybe_remote_command; update the comment to accurately describe
this branch (e.g., "If the daemon is available and the command can be parsed as
a remote command") and place it near the conditional that uses maybe_server,
maybe_remote_command and calls run_on_daemon so the comment reflects current
behavior.
crates/cli/src/start.rs (1)

58-58: Rename launch_socket_server wording to match daemon transport.

The implementation is daemon-based now; keeping “socket server” naming/comments will cause confusion over time.

♻️ Proposed rename
-    launch_socket_server(config.ctrl_port());
+    launch_daemon_server(config.ctrl_port());
@@
-/// Launch a socket server to read RemoteCli commands
-pub fn launch_socket_server(ctrl_port: u16) {
-    // Setup socket server for daemon
+/// Launch a daemon server to read RemoteCli commands
+pub fn launch_daemon_server(ctrl_port: u16) {
+    // Setup daemon server for remote CLI commands
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/start.rs` at line 58, References to "socket server" should be
renamed to reflect the daemon-based transport: update the function name
launch_socket_server (and any related identifiers and comments) to
launch_daemon_server (or launch_daemon_transport) and adjust all call sites such
as start_daemon_server(...) and any docs/comments to use "daemon" wording so
names and comments consistently describe the daemon transport implementation.
crates/zk-prover/src/backend/tests.rs (1)

64-67: Optional: deduplicate the repeated E3_CUSTOM_BB skip guard.

The same early-return block appears in three tests; a tiny helper would reduce repetition.

♻️ Proposed cleanup
+fn has_custom_bb() -> bool {
+    env::var("E3_CUSTOM_BB").is_ok()
+}
+
 #[tokio::test]
 async fn test_check_status_full_setup_needed() {
-    if let Some(_) = env::var("E3_CUSTOM_BB").ok() {
+    if has_custom_bb() {
         return;
     }
@@
 #[tokio::test]
 async fn test_check_status_ready_when_installed() {
-    if let Some(_) = env::var("E3_CUSTOM_BB").ok() {
+    if has_custom_bb() {
         return;
     }
@@
 #[tokio::test]
 async fn test_check_status_bb_needs_update() {
-    if let Some(_) = env::var("E3_CUSTOM_BB").ok() {
+    if has_custom_bb() {
         return;
     }

Also applies to: 81-84, 114-117

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/zk-prover/src/backend/tests.rs` around lines 64 - 67, Create a small
helper to remove the repeated env-check: add a function like fn
skip_if_e3_custom_bb() -> bool { std::env::var("E3_CUSTOM_BB").is_ok() } (or fn
skip_if_e3_custom_bb() { if std::env::var("E3_CUSTOM_BB").is_ok() { return; } }
depending on preferred style) in the tests module and replace the three
occurrences of if let Some(_) = env::var("E3_CUSTOM_BB").ok() { return; } with a
single call to that helper (e.g., if skip_if_e3_custom_bb() { return; } or
skip_if_e3_custom_bb();) so all tests use the same guard and duplication is
removed.
crates/cli/src/config.rs (1)

80-109: Avoid using Debug dumps as the control-plane output contract.

nodes, program, and the no-param branch currently expose {:?} output from internal structs. That format is unstable and awkward for scripts; prefer explicit fields or a structured format if these branches are meant to be consumed outside the process.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/config.rs` around lines 80 - 109, The current code in
config.rs prints internal structs using "{:?}" (see calls to log!(out, "{:?}",
config.program()) and log!(out, "nodes: {:?}", config.nodes()) and the no-param
branch that logs many fields with "{:?}"), which exposes unstable Debug output;
change these branches to emit a stable, script-friendly representation — either
by serializing the relevant objects to a well-known structured format (e.g.,
JSON via serde) or by printing explicit, documented fields (e.g., iterate
config.nodes() and print each node's name and explicit attributes, and for
config.program() print its named fields rather than Debug). Update the log!
invocations for the "nodes", "program", and None branches to use the chosen
structured output (serialization or formatted field strings) so external
consumers get stable, parseable output.
crates/daemon-server/src/lib.rs (3)

100-111: Manual HTTP header parsing lacks bounds on header count.

While the server is localhost-only (reducing attack surface), the header-reading loop has no limit on the number of header lines. A malicious local process could send an endless stream of headers without a blank line, causing unbounded memory growth.

Since this is localhost-only and serves a trusted CLI, this is low-severity but worth noting for defense-in-depth.

♻️ Optional: add a header count limit
     // Read headers until blank line
     let mut content_length: usize = 0;
+    let mut header_count = 0;
+    const MAX_HEADERS: usize = 100;
     loop {
         let mut line = String::new();
         buf_reader.read_line(&mut line).await?;
         if line.trim().is_empty() {
             break;
         }
+        header_count += 1;
+        if header_count > MAX_HEADERS {
+            anyhow::bail!("Too many headers");
+        }
         if let Some(val) = line.to_ascii_lowercase().strip_prefix("content-length:") {
             content_length = val.trim().parse().unwrap_or(0);
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/daemon-server/src/lib.rs` around lines 100 - 111, The header-reading
loop that uses buf_reader.read_line to parse headers (and sets content_length)
has no bound on how many header lines it will accept; add a max-header limit
(e.g., const MAX_HEADERS) and a counter inside the loop that increments per
iteration and returns an error (or closes the connection) if the count exceeds
the limit before hitting the blank line; update the code around the
content_length variable and the loop so it enforces this limit and returns a
clear error when exceeded.

113-116: Large Content-Length values cause unbounded allocation.

If a client sends a huge Content-Length header, line 114 allocates that many bytes before reading. While localhost-only mitigates external attacks, a buggy or malicious local process could trigger OOM.

Consider capping content_length to a reasonable maximum (e.g., 1MB) for robustness.

♻️ Optional: cap body size
+    const MAX_BODY_SIZE: usize = 1024 * 1024; // 1MB
+    if content_length > MAX_BODY_SIZE {
+        anyhow::bail!("Request body too large");
+    }
     // Read body
     let mut body = vec![0u8; content_length];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/daemon-server/src/lib.rs` around lines 113 - 116, The code allocates
body using content_length which allows unbounded allocation; before creating the
Vec check and cap content_length against a constant MAX_BODY_SIZE (e.g.,
1_048_576) and return a clear error (or respond with 413) if the header exceeds
the cap. Then read at most the capped size (use tokio::io::AsyncReadExt::take on
buf_reader or allocate Vec with the bounded size) into body and convert to
String as before; update references to content_length where used to the
validated/capped value to avoid OOM from malicious/buggy large Content-Length
values.

38-57: Consider adding a timeout to run_on_daemon.

Unlike connect_daemon, this function has no timeout. If the daemon accepts the connection but hangs while processing, the client will block indefinitely.

♻️ Suggested timeout addition
 pub async fn run_on_daemon<T: Serialize>(
     out: Console,
     server: ServerInfo,
     cli: T,
 ) -> anyhow::Result<()> {
     let url = format!("http://{}:{}", TCP_ADDRESS, server.port);
     let client = reqwest::Client::new();
     let resp = client
         .post(&url)
         .json(&cli)
+        .timeout(std::time::Duration::from_secs(60))
         .send()
         .await?
         .error_for_status()?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/daemon-server/src/lib.rs` around lines 38 - 57, run_on_daemon
currently uses reqwest::Client::new() with no timeout so a hung daemon can block
the caller; replace the client creation with a Client built via
reqwest::Client::builder().timeout(Duration::from_secs(N)).build() (import
std::time::Duration) or set a per-request timeout on the RequestBuilder, and
keep the rest of the logic the same so send().await will error on timeout;
update the error propagation to let the timeout bubble up from run_on_daemon.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/cli/src/events.rs`:
- Around line 89-94: The pagination cursor currently sets
BatchCursor::Next(last_event_seq) which causes the next read_from() call
(inclusive: seq >= from) to re-fetch the last event; update the logic in the
block that computes `next` so it returns BatchCursor::Next(last_event_seq + 1)
instead, locating the code around the `events`, `limit`, `last_event_seq`
calculation and the use of `seq()` to ensure the next query starts from the
following event.

In `@crates/config/src/app_config.rs`:
- Line 182: Fix the typo in the doc comment in app_config.rs: change
"otherwisse" to "otherwise" in the comment above the relevant function or method
that checks the environment variable and falls back to the given path (the doc
comment starting "Check the environment variable and if found use that
otherwisse use the given path").

In `@crates/daemon-server/src/lib.rs`:
- Line 1: The SPDX header at the top of this file uses "LGPL-2.0-only" but other
files in the PR use "LGPL-3.0-only"; confirm which license applies to this crate
and make them consistent by updating the SPDX-License-Identifier line in
crates/daemon-server/src/lib.rs (the top-of-file SPDX comment) to the chosen
license, and also ensure the crate's Cargo.toml and any other license metadata
match the same SPDX identifier across the repository.

In `@crates/events/src/seed.rs`:
- Around line 39-42: The deserialize error message in Seed::try_from_bytes is a
copy/paste bug referencing "EventId requires 32 bytes"; update the map_err
message in the Seed::try_from_bytes function to a correct, clear message (e.g.
"Seed requires 32 bytes" or similar) so failures point to Seed instead of
EventId, leaving the rest of the try_from_bytes logic unchanged.

In `@tests/integration/fns.sh`:
- Around line 283-285: The port lookup assigns the result of ENCLAVE_BIN config
get ctrl_port to local ctrl_port in a single statement which masks the command's
exit status; change this to declare local ctrl_port first and then run
ENCLAVE_BIN config get ctrl_port into ctrl_port so the actual exit code is
preserved, and while doing that quote the name argument (use "$name") when
invoking ENCLAVE_BIN config get ctrl_port and keep the same --config
"$SCRIPT_DIR/enclave.config.yaml" flag so failures surface immediately.

---

Nitpick comments:
In `@crates/cli/src/config.rs`:
- Around line 80-109: The current code in config.rs prints internal structs
using "{:?}" (see calls to log!(out, "{:?}", config.program()) and log!(out,
"nodes: {:?}", config.nodes()) and the no-param branch that logs many fields
with "{:?}"), which exposes unstable Debug output; change these branches to emit
a stable, script-friendly representation — either by serializing the relevant
objects to a well-known structured format (e.g., JSON via serde) or by printing
explicit, documented fields (e.g., iterate config.nodes() and print each node's
name and explicit attributes, and for config.program() print its named fields
rather than Debug). Update the log! invocations for the "nodes", "program", and
None branches to use the chosen structured output (serialization or formatted
field strings) so external consumers get stable, parseable output.

In `@crates/cli/src/main.rs`:
- Around line 74-80: Comment above the conditional still refers to "socket" but
the code checks for daemon availability via maybe_server and
maybe_remote_command; update the comment to accurately describe this branch
(e.g., "If the daemon is available and the command can be parsed as a remote
command") and place it near the conditional that uses maybe_server,
maybe_remote_command and calls run_on_daemon so the comment reflects current
behavior.

In `@crates/cli/src/start.rs`:
- Line 58: References to "socket server" should be renamed to reflect the
daemon-based transport: update the function name launch_socket_server (and any
related identifiers and comments) to launch_daemon_server (or
launch_daemon_transport) and adjust all call sites such as
start_daemon_server(...) and any docs/comments to use "daemon" wording so names
and comments consistently describe the daemon transport implementation.

In `@crates/daemon-server/src/lib.rs`:
- Around line 100-111: The header-reading loop that uses buf_reader.read_line to
parse headers (and sets content_length) has no bound on how many header lines it
will accept; add a max-header limit (e.g., const MAX_HEADERS) and a counter
inside the loop that increments per iteration and returns an error (or closes
the connection) if the count exceeds the limit before hitting the blank line;
update the code around the content_length variable and the loop so it enforces
this limit and returns a clear error when exceeded.
- Around line 113-116: The code allocates body using content_length which allows
unbounded allocation; before creating the Vec check and cap content_length
against a constant MAX_BODY_SIZE (e.g., 1_048_576) and return a clear error (or
respond with 413) if the header exceeds the cap. Then read at most the capped
size (use tokio::io::AsyncReadExt::take on buf_reader or allocate Vec with the
bounded size) into body and convert to String as before; update references to
content_length where used to the validated/capped value to avoid OOM from
malicious/buggy large Content-Length values.
- Around line 38-57: run_on_daemon currently uses reqwest::Client::new() with no
timeout so a hung daemon can block the caller; replace the client creation with
a Client built via
reqwest::Client::builder().timeout(Duration::from_secs(N)).build() (import
std::time::Duration) or set a per-request timeout on the RequestBuilder, and
keep the rest of the logic the same so send().await will error on timeout;
update the error propagation to let the timeout bubble up from run_on_daemon.

In `@crates/zk-prover/src/backend/tests.rs`:
- Around line 64-67: Create a small helper to remove the repeated env-check: add
a function like fn skip_if_e3_custom_bb() -> bool {
std::env::var("E3_CUSTOM_BB").is_ok() } (or fn skip_if_e3_custom_bb() { if
std::env::var("E3_CUSTOM_BB").is_ok() { return; } } depending on preferred
style) in the tests module and replace the three occurrences of if let Some(_) =
env::var("E3_CUSTOM_BB").ok() { return; } with a single call to that helper
(e.g., if skip_if_e3_custom_bb() { return; } or skip_if_e3_custom_bb();) so all
tests use the same guard and duplication is removed.

In `@crates/zk-prover/tests/common/helpers.rs`:
- Line 142: The current call let bb_binary =
BBPath::check(noir_dir.join("bin").join("bb")).unwrap(); should use .expect(...)
to give a clearer panic when E3_CUSTOM_BB points to an invalid path—replace the
.unwrap() with .expect("invalid E3_CUSTOM_BB or bb binary not found: ...") to
include context; additionally, update the manual handling in test_backend() to
reuse BBPath::check(...) (and propagate its expect message) so both bb_binary
initialization and test_backend() consistently validate E3_CUSTOM_BB using
BBPath::check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9c91cf65-8a5e-4a81-8d09-747b65fd71f8

📥 Commits

Reviewing files that changed from the base of the PR and between 279c7a6 and aa57aa0.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • examples/CRISP/Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (37)
  • .github/workflows/ci.yml
  • Cargo.toml
  • crates/Dockerfile
  • crates/ciphernode-builder/src/ciphernode_builder.rs
  • crates/cli/Cargo.toml
  • crates/cli/src/cli.rs
  • crates/cli/src/config.rs
  • crates/cli/src/events.rs
  • crates/cli/src/main.rs
  • crates/cli/src/start.rs
  • crates/config/src/app_config.rs
  • crates/crypto/src/sensitive.rs
  • crates/daemon-server/Cargo.toml
  • crates/daemon-server/src/lib.rs
  • crates/entrypoint/Cargo.toml
  • crates/events/src/enclave_event/accusation_vote.rs
  • crates/events/src/enclave_event/committee_published.rs
  • crates/events/src/enclave_event/plaintext_output_published.rs
  • crates/events/src/enclave_event/proof_failure_accusation.rs
  • crates/events/src/event_id.rs
  • crates/events/src/events.rs
  • crates/events/src/seed.rs
  • crates/evm/src/slashing_manager_sol_writer.rs
  • crates/socket-server/src/lib.rs
  • crates/tests/tests/integration.rs
  • crates/utils-derive/Cargo.toml
  • crates/utils-derive/src/lib.rs
  • crates/utils/Cargo.toml
  • crates/utils/src/lib.rs
  • crates/utils/src/serde_bytes.rs
  • crates/utils/src/utility_types.rs
  • crates/zk-prover/src/actors/accusation_manager.rs
  • crates/zk-prover/src/backend/tests.rs
  • crates/zk-prover/tests/common/helpers.rs
  • examples/CRISP/server/Dockerfile
  • tests/integration/base.sh
  • tests/integration/fns.sh
💤 Files with no reviewable changes (1)
  • crates/socket-server/src/lib.rs

Comment thread crates/cli/src/events.rs Outdated
Comment thread crates/config/src/app_config.rs Outdated
Comment thread crates/daemon-server/src/lib.rs Outdated
Comment thread crates/events/src/seed.rs
Comment thread tests/integration/fns.sh
ryardley and others added 2 commits April 5, 2026 13:13
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/events/src/seed.rs`:
- Around line 44-45: The impl block for AsBytesSerde on Seed contains an extra
closing brace; locate the impl AsBytesSerde for Seed block and remove the
redundant `}` so there is only a single closing brace terminating the impl,
ensuring the block compiles correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 28dc26df-e7b0-48ff-b444-ef520d4d49b0

📥 Commits

Reviewing files that changed from the base of the PR and between aa57aa0 and 101a154.

📒 Files selected for processing (2)
  • crates/config/src/app_config.rs
  • crates/events/src/seed.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/config/src/app_config.rs

Comment thread crates/events/src/seed.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
crates/events/src/seed.rs (1)

39-42: Consider including the received length in try_from_bytes errors.

This makes malformed payload debugging faster without changing behavior.

💡 Optional refactor
     fn try_from_bytes(bytes: Vec<u8>) -> Result<Self, String> {
         Ok(Seed(
-            bytes.try_into().map_err(|_| "Seed requires 32 bytes")?,
+            bytes
+                .try_into()
+                .map_err(|b: Vec<u8>| format!("Seed requires 32 bytes, got {}", b.len()))?,
         ))
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/events/src/seed.rs` around lines 39 - 42, The error message in
try_from_bytes should include the actual received length to aid debugging: in
the Seed::try_from_bytes implementation, capture bytes.len() before the try_into
and change the map_err closure to return a String (e.g. "Seed requires 32 bytes,
got N") that interpolates the captured length instead of the current constant
message; keep the Result<Self, String> signature and behavior otherwise.
crates/daemon-server/src/lib.rs (2)

108-110: Malformed Content-Length silently treated as zero.

If Content-Length header exists but contains a non-numeric value, unwrap_or(0) will treat it as zero, causing the body to be ignored. This could lead to confusing behavior during debugging.

💡 Optional: Log a warning on parse failure
         if let Some(val) = line.to_ascii_lowercase().strip_prefix("content-length:") {
-            content_length = val.trim().parse().unwrap_or(0);
+            content_length = val.trim().parse().unwrap_or_else(|_| {
+                tracing::warn!("Malformed Content-Length header, defaulting to 0");
+                0
+            });
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/daemon-server/src/lib.rs` around lines 108 - 110, The current code
parsing the Content-Length header uses val.trim().parse().unwrap_or(0), which
silently treats malformed non-numeric values as zero; change it so you only
update content_length when parse::<usize>() succeeds and, on Err, emit a warning
(e.g., log::warn! or tracing::warn!) including the raw header value rather than
silently setting zero—i.e., replace the unwrap_or(0) with a match or if let
Ok(n) => content_length = n; Err(e) => log a warning referencing the raw val and
parsing error, leaving content_length unchanged or handling the error
explicitly.

44-50: Consider adding a timeout to the POST request.

Unlike connect_daemon which has a 1-second timeout, this POST request has no timeout configured. If the daemon hangs while processing, the CLI will block indefinitely.

💡 Suggested improvement
     let client = reqwest::Client::new();
     let resp = client
         .post(&url)
         .json(&cli)
+        .timeout(std::time::Duration::from_secs(30))
         .send()
         .await?
         .error_for_status()?;

Adjust the timeout duration based on expected maximum command execution time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/daemon-server/src/lib.rs` around lines 44 - 50, The POST request
created with reqwest::Client::new() (variables client and resp) has no timeout
and can block indefinitely; modify the code to use a client with a timeout
(e.g., via reqwest::Client::builder().timeout(Duration::from_secs(…)) or wrap
the send in tokio::time::timeout) similar to connect_daemon’s 1-second behavior,
choose an appropriate Duration for command execution, and ensure the
send().await is aborted/returns an error on timeout so resp.error_for_status()
isn’t awaited indefinitely.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/daemon-server/src/lib.rs`:
- Around line 108-110: The current code parsing the Content-Length header uses
val.trim().parse().unwrap_or(0), which silently treats malformed non-numeric
values as zero; change it so you only update content_length when
parse::<usize>() succeeds and, on Err, emit a warning (e.g., log::warn! or
tracing::warn!) including the raw header value rather than silently setting
zero—i.e., replace the unwrap_or(0) with a match or if let Ok(n) =>
content_length = n; Err(e) => log a warning referencing the raw val and parsing
error, leaving content_length unchanged or handling the error explicitly.
- Around line 44-50: The POST request created with reqwest::Client::new()
(variables client and resp) has no timeout and can block indefinitely; modify
the code to use a client with a timeout (e.g., via
reqwest::Client::builder().timeout(Duration::from_secs(…)) or wrap the send in
tokio::time::timeout) similar to connect_daemon’s 1-second behavior, choose an
appropriate Duration for command execution, and ensure the send().await is
aborted/returns an error on timeout so resp.error_for_status() isn’t awaited
indefinitely.

In `@crates/events/src/seed.rs`:
- Around line 39-42: The error message in try_from_bytes should include the
actual received length to aid debugging: in the Seed::try_from_bytes
implementation, capture bytes.len() before the try_into and change the map_err
closure to return a String (e.g. "Seed requires 32 bytes, got N") that
interpolates the captured length instead of the current constant message; keep
the Result<Self, String> signature and behavior otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e210de8f-fb5b-4752-801f-704582929149

📥 Commits

Reviewing files that changed from the base of the PR and between 101a154 and b330373.

📒 Files selected for processing (2)
  • crates/daemon-server/src/lib.rs
  • crates/events/src/seed.rs

@ryardley ryardley marked this pull request as draft April 6, 2026 06:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
crates/events/src/cursor.rs (1)

15-18: ⚠️ Potential issue | 🟠 Major

SeqCursor::Next is off-by-one for inclusive sequence reads.

compute_seq_cursor currently returns the last seen sequence, which causes duplicate events on the next page when consumed as since (inclusive read semantics). Also, limit == 0 currently yields Next(0) for an empty page.

💡 Proposed fix
 pub fn compute_seq_cursor<T: EventContextSeq>(events: &[T], limit: usize) -> SeqCursor {
-    if events.len() == limit {
-        let last_seq = events.last().map(|e| e.seq()).unwrap_or(0);
-        SeqCursor::Next(last_seq)
-    } else {
-        SeqCursor::Done
-    }
+    if limit == 0 || events.len() < limit {
+        return SeqCursor::Done;
+    }
+
+    let next_seq = events
+        .last()
+        .map(|e| e.seq().saturating_add(1))
+        .unwrap_or(0);
+    SeqCursor::Next(next_seq)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/events/src/cursor.rs` around lines 15 - 18, compute_seq_cursor
currently returns the last seen sequence directly, causing off-by-one duplicates
and producing Next(0) for an empty page; update the branch in compute_seq_cursor
so that when events.len() == limit and events.last() is Some(last_seq) you
return SeqCursor::Next(last_seq + 1) (use checked_add if needed), and if there
is no last element (empty slice / limit == 0) return the appropriate terminal
variant (e.g., SeqCursor::Done) instead of Next(0); reference compute_seq_cursor
and SeqCursor::Next in the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/events/src/cursor.rs`:
- Around line 15-18: compute_seq_cursor currently returns the last seen sequence
directly, causing off-by-one duplicates and producing Next(0) for an empty page;
update the branch in compute_seq_cursor so that when events.len() == limit and
events.last() is Some(last_seq) you return SeqCursor::Next(last_seq + 1) (use
checked_add if needed), and if there is no last element (empty slice / limit ==
0) return the appropriate terminal variant (e.g., SeqCursor::Done) instead of
Next(0); reference compute_seq_cursor and SeqCursor::Next in the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 22976c4a-2741-404f-bc8c-3a5bcf81d0be

📥 Commits

Reviewing files that changed from the base of the PR and between b330373 and ca749b3.

📒 Files selected for processing (5)
  • crates/cli/src/events.rs
  • crates/events/src/cursor.rs
  • crates/events/src/eventstore.rs
  • crates/events/src/lib.rs
  • crates/net/src/net_event_batch.rs

@ctrlc03 ctrlc03 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

utACK

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.

provide api for connecting to eventlog

2 participants