Skip to content

Conversation

@HudsonGraeme
Copy link
Member

@HudsonGraeme HudsonGraeme commented Aug 8, 2025

Summary by CodeRabbit

  • New Features

    • TEE-based DCAP proof system for end-to-end witness generation, proof creation and verification.
    • Two deployment models added with metadata and input schemas ("Honest Advisor", "Dishonest Advisor").
  • Improvements

    • Centralized circuit metadata handling supporting TOML/JSON.
    • Session storage now adapts per proof system via a factory.
    • Pre-flight checks and helpers for TEE runtime and Docker; stricter temp-folder creation permissions.
  • Removals

    • Jolt proof system and global proof-aggregation APIs removed.
  • Chores

    • .onnx files added to VCS ignore; tool/file path constants centralized.

@coderabbitai
Copy link

coderabbitai bot commented Aug 8, 2025

Walkthrough

Adds DCAP proof-system handler and session-storage variants (DCAP/Circom/EZKL), removes the Jolt handler and aggregate_proofs requirement, centralizes circuit metadata parsing into a new module with TOML/JSON support, updates constants and pre-flight tooling (teeonnx, snarkjs), adds two deployment-layer models with TOML metadata, and ignores .onnx files.

Changes

Cohort / File(s) Change Summary
Ignore Rule Update
​.gitignore
Added **.onnx ignore pattern.
Constants and Paths
neurons/constants.py
Added LOCAL_TEEONNX_PATH, LOCAL_SNARKJS_INSTALL_DIR, LOCAL_SNARKJS_PATH; added MINER_EXTERNAL_FILES and VALIDATOR_EXTERNAL_FILES lists.
Deployment Layer Models
neurons/deployment_layer/.../model_c2493.../input.py, neurons/deployment_layer/.../model_c2493.../metadata.toml,
neurons/deployment_layer/.../model_815d4.../input.py, neurons/deployment_layer/.../model_815d4.../metadata.toml
Added two new models ("Honest Advisor", "Dishonest Advisor") with Pydantic input schemas and TOML metadata referencing external network.onnx.
Circuit Metadata Module
neurons/execution_layer/circuit_metadata.py
New module: CircuitType and ProofSystem enums, CircuitMetadata dataclass with from_file loader supporting JSON and TOML.
Circuit Adjustments
neurons/execution_layer/circuit.py
Removed in-file enums/dataclass; now imports CircuitMetadata and ProofSystem; prefer metadata.toml when present; DCAP compiled-model path handling added.
Proof Handler Base / Factory
neurons/execution_layer/proof_handlers/base_handler.py, neurons/execution_layer/proof_handlers/factory.py
Removed abstract aggregate_proofs from base handler; replaced JoltHandler mapping with DCAPHandler in factory; import ProofSystem from circuit_metadata.
Proof Handlers — New / Removed
neurons/execution_layer/proof_handlers/dcap_handler.py, neurons/execution_layer/proof_handlers/jolt_handler.py, neurons/execution_layer/proof_handlers/circom_handler.py, neurons/execution_layer/proof_handlers/ezkl_handler.py
Added DCAPHandler (gen_input_file, generate_witness, gen_proof, verify_proof); removed JoltHandler; removed aggregate_proofs placeholders from Circom and EZKL handlers.
Session Storage & Factory
neurons/execution_layer/session_storage.py
Removed aggregated_proof_path/public_path from base; added EZKLSessionStorage, CircomSessionStorage (public_path), DCAPSessionStorage (output_path, quote_path, witness override); added SessionStorageFactory mapping by ProofSystem.
Verified Model Session
neurons/execution_layer/verified_model_session.py
session_storage now created via SessionStorageFactory using metadata.proof_system; removed aggregate_proofs method; adjusted temp-file cleanup (no public_path removal).
Pre-flight Checks & Helpers
neurons/utils/pre_flight.py
Use constants for tool paths; added ensure_teeonnx_installed and ensure_docker_installed; added teeonnx and conditional Docker checks; use CircuitMetadata.from_file and external_files for model downloads; removed local external-file lists.
Imports Updated Across Validator/Protocol/Notebook
neurons/_validator/**, neurons/protocol.py, neurons/incentive_mechanism.ipynb
Repointed imports of CircuitType/ProofSystem from execution_layer.circuitexecution_layer.circuit_metadata; notebook Python version metadata bumped.
System Utility
neurons/utils/system.py
get_temp_folder now calls os.makedirs with mode=0o777 when creating TEMP_FOLDER.

Sequence Diagram(s)

DCAP Proof Generation and Verification Flow

sequenceDiagram
    participant Client
    participant VerifiedModelSession
    participant DCAPHandler
    participant Docker_TEE
    participant FileSystem

    Client->>VerifiedModelSession: request proof generation
    VerifiedModelSession->>DCAPHandler: gen_proof(session)
    DCAPHandler->>FileSystem: write input JSON (gen_input_file)
    DCAPHandler->>Docker_TEE: generate_witness(session) -> produce witness + quote
    Docker_TEE->>FileSystem: write witness and quote
    DCAPHandler->>FileSystem: run teeonnx to create proof, read proof
    DCAPHandler-->>VerifiedModelSession: return proof and instances

    Client->>VerifiedModelSession: request verification
    VerifiedModelSession->>DCAPHandler: verify_proof(session, inputs, proof)
    DCAPHandler->>FileSystem: persist proof, compute SHA256 hashes
    DCAPHandler->>Docker_TEE: run teeonnx verify with hashes + expected MRENCLAVE
    DCAPHandler-->>VerifiedModelSession: return verification result (bool)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibble bytes beneath the moonlit logs,
DCAP springs up while Jolt hops off the thrones.
TOML hums its metadata, networks tucked in files,
Storages stretch their paths and pre-flights check their toils.
A rabbit cheers — new handlers, tidy homes 🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Pylint (3.3.7)
neurons/execution_layer/proof_handlers/dcap_handler.py
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dcap

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@HudsonGraeme HudsonGraeme changed the base branch from main to testnet August 8, 2025 13:58
Copy link

@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: 6

🔭 Outside diff range comments (1)
neurons/execution_layer/circuit.py (1)

412-417: Enum misuse will raise at runtime

self.metadata.proof_system is now a ProofSystem enum instance (converted in CircuitMetadata.from_file), yet you pass it to ProofSystem[...] which expects the name of a member, not an enum value.
At runtime this line will raise KeyError, preventing any circuit from loading.

-        self.proof_system = ProofSystem[self.metadata.proof_system]
+        self.proof_system = self.metadata.proof_system
🧹 Nitpick comments (6)
neurons/constants.py (1)

173-181: Consolidate external file requirements by role

Current lists diverge easily across the codebase. Suggest centralizing in a single mapping to improve maintainability and make lookups simpler.

You can add this mapping near these constants:

EXTERNAL_FILES_BY_ROLE = {
    Roles.MINER: ["circuit.zkey", "pk.key", "network.onnx"],
    Roles.VALIDATOR: ["circuit.zkey", "network.onnx"],
}

Then phase out direct use of MINER_EXTERNAL_FILES and VALIDATOR_EXTERNAL_FILES.

.gitignore (1)

198-198: Prefer a clearer pattern for ONNX files

Use a more conventional pattern. *.onnx matches in all directories; **/*.onnx is also clear. Either avoids ambiguity.

Apply one of:

-**.onnx
+*.onnx

or

-**.onnx
+**/*.onnx

If any ONNX files are intentionally tracked (e.g., small test fixtures), add exceptions like:

!path/to/fixture.onnx
neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml (1)

1-11: Fix description grammar & add checksum for external file integrity

Please update neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml as follows:

  • Correct “advise” → “advice” in the description.
  • Add an [external_files_checksums] section with the SHA-256 of the remote ONNX to ensure integrity.

Locations to update:

  • Line with description = "...advise..."
  • After the existing [external_files] block

Diff:

-description = "A dishonest advisor which provides generic cryptocurrency advise (NFA)"
+description = "A dishonest advisor which provides generic cryptocurrency advice (NFA)"

Add immediately below:

[external_files_checksums]
# SHA-256 of the file at the given URL
network.onnx = "<SHA256_CHECKSUM>"

Please compute the SHA-256 checksum of the ONNX file at
https://storage.omron.ai/815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/network.onnx
(e.g. via openssl dgst -sha256 network.onnx) and replace <SHA256_CHECKSUM> with the result.

neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml (1)

1-11: Update description grammar and add external file checksum

Please correct the typo in the description field and include a SHA-256 checksum for the remote ONNX file to ensure integrity:

• File:
neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml
• Lines: 1–11

-description = "An honest advisor which provides generic cryptocurrency advise (NFA)"
+description = "An honest advisor which provides generic cryptocurrency advice (NFA)"

Add the following section after [external_files], replacing <sha256> with the actual checksum computed in your environment:

[external_files_checksums]
# sha256 of the file at the given URL
network.onnx = "<sha256>"

Compute the SHA-256 hash locally (e.g. with sha256sum network.onnx or your preferred tool) and fill in the value above.

neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/input.py (1)

41-44: Return-type mismatch in validate

validate() is annotated to return None yet returns a CircuitInputSchema instance.
Either change the annotation to CircuitInputSchema or explicitly discard the result:

-    def validate(data: dict[str, object]) -> None:
-        return CircuitInputSchema(**data)
+    def validate(data: dict[str, object]) -> CircuitInputSchema:
+        return CircuitInputSchema(**data)
neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/input.py (1)

41-44: Return-type mismatch in validate

Same as the “Honest Advisor” input: adjust the return annotation or drop the return value.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 488c96f and 16629c2.

📒 Files selected for processing (16)
  • .gitignore (1 hunks)
  • neurons/constants.py (1 hunks)
  • neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/input.py (1 hunks)
  • neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml (1 hunks)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/input.py (1 hunks)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml (1 hunks)
  • neurons/execution_layer/circuit.py (6 hunks)
  • neurons/execution_layer/proof_handlers/base_handler.py (0 hunks)
  • neurons/execution_layer/proof_handlers/circom_handler.py (0 hunks)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
  • neurons/execution_layer/proof_handlers/ezkl_handler.py (0 hunks)
  • neurons/execution_layer/proof_handlers/factory.py (1 hunks)
  • neurons/execution_layer/proof_handlers/jolt_handler.py (0 hunks)
  • neurons/execution_layer/session_storage.py (2 hunks)
  • neurons/execution_layer/verified_model_session.py (2 hunks)
  • neurons/utils/pre_flight.py (5 hunks)
💤 Files with no reviewable changes (4)
  • neurons/execution_layer/proof_handlers/ezkl_handler.py
  • neurons/execution_layer/proof_handlers/circom_handler.py
  • neurons/execution_layer/proof_handlers/base_handler.py
  • neurons/execution_layer/proof_handlers/jolt_handler.py
🧰 Additional context used
🪛 GitHub Check: Bandit
neurons/utils/pre_flight.py

[failure] 100-101:
subprocess call with shell=True identified, security issue.


[warning] 106-106:
Chmod setting a permissive mask 0o755 on file (LOCAL_TEEONNX_PATH).

🔇 Additional comments (2)
neurons/execution_layer/proof_handlers/factory.py (1)

4-12: DCAP handler successfully integrated in ProofSystemFactory

Validation confirms:

  • ProofSystem.DCAP is defined in neurons/execution_layer/circuit.py
  • DCAPHandler class exists in neurons/execution_layer/proof_handlers/dcap_handler.py
  • Factory mapping includes ProofSystem.DCAP: DCAPHandler alongside the other handlers

All wiring matches the established pattern—no further changes needed.

neurons/execution_layer/circuit.py (1)

132-135: DCAP paths likely incomplete

For the DCAP branch only compiled_model is updated; pk and vk remain set to CIRCOM defaults (circuit.zkey, verification_key.json).
Confirm if DCAP really has no proving/verification keys; if it does, add them here to avoid downstream file-not-found errors.

Copy link

@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

♻️ Duplicate comments (2)
neurons/execution_layer/proof_handlers/dcap_handler.py (2)

92-92: Hashing a dict causes TypeError – serialize first


158-164: Ternary returns ValueError instance instead of raising

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16629c2 and 4c677e1.

📒 Files selected for processing (1)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
neurons/execution_layer/proof_handlers/dcap_handler.py (4)
neurons/execution_layer/proof_handlers/base_handler.py (1)
  • ProofSystemHandler (11-64)
neurons/execution_layer/generic_input.py (1)
  • GenericInput (7-26)
neurons/execution_layer/session_storage.py (2)
  • DCAPSessionStorage (68-86)
  • witness_path (85-86)
neurons/execution_layer/verified_model_session.py (5)
  • VerifiedModelSession (24-174)
  • gen_input_file (66-70)
  • gen_proof (72-94)
  • generate_witness (151-156)
  • verify_proof (121-137)
🪛 GitHub Check: CodeQL
neurons/execution_layer/proof_handlers/dcap_handler.py

[warning] 94-94: File is not always closed
File may not be closed if an exception is raised.


[warning] 97-97: File is not always closed
File may not be closed if an exception is raised.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL Analysis (python)

Copy link

@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: 1

♻️ Duplicate comments (1)
neurons/utils/pre_flight.py (1)

100-104: subprocess.run will fail – pass the command as a list

With shell=False (the default), a single-string args is treated as the executable path, so the call currently raises FileNotFoundError.
Switch to the vector form:

-        subprocess.run(
-            "wget https://github.com/zkonduit/teeonnx-p/releases/v23/download/teeonnx-zk-cpu-linux -O "
-            f"{LOCAL_TEEONNX_PATH}",
-            check=True,
-        )
+        subprocess.run(
+            [
+                "wget",
+                "https://github.com/zkonduit/teeonnx-p/releases/v23/download/teeonnx-zk-cpu-linux",
+                "-O",
+                LOCAL_TEEONNX_PATH,
+            ],
+            check=True,
+        )

This eliminates the Bandit warning, avoids invoking a shell, and actually executes.

🧹 Nitpick comments (1)
neurons/utils/pre_flight.py (1)

16-24: Consolidate duplicated constants import

constants is already imported on Line 8. Re-importing the same module adds nothing and slightly slows startup; merge these into the first import.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c677e1 and a875d73.

📒 Files selected for processing (1)
  • neurons/utils/pre_flight.py (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
neurons/utils/pre_flight.py (1)
neurons/constants.py (1)
  • Roles (6-8)
🪛 GitHub Check: Semgrep OSS
neurons/utils/pre_flight.py

[warning] 105-105: Semgrep Finding: python.lang.security.audit.insecure-file-permissions.insecure-file-permissions
These permissions 0o700 are widely permissive and grant access to more people than may be necessary. A good default is 0o644 which gives read and write access to yourself and read access to everyone else.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL Analysis (python)

Copy link

@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: 6

🔭 Outside diff range comments (1)
neurons/utils/pre_flight.py (1)

259-266: Critical: Add support for metadata.toml in pre-flight sync

Our scan found two models in neurons/deployment_layer that ship only metadata.toml and would be skipped by the current JSON-only check. Please update the logic in neurons/utils/pre_flight.py (lines 259–266) to probe for both formats, preferring JSON when present:

-        metadata_file = os.path.join(MODEL_DIR, model_hash, "metadata.json")
-        if not os.path.isfile(metadata_file):
+        metadata_json = os.path.join(MODEL_DIR, model_hash, "metadata.json")
+        metadata_toml = os.path.join(MODEL_DIR, model_hash, "metadata.toml")
+        # prefer JSON, but fall back to TOML if JSON is missing
+        metadata_file = metadata_json if os.path.isfile(metadata_json) else metadata_toml
+        if not os.path.isfile(metadata_file):
             bt.logging.error(
-                SYNC_LOG_PREFIX
-                + f"Metadata file not found at {metadata_file} for {model_hash}. Skipping sync for this model."
+                SYNC_LOG_PREFIX
+                + f"No metadata.json or metadata.toml found in {os.path.join(MODEL_DIR, model_hash)} for {model_hash}. Skipping sync."
             )
             continue

This ensures both legacy JSON and newer TOML metadata are handled.

♻️ Duplicate comments (3)
neurons/utils/pre_flight.py (1)

157-162: Remove shell=True and avoid piping via shell for EZKL install.

Shell piping is flagged by security scanners and is unnecessary here. Fetch the script and pass it to bash directly.

-        # trunk-ignore(bandit/B605)
-        subprocess.run(
-            f"curl -s https://raw.githubusercontent.com/zkonduit/ezkl/main/install_ezkl_cli.sh | bash -s -- v{python_ezkl_version}",  # noqa
-            shell=True,
-            check=True,
-        )
+        # Download install script then execute with bash, avoiding shell=True and pipes
+        curl_res = subprocess.run(
+            ["curl", "-s", "https://raw.githubusercontent.com/zkonduit/ezkl/main/install_ezkl_cli.sh"],
+            check=True,
+            capture_output=True,
+        )
+        subprocess.run(
+            ["bash", "-s", "--", f"v{python_ezkl_version}"],
+            input=curl_res.stdout,
+            check=True,
+        )
neurons/execution_layer/proof_handlers/dcap_handler.py (2)

34-41: Validate presence of session.inputs.data before writing JSON

Guard against None to avoid runtime errors when serializing inputs.

Apply this diff:

     def gen_input_file(self, session: VerifiedModelSession):
         bt.logging.trace("Generating input file")
-        if session.inputs is None:
-            raise ValueError("Session inputs cannot be None when generating input file")
+        if session.inputs is None:
+            raise ValueError("Session inputs cannot be None when generating input file")
+        if getattr(session.inputs, "data", None) is None:
+            raise ValueError("Session inputs.data cannot be None when generating input file")
         os.makedirs(os.path.dirname(session.session_storage.input_path), exist_ok=True)
         with open(session.session_storage.input_path, "w", encoding="utf-8") as f:
             json.dump(session.inputs.data, f)

165-196: Harden docker invocation: cleanup and timeouts

Add --rm to avoid container buildup and a timeout to prevent indefinite hangs. Also consider surfacing stderr on failure.

Apply this diff:

         result = subprocess.run(
             [
                 "docker",
                 "run",
+                "--rm",
                 "--device",
                 SGX_ENCLAVE_PATH,
                 "--device",
                 SGX_PROVISION_PATH,
                 "-v",
                 f"{session.session_storage.base_path}:{WORKSPACE_PATH}",
                 SGX_IMAGE,
                 "gen-output",
                 "--input",
                 os.path.join(
                     WORKSPACE_PATH, os.path.basename(session.session_storage.input_path)
                 ),
                 "--model",
                 os.path.join(WORKSPACE_PATH, f"network_{session.model_id}.onnx"),
                 "--output",
                 os.path.join(
                     WORKSPACE_PATH,
                     os.path.basename(session.session_storage.witness_path),
                 ),
                 "--quote",
                 os.path.join(
                     WORKSPACE_PATH, os.path.basename(session.session_storage.quote_path)
                 ),
             ],
             check=True,
             capture_output=True,
             text=True,
+            timeout=600,
         )
🧹 Nitpick comments (12)
neurons/execution_layer/circuit_metadata.py (3)

63-73: Docstring mismatch: supports TOML and JSON, not just JSON.

from_file handles both .json and .toml, but the docstring refers only to JSON and “ModelMetadata”. Update for accuracy and to reflect the class name.

-        """
-        Create a ModelMetadata instance from a JSON file.
-        
-        Args:
-            metadata_path (str): Path to the metadata JSON file.
-        
-        Returns:
-            ModelMetadata: An instance of ModelMetadata.
-        """
+        """
+        Load CircuitMetadata from a JSON or TOML file.
+
+        Args:
+            metadata_path (str): Path to the metadata file (.json or .toml).
+
+        Returns:
+            CircuitMetadata: An instance populated from the file.
+        """

75-81: Fail fast on unsupported extensions instead of loading empty metadata.

Returning an empty dict on unknown extensions leads to a less clear TypeError later. Raise a ValueError immediately with guidance.

-        with open(metadata_path, "r", encoding="utf-8") as f:
-            if metadata_path.endswith(".json"):
+        with open(metadata_path, "r", encoding="utf-8") as f:
+            if metadata_path.endswith(".json"):
                 metadata = json.load(f)
             elif metadata_path.endswith(".toml"):
                 metadata = toml.load(f)
             else:
-                metadata = {}
+                raise ValueError(
+                    f"Unsupported metadata file extension for {metadata_path}. "
+                    "Expected .json or .toml"
+                )

45-61: Consider making external_files optional with a default.

If a metadata file omits external_files, from_file will raise. Making it optional reduces friction for simple circuits.

-from dataclasses import dataclass
+from dataclasses import dataclass, field
@@
-    external_files: dict[str, str]
+    external_files: dict[str, str] = field(default_factory=dict)
neurons/utils/pre_flight.py (1)

268-273: Use CircuitMetadata directly; avoid re-wrapping to dict.

You can drop the intermediate dict and read external_files straight from the dataclass.

-            circuit_metadata = CircuitMetadata.from_file(metadata_file)
-            metadata = {"external_files": circuit_metadata.external_files}
+            circuit_metadata = CircuitMetadata.from_file(metadata_file)

And below:

-        external_files = metadata.get("external_files", {})
+        external_files = circuit_metadata.external_files or {}
neurons/_validator/models/miner_response.py (1)

15-16: Import move to circuit_metadata LGTM; add DCAP-specific proof_size for better metrics

Shifting ProofSystem to the centralized metadata module is correct. One follow-up: DCAP proofs currently fall into the default branch for proof_size, which may skew evaluation metrics. Consider adding a DCAP branch that derives a stable size from the teeonnx proof payload.

Example (apply outside this hunk):

# Inside from_raw_response(), when computing proof_size:
elif response.circuit.proof_system == ProofSystem.DCAP:
    try:
        # Prefer size of the 'proof' field if present; else length of serialized object
        payload = proof_content.get("proof", proof_content)
        proof_size = len(json.dumps(payload, separators=(",", ":")))
    except Exception:
        proof_size = DEFAULT_PROOF_SIZE
neurons/execution_layer/circuit.py (2)

94-96: DCAP compiled model path: add existence guard for clearer failures

Pointing compiled_model to external network.onnx is correct for DCAP. Add a guard so missing models fail fast with a clear message (helps avoid late failures in handlers).

Apply this diff:

-        elif proof_system == ProofSystem.DCAP:
-            self.compiled_model = os.path.join(self.external_base_path, "network.onnx")
+        elif proof_system == ProofSystem.DCAP:
+            self.compiled_model = os.path.join(self.external_base_path, "network.onnx")
+            if not os.path.exists(self.compiled_model):
+                logging.error(
+                    f"DCAP compiled model not found at {self.compiled_model}. "
+                    "Ensure the ONNX model is placed under the external models path."
+                )
+                raise FileNotFoundError(self.compiled_model)

329-334: Mapping metadata.proof_system to enum: add defensive handling for invalid values

Accessing ProofSystem[...] assumes exact enum name. Add a defensive guard to surface a helpful error if metadata is invalid or case-mismatched.

Apply this diff:

-        self.proof_system = ProofSystem[self.metadata.proof_system]
+        try:
+            self.proof_system = ProofSystem[self.metadata.proof_system]
+        except KeyError as e:
+            raise ValueError(
+                f"Unsupported proof_system in metadata: {self.metadata.proof_system!r}"
+            ) from e
neurons/execution_layer/proof_handlers/dcap_handler.py (1)

157-160: Return type annotation mismatch

Function may return str (stdout) or JSON content, but annotation is list | dict. Update for accuracy.

Apply this diff:

-    def generate_witness(
-        self, session: VerifiedModelSession, return_content: bool = False
-    ) -> list | dict:
+    def generate_witness(
+        self, session: VerifiedModelSession, return_content: bool = False
+    ) -> str | list | dict:
neurons/execution_layer/session_storage.py (4)

57-66: Make CircomSessionStorage a dataclass (or drop field()); current field() isn’t effective

The annotation public_path: str = field(init=False) in a subclass that isn’t decorated with @DataClass is treated as a class attribute holding a dataclasses.Field, not a dataclass field. Instance behavior works because you assign self.public_path in __post_init__, but this is misleading and breaks dataclass-based introspection.

Apply this diff to make the intent explicit and correct:

+@dataclass
 class CircomSessionStorage(SessionStorage):
     public_path: str = field(init=False)

Alternatively, if you prefer not to make the subclass a dataclass, remove field() usage:

-class CircomSessionStorage(SessionStorage):
-    public_path: str = field(init=False)
+class CircomSessionStorage(SessionStorage):
+    public_path: str  # set in __post_init__

68-89: Also decorate DCAPSessionStorage as a dataclass (field() otherwise becomes a class-level Field object)

Similar to Circom, output_path: str = field(init=False) and quote_path: str = field(init=False) in a non-dataclass subclass do not create dataclass fields. While instance assignment in __post_init__ works, this is confusing and harms tooling/introspection.

Apply this diff:

- class DCAPSessionStorage(SessionStorage):
+@dataclass
+class DCAPSessionStorage(SessionStorage):
     output_path: str = field(init=False)
     quote_path: str = field(init=False)

72-99: Optional: add a witness_path setter and call super().post_init() to DRY path initialization

To reduce duplication and stay in sync if the base changes how it computes input_path/proof_path, you can implement a setter for witness_path that re-routes to output_path, then call super().__post_init__(). This keeps base logic centralized while honoring DCAP’s different witness/output semantics.

Apply this diff:

-    def __post_init__(self):
-        # Initialize base path directory (from parent's __post_init__)
-        if not os.path.exists(self.base_path):
-            os.makedirs(self.base_path)
-        # Initialize paths required by base class, excluding witness_path (handled by property)
-        self.input_path = os.path.join(
-            self.base_path, f"input_{self.model_id}_{self.session_uuid}.json"
-        )
-        self.proof_path = os.path.join(
-            self.base_path, f"proof_{self.model_id}_{self.session_uuid}.json"
-        )
-        # Initialize DCAP-specific paths
-        self.output_path = os.path.join(
-            self.base_path, f"output_{self.model_id}_{self.session_uuid}.json"
-        )
-        self.quote_path = os.path.join(
-            self.base_path, f"quote_{self.model_id}_{self.session_uuid}.bin"
-        )
+    def __post_init__(self):
+        # Use base initialization; witness_path assignment will trigger our setter below
+        super().__post_init__()
+        # Initialize DCAP-specific paths
+        self.quote_path = os.path.join(
+            self.base_path, f"quote_{self.model_id}_{self.session_uuid}.bin"
+        )
         bt.logging.debug(
             f"DCAPSessionStorage initialized with model_id: {self.model_id} and session_uuid: {self.session_uuid}"
         )
         bt.logging.trace(f"DCAPSessionStorage: Input path: {self.input_path}")
-        bt.logging.trace(f"DCAPSessionStorage: Output path: {self.output_path}")
+        bt.logging.trace(f"DCAPSessionStorage: Output path: {self.output_path}")
         bt.logging.trace(f"DCAPSessionStorage: Quote path: {self.quote_path}")
         bt.logging.trace(f"DCAPSessionStorage: Proof path: {self.proof_path}")
         bt.logging.trace(f"DCAPSessionStorage: Witness path: {self.witness_path}")

And extend the property to include a setter:

     @property
     def witness_path(self) -> str:
         return self.output_path
+
+    @witness_path.setter
+    def witness_path(self, _value: str) -> None:
+        # Map witness_path to DCAP's output_path to keep base init DRY
+        self.output_path = os.path.join(
+            self.base_path, f"output_{self.model_id}_{self.session_uuid}.json"
+        )

111-129: String-to-enum coercion: trim whitespace for robustness; consider explicit typing

Minor resilience improvement: when accepting strings for proof_system, trim whitespace before uppercasing to avoid subtle input issues.

Apply this diff:

-        if isinstance(proof_system, str):
-            try:
-                proof_system = ProofSystem[proof_system.upper()]
+        if isinstance(proof_system, str):
+            try:
+                proof_system = ProofSystem[proof_system.strip().upper()]

Optionally, update the type hint to reflect accepted inputs:

  • Change the signature to: def create_storage(cls, proof_system: "ProofSystem | str", model_id: str, session_uuid: str) -> SessionStorage:
  • Or import Union and annotate as Union[ProofSystem, str].
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 56401a1 and 2f7b954.

📒 Files selected for processing (15)
  • neurons/_validator/core/request_pipeline.py (1 hunks)
  • neurons/_validator/models/miner_response.py (1 hunks)
  • neurons/_validator/models/poc_rpc_request.py (1 hunks)
  • neurons/_validator/pow/proof_of_weights_handler.py (1 hunks)
  • neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml (1 hunks)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml (1 hunks)
  • neurons/execution_layer/circuit.py (3 hunks)
  • neurons/execution_layer/circuit_metadata.py (1 hunks)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
  • neurons/execution_layer/proof_handlers/factory.py (1 hunks)
  • neurons/execution_layer/session_storage.py (2 hunks)
  • neurons/incentive_mechanism.ipynb (3 hunks)
  • neurons/protocol.py (1 hunks)
  • neurons/utils/pre_flight.py (7 hunks)
  • neurons/utils/system.py (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • neurons/incentive_mechanism.ipynb
  • neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml
  • neurons/execution_layer/proof_handlers/factory.py
🔇 Additional comments (14)
neurons/execution_layer/circuit_metadata.py (1)

31-35: Nice ergonomic enum conversion with missing.

Case-insensitive string-to-enum conversion is clean and predictable. Good use of Enum.missing for UX.

neurons/_validator/models/poc_rpc_request.py (1)

4-4: Import source aligned with new centralized metadata module.

Switching CircuitType import to execution_layer.circuit_metadata is consistent with the refactor. No further issues spotted.

neurons/utils/pre_flight.py (6)

15-24: Consolidated constants import looks good.

Centralizing these constants reduces duplication and improves maintainability.


42-46: Docstring update is accurate and helpful.

Calling out teeonnx and conditional Docker checks improves operator clarity.


57-61: Neat pattern: conditional checks via None sentinel.

Using None to skip non-applicable checks keeps the loop simple and readable.


71-75: Graceful skip log is a nice touch.

Clear runtime logging when a check is skipped due to role aids troubleshooting.


118-132: Docker check: robust and consistent.

Covers both CalledProcessError and FileNotFoundError; concise and clear.


228-229: Early exit for SRS download on miner role is appropriate.

Skipping SRS downloads for miners avoids unnecessary I/O. Good.

neurons/protocol.py (1)

7-7: Import aligned with new metadata module.

Switching ProofSystem import to execution_layer.circuit_metadata matches the centralization across the codebase.

neurons/_validator/pow/proof_of_weights_handler.py (1)

3-4: Import source change LGTM

Importing CircuitType from execution_layer.circuit_metadata aligns with the refactor. No functional concerns here.

neurons/_validator/core/request_pipeline.py (1)

21-23: Import split LGTM

Decoupling CircuitType from Circuit via circuit_metadata is consistent with the new design. No behavioral changes introduced by this edit.

neurons/execution_layer/circuit.py (1)

67-71: Prefer TOML metadata with JSON fallback: good change

Preferring metadata.toml while retaining a JSON fallback improves flexibility and keeps backward compatibility. Looks good.

neurons/execution_layer/session_storage.py (2)

51-55: LGTM: EZKL subclass initialization is correct

Defers to the base paths and adds a targeted trace; no functional issues spotted.


72-101: Good fix: avoids the previous witness_path property assignment clash

Not calling super().__post_init__() here avoids assigning to the read-only witness_path property. The explicit property mapping to output_path is clear and resolves the previously reported AttributeError risk.

Comment on lines 42 to 48
model_dest = os.path.join(
session.session_storage.base_path, f"network_{session.model_id}.onnx"
)
if not os.path.exists(model_dest):
shutil.copy2(session.model.paths.compiled_model, model_dest)
bt.logging.trace(f"Copied model to {model_dest}")

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure source ONNX exists; fail fast with a clear message

Copying the compiled model assumes the source exists. Add an explicit check to surface helpful errors.

Apply this diff:

         model_dest = os.path.join(
             session.session_storage.base_path, f"network_{session.model_id}.onnx"
         )
         if not os.path.exists(model_dest):
-            shutil.copy2(session.model.paths.compiled_model, model_dest)
+            if not os.path.exists(session.model.paths.compiled_model):
+                raise FileNotFoundError(
+                    f"Compiled model not found: {session.model.paths.compiled_model}"
+                )
+            shutil.copy2(session.model.paths.compiled_model, model_dest)
             bt.logging.trace(f"Copied model to {model_dest}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
model_dest = os.path.join(
session.session_storage.base_path, f"network_{session.model_id}.onnx"
)
if not os.path.exists(model_dest):
shutil.copy2(session.model.paths.compiled_model, model_dest)
bt.logging.trace(f"Copied model to {model_dest}")
model_dest = os.path.join(
session.session_storage.base_path, f"network_{session.model_id}.onnx"
)
if not os.path.exists(model_dest):
if not os.path.exists(session.model.paths.compiled_model):
raise FileNotFoundError(
f"Compiled model not found: {session.model.paths.compiled_model}"
)
shutil.copy2(session.model.paths.compiled_model, model_dest)
bt.logging.trace(f"Copied model to {model_dest}")
🤖 Prompt for AI Agents
In neurons/execution_layer/proof_handlers/dcap_handler.py around lines 42 to 48,
the code copies session.model.paths.compiled_model to model_dest without
verifying the source exists; add an explicit existence check for
session.model.paths.compiled_model before calling shutil.copy2, and if the file
is missing log a clear error (including the source path and session/model
identifiers) and fail fast by raising a FileNotFoundError or otherwise aborting
the operation so the missing-source condition surfaces immediately.

Comment on lines +79 to +89
with open(session.session_storage.proof_path, "r", encoding="utf-8") as f:
try:
proof = json.load(f)
except json.JSONDecodeError:
bt.logging.error(
f"Failed to load proof from {session.session_storage.proof_path}"
)
raise

return json.dumps(proof), json.dumps(proof["instances"])

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

KeyError risk: proof['instances'] may be absent

Not all teeonnx proofs guarantee an "instances" key. Default gracefully to an empty list or an alternate field.

Apply this diff:

-            with open(session.session_storage.proof_path, "r", encoding="utf-8") as f:
-                try:
-                    proof = json.load(f)
-                except json.JSONDecodeError:
-                    bt.logging.error(
-                        f"Failed to load proof from {session.session_storage.proof_path}"
-                    )
-                    raise
-
-            return json.dumps(proof), json.dumps(proof["instances"])
+            with open(session.session_storage.proof_path, "r", encoding="utf-8") as f:
+                try:
+                    proof = json.load(f)
+                except json.JSONDecodeError:
+                    bt.logging.error(
+                        f"Failed to load proof from {session.session_storage.proof_path}"
+                    )
+                    raise
+
+            instances = proof.get("instances") or proof.get("public_signals") or []
+            return json.dumps(proof, separators=(",", ":")), json.dumps(instances, separators=(",", ":"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with open(session.session_storage.proof_path, "r", encoding="utf-8") as f:
try:
proof = json.load(f)
except json.JSONDecodeError:
bt.logging.error(
f"Failed to load proof from {session.session_storage.proof_path}"
)
raise
return json.dumps(proof), json.dumps(proof["instances"])
with open(session.session_storage.proof_path, "r", encoding="utf-8") as f:
try:
proof = json.load(f)
except json.JSONDecodeError:
bt.logging.error(
f"Failed to load proof from {session.session_storage.proof_path}"
)
raise
instances = proof.get("instances") or proof.get("public_signals") or []
return json.dumps(proof, separators=(",", ":")), json.dumps(instances, separators=(",", ":"))
🤖 Prompt for AI Agents
In neurons/execution_layer/proof_handlers/dcap_handler.py around lines 79 to 89,
the code assumes proof["instances"] exists which can raise KeyError for some
teeonnx proofs; change the return to safely access instances using
proof.get("instances", []) (or check for alternate field if applicable), and
ensure you JSON-dump that defaulted value (e.g.,
json.dumps(proof.get("instances", []))). Also consider logging a debug/info
message when the key is missing to aid diagnostics.

Comment on lines 98 to 101
def get_temp_folder() -> str:
if not os.path.exists(TEMP_FOLDER):
os.makedirs(TEMP_FOLDER, exist_ok=True)
os.makedirs(TEMP_FOLDER, mode=0o777, exist_ok=True)
return TEMP_FOLDER
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid world-writable temp dir; use 0o700 (or 0o1777 with sticky bit if multi-user sharing is required).

Creating TEMP_FOLDER with mode=0o777 is overly permissive and can enable unintended access or deletion by other users/processes. Prefer restrictive permissions by default.

Apply one of the following:

Option A (most secure, single-user):

-        os.makedirs(TEMP_FOLDER, mode=0o777, exist_ok=True)
+        os.makedirs(TEMP_FOLDER, mode=0o700, exist_ok=True)

Option B (multi-user shared, with sticky bit):

-        os.makedirs(TEMP_FOLDER, mode=0o777, exist_ok=True)
+        os.makedirs(TEMP_FOLDER, mode=0o1777, exist_ok=True)

Note: mode is subject to the process umask; set explicitly as needed for your deployment model.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_temp_folder() -> str:
if not os.path.exists(TEMP_FOLDER):
os.makedirs(TEMP_FOLDER, exist_ok=True)
os.makedirs(TEMP_FOLDER, mode=0o777, exist_ok=True)
return TEMP_FOLDER
def get_temp_folder() -> str:
if not os.path.exists(TEMP_FOLDER):
os.makedirs(TEMP_FOLDER, mode=0o700, exist_ok=True)
return TEMP_FOLDER
Suggested change
def get_temp_folder() -> str:
if not os.path.exists(TEMP_FOLDER):
os.makedirs(TEMP_FOLDER, exist_ok=True)
os.makedirs(TEMP_FOLDER, mode=0o777, exist_ok=True)
return TEMP_FOLDER
def get_temp_folder() -> str:
if not os.path.exists(TEMP_FOLDER):
os.makedirs(TEMP_FOLDER, mode=0o1777, exist_ok=True)
return TEMP_FOLDER
🤖 Prompt for AI Agents
In neurons/utils/system.py around lines 98 to 101, the TEMP_FOLDER is created
with mode 0o777 which is too permissive; change it to a restrictive default (use
0o700 for single-user use) or to a shared-but-safe mode (0o1777 with the sticky
bit) depending on your deployment, and ensure the permission is enforced despite
the process umask by either setting the umask appropriately before mkdir or
calling os.chmod(TEMP_FOLDER, desired_mode) after creation; keep exist_ok=True
and preserve existing behavior when the folder already exists.

Copy link

@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

♻️ Duplicate comments (6)
neurons/execution_layer/proof_handlers/dcap_handler.py (6)

42-47: Fail fast if compiled model is missing before copy.

Improve diagnostics when the source ONNX is absent rather than surfacing a generic FileNotFoundError from shutil.

         if not os.path.exists(model_dest):
-            shutil.copy2(session.model.paths.compiled_model, model_dest)
+            if not os.path.exists(session.model.paths.compiled_model):
+                raise FileNotFoundError(
+                    f"Compiled model not found: {session.model.paths.compiled_model}"
+                )
+            shutil.copy2(session.model.paths.compiled_model, model_dest)
             bt.logging.trace(f"Copied model to {model_dest}")

79-89: Avoid KeyError on missing proof['instances']; return deterministic JSON.

Instances may be absent or named differently (e.g., public_signals). Default gracefully and canonicalize JSON.

-            return json.dumps(proof), json.dumps(proof["instances"])
+            instances = proof.get("instances") or proof.get("public_signals") or []
+            return json.dumps(proof, separators=(",", ":")), json.dumps(
+                instances, separators=(",", ":")
+            )

165-196: Harden docker invocation: add --rm and a timeout.

Prevents container buildup and hangs.

             [
                 "docker",
                 "run",
+                "--rm",
                 "--device",
                 SGX_ENCLAVE_PATH,
                 "--device",
                 SGX_PROVISION_PATH,
                 "-v",
                 f"{session.session_storage.base_path}:{WORKSPACE_PATH}",
                 SGX_IMAGE,
                 "gen-output",
@@
             ],
             check=True,
             capture_output=True,
             text=True,
+            timeout=600,
         )

11-17: Fix incorrect import paths (missing 'neurons.' package prefix).

These imports will raise ModuleNotFoundError when used outside the local package context. Use absolute package paths.

-from execution_layer.proof_handlers.base_handler import ProofSystemHandler
-from execution_layer.generic_input import GenericInput
-from constants import LOCAL_TEEONNX_PATH
-from execution_layer.session_storage import DCAPSessionStorage
+from neurons.execution_layer.proof_handlers.base_handler import ProofSystemHandler
+from neurons.execution_layer.generic_input import GenericInput
+from neurons.constants import LOCAL_TEEONNX_PATH
+from neurons.execution_layer.session_storage import DCAPSessionStorage

 if TYPE_CHECKING:
-    from execution_layer.verified_model_session import VerifiedModelSession
+    from neurons.execution_layer.verified_model_session import VerifiedModelSession

36-41: Guard against None input data before writing input.json.

If session.inputs.data is None, you'll write "null" and break downstream. Fail fast with a clear error.

         if session.inputs is None:
             raise ValueError("Session inputs cannot be None when generating input file")
+        if session.inputs.data is None:
+            raise ValueError("Session inputs data cannot be None when generating input file")
         os.makedirs(os.path.dirname(session.session_storage.input_path), exist_ok=True)
         with open(session.session_storage.input_path, "w", encoding="utf-8") as f:
             json.dump(session.inputs.data, f)

112-115: Canonicalize validator input JSON before hashing; guard None.

Using default separators introduces spaces and unstable hashes; also, hashing None-serialized "null" is dangerous.

-        input_hash = hashlib.sha256(
-            json.dumps(validator_inputs.data, sort_keys=True).encode("utf-8")
-        ).hexdigest()[:64]
+        if validator_inputs.data is None:
+            raise ValueError("validator_inputs.data cannot be None for verification")
+        input_hash = hashlib.sha256(
+            json.dumps(
+                validator_inputs.data, sort_keys=True, separators=(",", ":")
+            ).encode("utf-8")
+        ).hexdigest()[:64]
🧹 Nitpick comments (2)
neurons/execution_layer/proof_handlers/dcap_handler.py (2)

49-49: Avoid logging full input payloads (possible PII leakage).

Reduce verbosity to metadata (e.g., count) instead of full content.

-        bt.logging.trace(f"Generated input.json with data: {session.inputs.data}")
+        bt.logging.trace(
+            f"Generated input.json (items={0 if session.inputs.data is None else len(session.inputs.data)})"
+        )

61-73: Add timeout to proof generation subprocess.

Prevents indefinite hangs when teeonnx stalls.

             result = subprocess.run(
                 [
                     LOCAL_TEEONNX_PATH,
                     "prove",
                     "--quote",
                     session.session_storage.quote_path,
                     "--proof",
                     session.session_storage.proof_path,
                 ],
                 check=True,
                 capture_output=True,
                 text=True,
+                timeout=300,
             )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f7b954 and 6394d6e.

📒 Files selected for processing (1)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
neurons/execution_layer/proof_handlers/dcap_handler.py (4)
neurons/execution_layer/proof_handlers/base_handler.py (1)
  • ProofSystemHandler (11-64)
neurons/execution_layer/generic_input.py (1)
  • GenericInput (7-26)
neurons/execution_layer/session_storage.py (2)
  • DCAPSessionStorage (68-101)
  • witness_path (100-101)
neurons/execution_layer/verified_model_session.py (5)
  • VerifiedModelSession (24-182)
  • gen_input_file (74-78)
  • gen_proof (80-102)
  • generate_witness (159-164)
  • verify_proof (129-145)
🔇 Additional comments (1)
neurons/execution_layer/proof_handlers/dcap_handler.py (1)

116-129: LGTM: files are safely closed via context managers.

Using with open(...) ensures file descriptors are closed even on exceptions, addressing prior “File is not always closed” warnings.

Copy link

@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: 1

♻️ Duplicate comments (1)
neurons/utils/pre_flight.py (1)

88-116: Harden teeonnx installer: catch FileNotFoundError, restrict perms, and ensure executability

  • Catch FileNotFoundError (wget missing).
  • Use 0o750 to avoid world-executable, as flagged by security scanners.
  • If the binary exists but is not executable, fix perms instead of early returning.

Apply this diff:

 def ensure_teeonnx_installed():
@@
-    try:
-        if os.path.exists(LOCAL_TEEONNX_PATH):
-            bt.logging.info("teeonnx is already installed")
-            return
+    try:
+        if os.path.exists(LOCAL_TEEONNX_PATH):
+            if os.access(LOCAL_TEEONNX_PATH, os.X_OK):
+                bt.logging.info("teeonnx is already installed and executable")
+                return
+            bt.logging.warning("teeonnx exists but is not executable; fixing permissions...")
+            os.chmod(LOCAL_TEEONNX_PATH, 0o750)
+            return
@@
-        # trunk-ignore(bandit/B605)
         subprocess.run(
             [
                 "wget",
                 "https://github.com/zkonduit/teeonnx-p/releases/download/v23/teeonnx-zk-cpu-linux",
                 "-O",
-                f"{LOCAL_TEEONNX_PATH}",
+                LOCAL_TEEONNX_PATH,
             ],
             check=True,
         )
-        os.chmod(LOCAL_TEEONNX_PATH, 0o755)
+        os.chmod(LOCAL_TEEONNX_PATH, 0o750)
         bt.logging.info("teeonnx installed successfully")
-    except subprocess.CalledProcessError as e:
+    except (subprocess.CalledProcessError, FileNotFoundError) as e:
         bt.logging.error(f"Failed to install teeonnx: {e}")
         raise RuntimeError(
             "teeonnx installation failed. Please install it manually."
         ) from e

Optional: remove the trunk-ignore since shell=True is no longer used here. Also consider replacing wget with a requests-based download and checksum verification to avoid relying on external tooling.

🧹 Nitpick comments (4)
neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml (1)

10-11: Optional: add integrity metadata for external file

If your metadata model supports it, consider including a checksum (e.g., sha256) for network.onnx to harden download verification during sync.

neurons/utils/pre_flight.py (3)

72-75: Skip-log wording when role is None

Minor: when role is None, the log reads “… for None”. Consider clarifying the message for readability.

Apply this diff to avoid printing “None”:

-        if check_function is None:
-            bt.logging.info(f" PreFlight | Skipping {check_name} check for {role}")
+        if check_function is None:
+            suffix = f" for {role}" if role is not None else ""
+            bt.logging.info(f" PreFlight | Skipping {check_name} check{suffix}")
             continue

80-83: Improve exception logging: avoid passing e as a second arg; use exception() or exc_info

Passing e as a second argument to logging is non-idiomatic and may format incorrectly. Use exception logging to capture stack trace once.

Apply this diff:

-        except Exception as e:
-            bt.logging.error(f"Failed {check_name.lower()}.", e)
-            bt.logging.debug(f" PreFlight | {check_name} error details: {str(e)}")
-            traceback.print_exc()
-            raise e
+        except Exception:
+            bt.logging.exception(f" PreFlight | {check_name} failed")
+            raise

262-275: Prefer TOML over JSON if both exist (align with new TOML-first workflow)

Given TOML is the new canonical format, consider checking metadata.toml before metadata.json.

Apply this diff:

-        for filename in ["metadata.json", "metadata.toml"]:
+        for filename in ["metadata.toml", "metadata.json"]:
             metadata_path = os.path.join(model_dir, filename)
             if os.path.isfile(metadata_path):
                 try:
                     circuit_metadata = CircuitMetadata.from_file(metadata_path)
                     break
                 except Exception as e:
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6394d6e and 43fb377.

📒 Files selected for processing (4)
  • neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml (1 hunks)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml (1 hunks)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
  • neurons/utils/pre_flight.py (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml
  • neurons/execution_layer/proof_handlers/dcap_handler.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
neurons/utils/pre_flight.py (2)
neurons/execution_layer/circuit_metadata.py (2)
  • CircuitMetadata (46-87)
  • from_file (64-87)
neurons/constants.py (1)
  • Roles (6-8)
🔇 Additional comments (4)
neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml (1)

5-6: Enums Verified: “DCAP” and “proof_of_computation” Exist

Both values are defined in the enums used by CircuitMetadata.from_file, so loading will succeed without a ValueError.

• ProofSystem enum (neurons/execution_layer/circuit_metadata.py):
– DCAP = "DCAP"
• CircuitType enum (neurons/execution_layer/circuit_metadata.py):
– PROOF_OF_COMPUTATION = "proof_of_computation"

neurons/utils/pre_flight.py (3)

57-61: Good addition: role-conditional Docker check and teeonnx preflight

The preflight now conditionally verifies Docker for miners and includes a teeonnx check. This aligns with DCAP requirements.


118-131: LGTM on Docker verification

Covers both failure modes (non-zero exit and missing binary) and doesn’t over-promise an installation.


282-289: LGTM: role-scoped external file filtering

The selection logic cleanly separates downloads for validator vs miner roles.

Copy link

@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: 0

🔭 Outside diff range comments (1)
neurons/utils/pre_flight.py (1)

293-301: Prevent path traversal when writing external files derived from metadata

Keys from circuit_metadata.external_files are used as relative paths. If a key includes “..” or absolute paths, the write could escape the model directory. You already have is_safe_path; use it here.

Apply this diff:

-            file_path = os.path.join(
-                cli_parser.config.full_path_models, model_hash, key
-            )
-            os.makedirs(os.path.dirname(file_path), exist_ok=True)
+            base_dir = os.path.join(cli_parser.config.full_path_models, model_hash)
+            file_path = os.path.normpath(os.path.join(base_dir, key))
+            if not is_safe_path(base_dir, file_path):
+                bt.logging.error(
+                    SYNC_LOG_PREFIX
+                    + f"Refusing to write outside model dir for key '{key}' in {model_hash}"
+                )
+                continue
+            os.makedirs(os.path.dirname(file_path), exist_ok=True)
@@
-            try:
+            try:
                 with requests.get(
                     url, timeout=FIVE_MINUTES * 2, stream=True
                 ) as response:
                     response.raise_for_status()
                     with open(file_path, "wb") as f:
                         for chunk in response.iter_content(chunk_size=8192):
                             f.write(chunk)

Optional hardening:

  • Download to a temp file (same directory) and os.replace to avoid partial files if interrupted.
  • Enforce an allowlist of expected keys per role beyond the existing filter to prevent unexpected file placement even within the model dir.

Also applies to: 303-317

♻️ Duplicate comments (1)
neurons/utils/pre_flight.py (1)

88-117: Tighten file permissions and broaden exception handling in teeonnx installer

  • 0o755 is flagged as overly permissive. Use 0o750 (or 0o700) to avoid world-executable/readable.
  • If wget is missing, subprocess.run raises FileNotFoundError; catch it to surface the intended RuntimeError.
  • Only chmod when needed; if the file exists and is executable, return early.

Apply this diff:

 def ensure_teeonnx_installed():
@@
-    try:
-        if os.path.exists(LOCAL_TEEONNX_PATH):
-            os.chmod(LOCAL_TEEONNX_PATH, 0o755)
-            bt.logging.info("teeonnx is already installed")
-            return
+    try:
+        if os.path.exists(LOCAL_TEEONNX_PATH):
+            if os.access(LOCAL_TEEONNX_PATH, os.X_OK):
+                bt.logging.info("teeonnx is already installed and executable")
+                return
+            bt.logging.warning("teeonnx exists but is not executable; fixing permissions...")
+            os.chmod(LOCAL_TEEONNX_PATH, 0o750)
+            return
@@
-        subprocess.run(
+        subprocess.run(
             [
                 "wget",
                 "https://github.com/zkonduit/teeonnx-p/releases/download/v23/teeonnx-zk-cpu-linux",
                 "-O",
-                f"{LOCAL_TEEONNX_PATH}",
+                LOCAL_TEEONNX_PATH,
             ],
             check=True,
         )
-        os.chmod(LOCAL_TEEONNX_PATH, 0o755)
+        os.chmod(LOCAL_TEEONNX_PATH, 0o750)
         bt.logging.info("teeonnx installed successfully")
-    except subprocess.CalledProcessError as e:
+    except (subprocess.CalledProcessError, FileNotFoundError) as e:
         bt.logging.error(f"Failed to install teeonnx: {e}")
         raise RuntimeError(
             "teeonnx installation failed. Please install it manually."
         ) from e

Optional hardening:

  • Verify checksum/signature of the downloaded binary before chmod.
  • Avoid external dependency on wget by streaming via requests and setting mode bits after an atomic rename.
🧹 Nitpick comments (4)
neurons/utils/pre_flight.py (4)

15-24: Import paths appear package-relative; verify they resolve in production and prefer explicit package-qualified imports

If this module is executed within the neurons package, absolute imports like from constants import ... and from execution_layer... may depend on sys.path side-effects. To avoid import ambiguity and improve maintainability, prefer package-qualified imports.

Apply this diff locally within the selected range:

-from constants import (
+from neurons.constants import (
     IGNORED_MODEL_HASHES,
     LOCAL_EZKL_PATH,
     LOCAL_TEEONNX_PATH,
     LOCAL_SNARKJS_PATH,
     LOCAL_SNARKJS_INSTALL_DIR,
     MINER_EXTERNAL_FILES,
     VALIDATOR_EXTERNAL_FILES,
 )
-from execution_layer.circuit_metadata import CircuitMetadata
+from neurons.execution_layer.circuit_metadata import CircuitMetadata

Additionally update the earlier import (outside the selected range) for consistency:

# elsewhere in this file (Line 7)
from neurons.constants import FIVE_MINUTES, Roles

42-46: Preflight gating additions read well and are safe to skip dynamically

Doc updates and conditional inclusion of Docker/teeonnx checks look good. Skipping None-valued checks keeps the loop simple. Minor nit: when role is None, the skip log prints “for None,” which is fine but you could omit the suffix for cleaner logs.

Also applies to: 57-61, 72-75


119-133: Nit: error message says “install/verify” but the function only verifies

The function doesn’t install Docker; reword the error to avoid confusion.

-    except (subprocess.CalledProcessError, FileNotFoundError) as e:
-        bt.logging.error(f"Failed to install/verify Docker: {e}")
-        raise RuntimeError(
-            "Docker installation failed. Please install it manually."
-        ) from e
+    except (subprocess.CalledProcessError, FileNotFoundError) as e:
+        bt.logging.error(f"Failed to verify Docker availability: {e}")
+        raise RuntimeError(
+            "Docker not available. Please install Docker and retry."
+        ) from e

229-231: Minor structure nit: check miner role before entering the loop

Breaking on the first iteration works but doing the role check before the loop avoids unnecessary loop setup and a branch per-iteration.

Example refactor (outside this range):

loop = asyncio.get_event_loop()
if role != Roles.MINER:
    for logrows in range(1, 26):
        ...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43fb377 and ef953c7.

📒 Files selected for processing (1)
  • neurons/utils/pre_flight.py (7 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
neurons/utils/pre_flight.py (2)
neurons/execution_layer/circuit_metadata.py (2)
  • CircuitMetadata (46-87)
  • from_file (64-87)
neurons/constants.py (1)
  • Roles (6-8)
🪛 GitHub Check: CodeQL
neurons/utils/pre_flight.py

[failure] 94-94: Overly permissive file permissions
Overly permissive mask in chmod sets file to world readable.

🪛 GitHub Check: Semgrep OSS
neurons/utils/pre_flight.py

[warning] 94-94: Semgrep Finding: python.lang.security.audit.insecure-file-permissions.insecure-file-permissions
These permissions 0o755 are widely permissive and grant access to more people than may be necessary. A good default is 0o644 which gives read and write access to yourself and read access to everyone else.

🪛 GitHub Check: Bandit
neurons/utils/pre_flight.py

[warning] 94-94:
Chmod setting a permissive mask 0o755 on file (LOCAL_TEEONNX_PATH).

🔇 Additional comments (1)
neurons/utils/pre_flight.py (1)

260-275: LGTM: TOML/JSON metadata resolution with graceful error handling

Falling back between metadata.json and metadata.toml and logging parse failures per-file is a good DX improvement.

Copy link

@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

🔭 Outside diff range comments (2)
neurons/utils/pre_flight.py (2)

290-303: Sanitize metadata-derived filenames to prevent path traversal; enforce base dir

Keys from external_files are used directly in os.path.join. A malicious or malformed key containing path separators (e.g., "../../…") could escape the intended directory.

Apply this diff:

-            file_path = os.path.join(
-                cli_parser.config.full_path_models, model_hash, key
-            )
-            os.makedirs(os.path.dirname(file_path), exist_ok=True)
+            base_dir = os.path.join(cli_parser.config.full_path_models, model_hash)
+            safe_key = os.path.basename(key)
+            file_path = os.path.join(base_dir, safe_key)
+            # Enforce staying within base_dir
+            if not is_safe_path(base_dir, file_path):
+                bt.logging.error(
+                    SYNC_LOG_PREFIX
+                    + f"Refusing to write outside model directory for key={key}"
+                )
+                continue
+            os.makedirs(base_dir, exist_ok=True)

310-323: Avoid corrupt partial downloads: write to a temp file and atomic-rename on success

If the process crashes mid-download, the next run will see the file present and skip re-downloading, leaving a corrupted file. Use a .part file and os.replace to commit atomically.

Apply this diff:

-            try:
-                with requests.get(
-                    url, timeout=FIVE_MINUTES * 2, stream=True
-                ) as response:
-                    response.raise_for_status()
-                    with open(file_path, "wb") as f:
-                        for chunk in response.iter_content(chunk_size=8192):
-                            f.write(chunk)
-            except requests.RequestException as e:
-                bt.logging.error(
-                    SYNC_LOG_PREFIX + f"Failed to download {url} to {file_path}: {e}"
-                )
-                continue
+            try:
+                tmp_path = f"{file_path}.part"
+                with requests.get(url, timeout=FIVE_MINUTES * 2, stream=True) as response:
+                    response.raise_for_status()
+                    with open(tmp_path, "wb") as f:
+                        for chunk in response.iter_content(chunk_size=8192):
+                            if chunk:
+                                f.write(chunk)
+                os.replace(tmp_path, file_path)
+            except requests.RequestException as e:
+                # Cleanup temp file on failure
+                try:
+                    if os.path.exists(tmp_path):
+                        os.remove(tmp_path)
+                except OSError:
+                    pass
+                bt.logging.error(
+                    SYNC_LOG_PREFIX + f"Failed to download {url} to {file_path}: {e}"
+                )
+                continue

Optional hardening:

  • Validate URL scheme is http(s) before requesting.
  • Verify checksum if provided in metadata.
♻️ Duplicate comments (6)
neurons/execution_layer/proof_handlers/dcap_handler.py (5)

50-56: Fail fast if the compiled model is missing; improve error message

Copying without checking the source will raise a less-informative error. Add an explicit existence check and raise a clear exception.

         model_dest = os.path.join(
             session.session_storage.base_path, f"network_{session.model.id}.onnx"
         )
         if not os.path.exists(model_dest):
-            shutil.copy2(session.model.paths.compiled_model, model_dest)
+            if not os.path.exists(session.model.paths.compiled_model):
+                raise FileNotFoundError(
+                    f"Compiled model not found: {session.model.paths.compiled_model}"
+                )
+            shutil.copy2(session.model.paths.compiled_model, model_dest)
             bt.logging.trace(f"Copied model to {model_dest}")

126-139: LGTM: files are safely closed via context managers

These with open(...): blocks address prior “file not always closed” scanner findings; files are closed even if hashing raises.


11-17: Fix import paths to the neurons package to prevent ModuleNotFoundError

These absolute imports miss the neurons. package prefix (and one import should come from neurons.constants). This will break module resolution outside this module’s directory.

Apply:

-from execution_layer.proof_handlers.base_handler import ProofSystemHandler
-from execution_layer.generic_input import GenericInput
-from constants import LOCAL_TEEONNX_PATH
-from execution_layer.session_storage import DCAPSessionStorage
+from neurons.execution_layer.proof_handlers.base_handler import ProofSystemHandler
+from neurons.execution_layer.generic_input import GenericInput
+from neurons.constants import LOCAL_TEEONNX_PATH
+from neurons.execution_layer.session_storage import DCAPSessionStorage

And under TYPE_CHECKING:

-if TYPE_CHECKING:
-    from execution_layer.verified_model_session import VerifiedModelSession
+if TYPE_CHECKING:
+    from neurons.execution_layer.verified_model_session import VerifiedModelSession

34-49: Guard against missing input data before building teeonnx payload

session.inputs can be set while session.inputs.data is None, which will raise when calling .get(...). Fail fast with a clear error and serialize deterministically.

     def gen_input_file(self, session: VerifiedModelSession):
         bt.logging.trace("Generating input file")
         if session.inputs is None:
             raise ValueError("Session inputs cannot be None when generating input file")
         os.makedirs(os.path.dirname(session.session_storage.input_path), exist_ok=True)
 
-        input_data = session.inputs.data
+        if session.inputs.data is None:
+            raise ValueError("Session inputs data is required to generate input file")
+        input_data = session.inputs.data
         teeonnx_format = {
             "data": input_data.get("input_ids", input_data.get("data", [])),
             "shapes": input_data.get("shapes", []),
             "variables": input_data.get("variables", []),
         }
 
-        with open(session.session_storage.input_path, "w", encoding="utf-8") as f:
-            json.dump(teeonnx_format, f)
+        with open(session.session_storage.input_path, "w", encoding="utf-8") as f:
+            json.dump(teeonnx_format, f, separators=(",", ":"))

87-96: Prevent KeyError when ‘instances’ is absent; canonicalize output JSON

Some proofs may not include an “instances” field. Default gracefully and use canonical separators for consistency.

-            return json.dumps(proof), json.dumps(proof["instances"])
+            instances = proof.get("instances") or proof.get("public_signals") or []
+            return json.dumps(proof, separators=(",", ":")), json.dumps(
+                instances, separators=(",", ":")
+            )
neurons/utils/pre_flight.py (1)

101-101: Semgrep warning on 0o700 is a false positive for executables; consider 0o500 or document the choice

0o700 is owner-only read/write/execute and is more restrictive than 0o644. For binaries, execute permission is required. If the binary doesn’t need to be owner-writable post-install, 0o500 is even tighter; if multiple users must execute it, prefer 0o750.

Optional tweak:

- os.chmod(LOCAL_TEEONNX_PATH, 0o700)
+ os.chmod(LOCAL_TEEONNX_PATH, 0o500)  # owner read/execute only

Alternatively, add an inline comment to justify the permission to appease scanners.

Also applies to: 116-116

🧹 Nitpick comments (4)
neurons/execution_layer/proof_handlers/dcap_handler.py (2)

140-160: Prefer exit code over parsing stdout to decide verification result; log both streams

Because check=True already throws on verification failure, you can return True on success and avoid brittle string matching. Also log both stdout and stderr for diagnostics.

             result = subprocess.run(
                 [
                     LOCAL_TEEONNX_PATH,
                     "verify",
                     session.session_storage.proof_path,
                     "--input-hash",
                     input_hash,
                     "--model-hash",
                     model_hash,
                     "--output-hash",
                     witness_hash,
                     "--mrenclave",
                     EXPECTED_MRENCLAVE,
                 ],
                 check=True,
                 capture_output=True,
                 text=True,
                 timeout=60,
             )
-            return "verified: true" in result.stdout
+            bt.logging.debug(
+                f"teeonnx verify stdout: {result.stdout.strip()} | stderr: {result.stderr.strip()}"
+            )
+            return True

167-169: Parameter return_content is ignored; align behavior or docstring

This method always returns the witness JSON regardless of return_content. Either honor the flag or adjust the docstring to reflect current behavior to avoid confusion.

Also applies to: 211-227

neurons/utils/pre_flight.py (2)

57-61: Gate teeonnx installation to when DCAP circuits are present

Installing teeonnx unconditionally increases surface and build time. Consider enabling this check only if the configured circuits include proof_system == DCAP.

Example update within preflight (conceptual):

-            "Checking teeonnx installation": ensure_teeonnx_installed,
+            "Checking teeonnx installation": (
+                ensure_teeonnx_installed if has_dcap_circuits() else None
+            ),

Helper (outside this file’s changed lines): scan deployment_layer/*/metadata.(json|toml) for proof_system == "DCAP".


106-117: Use requests + atomic write instead of external wget; broaden exceptions

Removing the wget dependency improves portability, reduces supply-chain surface, and lets you write atomically to avoid partially-written binaries if the download is interrupted.

Apply this diff:

-        # trunk-ignore(bandit/B605)
-        subprocess.run(
-            [
-                "wget",
-                "https://github.com/zkonduit/teeonnx-p/releases/download/v23/teeonnx-zk-cpu-linux",
-                "-O",
-                LOCAL_TEEONNX_PATH,
-            ],
-            check=True,
-        )
-        os.chmod(LOCAL_TEEONNX_PATH, 0o700)
-        bt.logging.info("teeonnx installed successfully")
-    except (subprocess.CalledProcessError, FileNotFoundError) as e:
+        tmp_path = f"{LOCAL_TEEONNX_PATH}.part"
+        with requests.get(
+            "https://github.com/zkonduit/teeonnx-p/releases/download/v23/teeonnx-zk-cpu-linux",
+            timeout=FIVE_MINUTES * 2,
+            stream=True,
+        ) as resp:
+            resp.raise_for_status()
+            with open(tmp_path, "wb") as f:
+                for chunk in resp.iter_content(chunk_size=8192):
+                    if chunk:
+                        f.write(chunk)
+        os.chmod(tmp_path, 0o700)
+        os.replace(tmp_path, LOCAL_TEEONNX_PATH)
+        bt.logging.info("teeonnx installed successfully")
+    except (requests.RequestException, OSError) as e:
         bt.logging.error(f"Failed to install teeonnx: {e}")
         raise RuntimeError(
             "teeonnx installation failed. Please install it manually."
         ) from e

Outside this range: add a cleanup guard if desired in case of exceptions before os.replace:

# ensure_teeonnx_installed(): before raising, attempt to remove tmp_path if it exists
try:
    if os.path.exists(tmp_path):
        os.remove(tmp_path)
except OSError:
    pass

Also applies to: 118-123

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ef953c7 and a8f48a0.

📒 Files selected for processing (4)
  • neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml (1 hunks)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml (1 hunks)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
  • neurons/utils/pre_flight.py (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • neurons/deployment_layer/model_c2493244e153e8dcb2bfa6e0ebb411b76069074041d95718c47a36f4ec637237/metadata.toml
  • neurons/deployment_layer/model_815d49886a610c17e34204b1c544c363bb06328a6cb126181bd0b6e77164cd4b/metadata.toml
🧰 Additional context used
🧬 Code Graph Analysis (2)
neurons/execution_layer/proof_handlers/dcap_handler.py (4)
neurons/execution_layer/proof_handlers/base_handler.py (1)
  • ProofSystemHandler (11-64)
neurons/execution_layer/generic_input.py (1)
  • GenericInput (7-26)
neurons/execution_layer/session_storage.py (2)
  • DCAPSessionStorage (68-101)
  • witness_path (100-101)
neurons/execution_layer/verified_model_session.py (5)
  • VerifiedModelSession (24-182)
  • gen_input_file (74-78)
  • gen_proof (80-102)
  • generate_witness (159-164)
  • verify_proof (129-145)
neurons/utils/pre_flight.py (2)
neurons/execution_layer/circuit_metadata.py (2)
  • CircuitMetadata (46-87)
  • from_file (64-87)
neurons/constants.py (1)
  • Roles (6-8)
🪛 GitHub Check: Semgrep OSS
neurons/utils/pre_flight.py

[warning] 101-101: Semgrep Finding: python.lang.security.audit.insecure-file-permissions.insecure-file-permissions
These permissions 0o700 are widely permissive and grant access to more people than may be necessary. A good default is 0o644 which gives read and write access to yourself and read access to everyone else.


[warning] 116-116: Semgrep Finding: python.lang.security.audit.insecure-file-permissions.insecure-file-permissions
These permissions 0o700 are widely permissive and grant access to more people than may be necessary. A good default is 0o644 which gives read and write access to yourself and read access to everyone else.

🔇 Additional comments (2)
neurons/execution_layer/proof_handlers/dcap_handler.py (1)

120-125: LGTM: canonical, stable hashing of validator inputs

Using sort_keys with compact separators and encoding to bytes correctly stabilizes the input hash computation.

neurons/utils/pre_flight.py (1)

72-74: LGTM: clean conditional skipping pattern

Skipping optional checks by storing None in the map and short-circuiting here is clear and maintainable.

Comment on lines +92 to +103
try:
if os.path.exists(LOCAL_TEEONNX_PATH):
if os.access(LOCAL_TEEONNX_PATH, os.X_OK):
bt.logging.info("teeonnx is already installed and executable")
return
else:
bt.logging.warning(
"teeonnx exists but is not executable; fixing permissions..."
)
os.chmod(LOCAL_TEEONNX_PATH, 0o700)
return

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t trust an existing teeonnx file solely based on executability; verify it runs

If a previous download created a truncated/non-binary file (e.g., network error) without the exec bit, this branch will chmod it and return, leaving a corrupt binary in place.

Apply this diff to verify the binary executes and re-install if it doesn’t:

-    try:
-        if os.path.exists(LOCAL_TEEONNX_PATH):
-            if os.access(LOCAL_TEEONNX_PATH, os.X_OK):
-                bt.logging.info("teeonnx is already installed and executable")
-                return
-            else:
-                bt.logging.warning(
-                    "teeonnx exists but is not executable; fixing permissions..."
-                )
-                os.chmod(LOCAL_TEEONNX_PATH, 0o700)
-                return
+    try:
+        if os.path.exists(LOCAL_TEEONNX_PATH):
+            if not os.access(LOCAL_TEEONNX_PATH, os.X_OK):
+                bt.logging.warning(
+                    "teeonnx exists but is not executable; fixing permissions..."
+                )
+                os.chmod(LOCAL_TEEONNX_PATH, 0o700)
+            # Verify the binary actually runs; if it fails, fall through and re-install
+            try:
+                subprocess.run(
+                    [LOCAL_TEEONNX_PATH, "--help"],
+                    check=True,
+                    capture_output=True,
+                    text=True,
+                )
+                bt.logging.info("teeonnx is already installed and executable")
+                return
+            except (subprocess.CalledProcessError, FileNotFoundError, OSError):
+                bt.logging.warning(
+                    "Existing teeonnx binary failed to execute; re-installing..."
+                )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
if os.path.exists(LOCAL_TEEONNX_PATH):
if os.access(LOCAL_TEEONNX_PATH, os.X_OK):
bt.logging.info("teeonnx is already installed and executable")
return
else:
bt.logging.warning(
"teeonnx exists but is not executable; fixing permissions..."
)
os.chmod(LOCAL_TEEONNX_PATH, 0o700)
return
try:
if os.path.exists(LOCAL_TEEONNX_PATH):
if not os.access(LOCAL_TEEONNX_PATH, os.X_OK):
bt.logging.warning(
"teeonnx exists but is not executable; fixing permissions..."
)
os.chmod(LOCAL_TEEONNX_PATH, 0o700)
# Verify the binary actually runs; if it fails, fall through and re-install
try:
subprocess.run(
[LOCAL_TEEONNX_PATH, "--help"],
check=True,
capture_output=True,
text=True,
)
bt.logging.info("teeonnx is already installed and executable")
return
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
bt.logging.warning(
"Existing teeonnx binary failed to execute; re-installing..."
)
# …rest of installation logic continues here…
🧰 Tools
🪛 GitHub Check: Semgrep OSS

[warning] 101-101: Semgrep Finding: python.lang.security.audit.insecure-file-permissions.insecure-file-permissions
These permissions 0o700 are widely permissive and grant access to more people than may be necessary. A good default is 0o644 which gives read and write access to yourself and read access to everyone else.

Copy link

@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: 0

♻️ Duplicate comments (4)
neurons/execution_layer/proof_handlers/dcap_handler.py (4)

68-71: Fail fast if compiled model is missing before copying

Copying without verifying the source path leads to a confusing FileNotFoundError later.

Apply this diff:

-        if not os.path.exists(model_dest):
-            shutil.copy2(session.model.paths.compiled_model, model_dest)
-            bt.logging.trace(f"Copied model to {model_dest}")
+        if not os.path.exists(model_dest):
+            if not os.path.exists(session.model.paths.compiled_model):
+                raise FileNotFoundError(
+                    f"Compiled model not found: {session.model.paths.compiled_model}"
+                )
+            shutil.copy2(session.model.paths.compiled_model, model_dest)
+            bt.logging.trace(f"Copied model to {model_dest}")

11-15: Fix import paths to use the package namespace (neurons.…)

These imports will raise ModuleNotFoundError when used outside the package root. Use fully-qualified package paths.

Apply this diff:

-from execution_layer.proof_handlers.base_handler import ProofSystemHandler
-from execution_layer.generic_input import GenericInput
-from constants import LOCAL_TEEONNX_PATH
-from execution_layer.session_storage import DCAPSessionStorage
+from neurons.execution_layer.proof_handlers.base_handler import ProofSystemHandler
+from neurons.execution_layer.generic_input import GenericInput
+from constants import LOCAL_TEEONNX_PATH
+from neurons.execution_layer.session_storage import DCAPSessionStorage

And for TYPE_CHECKING:

-from execution_layer.verified_model_session import VerifiedModelSession
+from neurons.execution_layer.verified_model_session import VerifiedModelSession

Note: If constants.py also lives under neurons/, adjust accordingly to:
from neurons.constants import LOCAL_TEEONNX_PATH.

Also applies to: 16-17


51-56: Guard against missing session.inputs.data to avoid AttributeError

Accessing .get on None will crash when inputs.data is None.

Apply this diff:

-        if session.inputs is None:
-            raise ValueError("Session inputs cannot be None when generating input file")
+        if session.inputs is None or session.inputs.data is None:
+            raise ValueError("Session inputs are required to generate input file")

102-112: Avoid KeyError when accessing proof["instances"] and canonicalize JSON

Some proofs may not include “instances”. Default gracefully and use compact separators.

Apply this diff:

-            return json.dumps(proof), json.dumps(proof["instances"])
+            instances = proof.get("instances") or proof.get("public_signals") or []
+            return json.dumps(proof, separators=(",", ":")), json.dumps(instances, separators=(",", ":"))
🧹 Nitpick comments (2)
neurons/execution_layer/proof_handlers/dcap_handler.py (2)

84-97: Add a timeout to teeonnx prove to prevent indefinite hangs

The verify step has a timeout; align prove with a reasonable timeout.

Apply this diff:

             result = subprocess.run(
                 [
                     LOCAL_TEEONNX_PATH,
                     "prove",
                     "--quote",
                     session.session_storage.quote_path,
                     "--proof",
                     session.session_storage.proof_path,
                 ],
                 check=True,
                 capture_output=True,
                 text=True,
+                timeout=600,
             )

182-185: Nit: return_content parameter is unused

The method always returns parsed JSON regardless of the flag. Either document this behavior or wire the flag to skip file/stdout parsing when False.

Would you like me to update the docstrings and interface comments to clarify this?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a8f48a0 and 24296db.

📒 Files selected for processing (1)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
neurons/execution_layer/proof_handlers/dcap_handler.py (4)
neurons/execution_layer/proof_handlers/base_handler.py (1)
  • ProofSystemHandler (11-64)
neurons/execution_layer/generic_input.py (1)
  • GenericInput (7-26)
neurons/execution_layer/session_storage.py (2)
  • DCAPSessionStorage (68-101)
  • witness_path (100-101)
neurons/execution_layer/verified_model_session.py (5)
  • VerifiedModelSession (24-182)
  • gen_input_file (74-78)
  • gen_proof (80-102)
  • generate_witness (159-164)
  • verify_proof (129-145)
🔇 Additional comments (4)
neurons/execution_layer/proof_handlers/dcap_handler.py (4)

135-139: LGTM: canonical, deterministic input hashing

Using sorted keys and compact separators stabilizes the hash across environments.


28-41: LGTM: conditional sudo and SGX device access checks are sensible

Nice small helpers to decide when sudo is needed; avoids unnecessary privilege escalation.


190-231: LGTM: hardened Docker invocation

Good use of --rm, device mounts, workspace bind, and a timeout with explicit error handling.


235-251: LGTM: robust witness JSON retrieval with fallback to stdout

Solid error handling path and consistent return type.

Copy link

@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: 1

♻️ Duplicate comments (5)
neurons/execution_layer/proof_handlers/dcap_handler.py (5)

190-193: Make sudo usage configurable; retain SGX-based auto-detection

Some environments require sudo for docker regardless of SGX device access. Allow override via env var while keeping the current heuristic.

-        docker_cmd = ["docker", "run", "--rm"]
-        if _needs_sudo_for_sgx():
-            docker_cmd = ["sudo"] + docker_cmd
+        docker_cmd = ["docker", "run", "--rm"]
+        use_sudo_env = os.getenv("TEEONNX_DOCKER_USE_SUDO")
+        if (use_sudo_env == "1") or (use_sudo_env is None and _needs_sudo_for_sgx()):
+            docker_cmd = ["sudo"] + docker_cmd

11-18: Fix import paths: missing 'neurons.' package prefix (prevents module resolution)

These imports will fail outside the local package context. Use the fully qualified package path, including constants and TYPE_CHECKING import.

Apply this diff:

-from execution_layer.proof_handlers.base_handler import ProofSystemHandler
-from execution_layer.generic_input import GenericInput
-from constants import LOCAL_TEEONNX_PATH, ONE_HOUR
-from execution_layer.session_storage import DCAPSessionStorage
+from neurons.execution_layer.proof_handlers.base_handler import ProofSystemHandler
+from neurons.execution_layer.generic_input import GenericInput
+from neurons.constants import LOCAL_TEEONNX_PATH, ONE_HOUR
+from neurons.execution_layer.session_storage import DCAPSessionStorage
@@
-if TYPE_CHECKING:
-    from execution_layer.verified_model_session import VerifiedModelSession
+if TYPE_CHECKING:
+    from neurons.execution_layer.verified_model_session import VerifiedModelSession

49-60: Guard against missing or invalid input data

session.inputs.data can be None or non-dict, which will raise on .get(). Fail fast with a clear message.

-        if session.inputs is None:
-            raise ValueError("Session inputs cannot be None when generating input file")
+        if session.inputs is None or session.inputs.data is None:
+            raise ValueError("Session inputs are required to generate input file")
+        if not isinstance(session.inputs.data, dict):
+            raise TypeError("Session inputs.data must be a dict[str, object]")
 
-        input_data = session.inputs.data
+        input_data = session.inputs.data

65-71: Ensure compiled model exists before copying; fail fast with helpful error

Copying without verifying the source path can hide the root cause. Check and raise FileNotFoundError to surface configuration issues early.

         if not os.path.exists(model_dest):
-            shutil.copy2(session.model.paths.compiled_model, model_dest)
+            if not os.path.exists(session.model.paths.compiled_model):
+                raise FileNotFoundError(
+                    f"Compiled model not found: {session.model.paths.compiled_model}"
+                )
+            shutil.copy2(session.model.paths.compiled_model, model_dest)
             bt.logging.trace(f"Copied model to {model_dest}")

102-112: Avoid KeyError for missing 'instances'; also canonicalize JSON output

Not all proofs include "instances". Default gracefully and return canonical JSON for determinism.

-            return json.dumps(proof), json.dumps(proof["instances"])
+            instances = proof.get("instances") or proof.get("public_signals") or []
+            return json.dumps(proof, separators=(",", ":")), json.dumps(
+                instances, separators=(",", ":")
+            )
🧹 Nitpick comments (2)
neurons/execution_layer/proof_handlers/dcap_handler.py (2)

175-175: Make verification result parsing more robust (prefer JSON, fallback to substring)

Relying on a substring is brittle. If teeonnx returns JSON, parse it; otherwise, fallback. This keeps behavior backward compatible.

-            return "verified: true" in result.stdout
+            # Prefer JSON output if available; fallback to substring for legacy CLI
+            try:
+                out = json.loads(result.stdout)
+                return bool(out.get("verified", False))
+            except Exception:
+                return "verified: true" in (result.stdout or "").lower()

If the CLI definitely does not return JSON, keep the substring check. Otherwise this improves reliability.


223-231: Include stderr and return code in docker failure logs

This greatly improves diagnosability for SGX/Docker issues.

-        except subprocess.CalledProcessError as e:
-            bt.logging.error(f"Docker command failed: {e}")
-            raise
+        except subprocess.CalledProcessError as e:
+            bt.logging.error(
+                f"Docker command failed (code={e.returncode}): {e}\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}"
+            )
+            raise
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 24296db and 06d9421.

📒 Files selected for processing (1)
  • neurons/execution_layer/proof_handlers/dcap_handler.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
neurons/execution_layer/proof_handlers/dcap_handler.py (4)
neurons/execution_layer/proof_handlers/base_handler.py (1)
  • ProofSystemHandler (11-64)
neurons/execution_layer/generic_input.py (1)
  • GenericInput (7-26)
neurons/execution_layer/session_storage.py (2)
  • DCAPSessionStorage (68-101)
  • witness_path (100-101)
neurons/execution_layer/verified_model_session.py (5)
  • VerifiedModelSession (24-182)
  • gen_input_file (74-78)
  • gen_proof (80-102)
  • generate_witness (159-164)
  • verify_proof (129-145)
🔇 Additional comments (1)
neurons/execution_layer/proof_handlers/dcap_handler.py (1)

135-139: LGTM: Stable, deterministic input hashing

Good use of sort_keys and compact separators to stabilize the hash across processes.

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.

2 participants