Skip to content

Conversation

@mpaya5
Copy link
Contributor

@mpaya5 mpaya5 commented Oct 2, 2025

Fixes https://linear.app/genlayer-labs/issue/DXP-692/configure-github-actions-and-workflows-part2
Depends on https://linear.app/genlayer-labs/issue/DXP-684/separate-and-regroup-integration-tests / #1331

Summary

This PR introduces a comprehensive mocking system for web requests in integration tests, extending the existing LLM mocking infrastructure to include HTTP requests. Both mocking systems are now controlled by a single environment variable TEST_WITH_MOCKS.

Changes

🔧 Core Implementation

  • Added web request mocking in web.lua: Intercepts web requests when mocking is enabled, returning predefined responses from JSON files
  • Extended validator system: Modified backend/validators/__init__.py to pass mock_web_responses to validators
  • Updated test fixtures: Enhanced conftest.py to load and inject mock web responses alongside LLM mocks

📁 Mock Response Infrastructure

  • Created /tests/integration/fixtures/mock_responses/web/ directory structure
  • Added example_responses.json with mock responses for common test scenarios
  • Supports both render (webpage content) and request (API calls) mock types

🎯 Unified Configuration

  • Single control variable: TEST_WITH_MOCKS now controls both LLM and web request mocking
  • Simplified workflow logic: GitHub Actions sets one variable instead of multiple
  • Consistent behavior: Both systems always work in sync

🧪 Test Coverage

  • Added test_web_mocking.py: Validates the mocking infrastructure
  • Added test_web_mocking_contract.py: Tests contracts that make web requests with mocks
  • Updated existing tests to use the unified mocking approach

Behavior

Regular PRs (not approved)

TEST_WITH_MOCKS=true
  • ✅ LLM calls return mock responses
  • ✅ Web requests return mock responses from JSON files
  • ✅ Fast execution, no external dependencies
  • ✅ Zero API costs

Approved PRs

TEST_WITH_MOCKS=false
  • ❌ Real LLM API calls (OpenAI, etc.)
  • ❌ Real HTTP/HTTPS requests
  • ✅ Full integration validation

Benefits

  1. Cost Reduction: No API charges for regular PR tests
  2. Speed: Tests run faster without network dependencies
  3. Reliability: No failures due to external service issues
  4. Consistency: Deterministic test results with mock data
  5. Simplicity: Single environment variable to manage

Testing

Run integration tests with mocking:

TEST_WITH_MOCKS=true pytest tests/integration/

Run integration tests without mocking:

TEST_WITH_MOCKS=false pytest tests/integration/

Files Modified

  • .env.example - Added TEST_WITH_MOCKS variable
  • backend/validators/web.lua - Web request mocking logic
  • backend/validators/__init__.py - Mock response passing
  • tests/integration/icontracts/conftest.py - Mock loading functions
  • .github/workflows/backend_integration_tests_pr.yml - Workflow configuration
  • tests/unit/consensus/test_helpers.py - Updated to use new variable name

Related Issues

Addresses the need for comprehensive mocking in CI/CD pipelines to reduce costs and improve test reliability.

Summary by CodeRabbit

  • New Features

    • Validators now reload dynamically when changed, enabling near-real-time updates without full restarts.
    • Services published as images for more consistent deployments.
  • Tests

    • Added web-request mocking with example fixtures and multiple integration tests.
    • Unified mocking toggle to TEST_WITH_MOCKS and new test helpers/fixtures for loading defaults.
  • Chores

    • CI workflow updated to manage TEST_WITH_MOCKS based on PR approval.

kstroobants and others added 11 commits October 24, 2025 14:31
Changed sim_getValidators to sim_getAllValidators to match the actual
RPC method implementation. The method sim_getValidators does not exist
and was causing CI test failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
1. Added mock_web_responses property to JSON schema for all plugin types
   (openai-compatible, ollama, google, anthropic, custom) to allow web
   request mocking in integration tests

2. Fixed RPC method names in tests: sim_getValidators → sim_getAllValidators

3. Fixed mock URL key to match actual test URL:
   this-domain-definitely-does-not-exist-12345.com

These fixes resolve CI test failures where:
- Schema validation was rejecting mock_web_responses parameter
- Tests were calling non-existent RPC method
- Mock URL key didn't match the URL being tested

Note: Further investigation needed into why web.lua mocking isn't
intercepting requests even when host_data contains mock_web_responses.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@kstroobants kstroobants marked this pull request as ready for review October 29, 2025 08:13
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
backend/protocol_rpc/redis_subscriber.py (1)

145-153: Reconnection can leak Redis resources; close pubsub/client before restart.

On exceptions in the listener loop you call start() without closing existing connections.

@@
         except Exception as e:
             logger.error(f"Error in event listener: {e}")
             if self.is_running:
-                # Clean up before reconnection
-                self.is_running = False
-                self.subscription_task = None
-                # Attempt to reconnect after delay
-                await asyncio.sleep(5)
-                await self.start()
+                # Clean up before reconnection
+                self.is_running = False
+                self.subscription_task = None
+                try:
+                    if self.pubsub:
+                        await self.pubsub.unsubscribe()
+                        await self.pubsub.close()
+                        self.pubsub = None
+                    if self.redis_client:
+                        await self.redis_client.close()
+                        self.redis_client = None
+                except Exception:
+                    logger.warning("Suppressing cleanup error during reconnect")
+                # Attempt to reconnect after delay
+                await asyncio.sleep(5)
+                await self.start()
🧹 Nitpick comments (25)
.env.example (1)

167-167: Add documentation comment for TEST_WITH_MOCKS variable.

The variable definition lacks an explanatory comment describing its purpose and impact. Per coding guidelines, environment variables should be documented in .env.example.

Apply this diff to add a descriptive comment:

  # Tests parameters
+ # When true, enables mocking for both LLM providers and web requests during integration tests
  TEST_WITH_MOCKS="false"

This clarifies that TEST_WITH_MOCKS unifies control over both LLM and web request mocking, helping developers understand the scope and behavior of the flag when they enable it.

backend/database_handler/migration/versions/96840ab9133a_add_blocked_at_and_worker_id_to_.py (1)

24-30: Consider formatting both add_column calls consistently.

The first add_column call (lines 24-27) places each argument on a separate line, while the second call (lines 28-30) places the table name and Column definition on the same line. For better readability and consistency, both calls should follow the same formatting pattern.

Apply this diff to align the formatting:

 op.add_column(
-    "transactions", sa.Column("worker_id", sa.String(length=255), nullable=True)
+    "transactions",
+    sa.Column("worker_id", sa.String(length=255), nullable=True),
 )
tests/unit/consensus/test_base.py (1)

562-562: Document the rationale for custom interval value.

This is the only test using a custom interval=0.01 parameter. While a faster polling interval (10ms vs 100ms default) is safe with mocks and can speed up tests, consider adding a brief comment explaining why this particular test benefits from more aggressive polling compared to others.

         appeal(transaction, transactions_processor)
 
+        # Use faster interval for rapid PENDING->ACTIVATED transition
         current_status = assert_transaction_status_match(
             transactions_processor,
             transaction,
             [TransactionStatus.PENDING.value, TransactionStatus.ACTIVATED.value],
             interval=0.01,
         )
tests/unit/consensus/test_helpers.py (1)

35-36: Consider renaming USE_MOCK_LLMS for clarity.

The variable USE_MOCK_LLMS is now controlled by TEST_WITH_MOCKS, which covers both LLM and web request mocking. Consider renaming this variable to USE_MOCKS or USE_TEST_MOCKS to better reflect its expanded scope.

 # Configuration for LLM mocking
-USE_MOCK_LLMS = os.getenv("TEST_WITH_MOCKS", "true").lower() == "true"
+USE_MOCKS = os.getenv("TEST_WITH_MOCKS", "true").lower() == "true"

Note: This would require updating all references throughout the file (lines 117, 124, 138, 252, 260, 617, 904).

backend/protocol_rpc/message_handler/worker_handler.py (1)

115-122: Use get_running_loop; fall back to asyncio.run when no loop

get_event_loop can be misleading in 3.12. Prefer get_running_loop + fallback.

Apply:

-        try:
-            loop = asyncio.get_event_loop()
-            if loop.is_running():
-                # Schedule the async send operation
-                asyncio.create_task(self._send_event_to_server(log_event))
-            else:
-                # If no loop is running, try to run it synchronously
-                asyncio.run(self._send_event_to_server(log_event))
+        try:
+            loop = asyncio.get_running_loop()
+            loop.create_task(self._send_event_to_server(log_event))
         except RuntimeError:
             # No event loop available, log the event locally only
-            logger.debug(
-                f"No event loop available for worker {self.worker_id}, "
-                f"event not sent to server: {log_event.name}"
-            )
+            asyncio.run(self._send_event_to_server(log_event))
tests/integration/fixtures/conftest.py (1)

20-53: Harmonize env parsing and add type hints; prefer warnings over print

Make the toggle robust to common truthy values, annotate return types, and avoid print in tests.

Apply:

+from typing import Optional
+import warnings
@@
-def mock_llms():
+def mock_llms() -> bool:
     """Check if LLM mocking is enabled"""
-    env_var = os.getenv("TEST_WITH_MOCKS", "false")  # default no mocking
-    if env_var == "true":
-        return True
-    return False
+    env = os.getenv("TEST_WITH_MOCKS", "false").strip().lower()
+    return env in {"1", "true", "yes", "on"}
@@
-def mock_web_requests():
+def mock_web_requests() -> bool:
@@
-def load_mock_web_responses():
+def load_mock_web_responses() -> dict | None:
@@
-    mock_file = os.path.join(
-        os.path.dirname(__file__),
-        "..",
-        "fixtures",
-        "mock_responses",
-        "web",
-        "example_responses.json",
-    )
+    mock_file = Path(__file__).parent / "mock_responses" / "web" / "example_responses.json"
@@
-        with open(mock_file, "r") as f:
-            return json.loads(f.read())
+        return json.loads(Path(mock_file).read_text())
     except (FileNotFoundError, json.JSONDecodeError) as e:
-        print(f"Warning: Could not load mock web responses: {e}")
+        warnings.warn(f"Could not load mock web responses: {e}")
         return {}
tests/integration/contracts/conftest.py (1)

14-14: Consider if wildcard import is necessary.

Pytest automatically discovers fixtures from parent conftest.py files in the directory hierarchy. The from conftest import * may be redundant unless you're importing pytest hooks, non-fixture symbols, or session-scoped configuration that needs explicit inheritance.

If the parent conftest only contains fixtures, you can remove line 14 and rely on pytest's automatic fixture discovery. If there are specific symbols (like pytest hooks or configuration) that need to be imported, consider importing them explicitly:

from conftest import pytest_configure, mock_llms, mock_web_requests

This would resolve the Ruff F403 warning and make dependencies explicit.

backend/database_handler/validators_registry.py (1)

47-48: Optional optimization: Consider targeted cache invalidation for read-only RPC endpoints.

Your concern is valid. The RPC endpoints (count_validators, get_all_validators, get_validator) are network-exposed and expire_all() forces full session invalidation on every call. However, this appears intentional for consistency in a multi-service environment where validators are modified externally (evidenced by the Redis event system and reader/writer locks in ValidatorManager).

The expire_all() usage is justified because:

  • ValidatorManager's do_write() pattern explicitly re-reads validators after writes
  • Validators can be modified by other services via the Redis event system
  • Fresh data is required for correctness, not just performance

Consider whether:

  1. The performance impact justifies optimization (depends on actual call frequency)
  2. session.refresh() on specific validator objects is viable after internal write operations (create_validator at line 74)
  3. Stateless/cacheless read endpoints could bypass invalidation for external RPC calls
backend/protocol_rpc/redis_subscriber.py (2)

23-28: Annotate CHANNELS as ClassVar and prefer immutable tuple.

Prevents RUF012 and accidental per-instance mutation.

-from typing import Optional, Callable, Dict, Any
+from typing import Optional, Callable, Dict, Any, Awaitable, ClassVar
@@
-    CHANNELS = [
-        "consensus:events",
-        "transaction:events",
-        "general:events",
-        "validator:events",
-    ]
+    CHANNELS: ClassVar[tuple[str, ...]] = (
+        "consensus:events",
+        "transaction:events",
+        "general:events",
+        "validator:events",
+    )
@@
-        self.event_handlers: Dict[str, Callable] = {}
+        self.event_handlers: Dict[str, Callable[[Dict[str, Any]], Awaitable[None]]] = {}

225-233: Add broadcast routing for validator events (optional).

Without a map entry, validator events go to "broadcast". Map explicitly if UI listens on a specific channel.

                 channel_map = {
                     "consensus:events": "consensus",
                     "transaction:events": "transactions",
                     "general:events": "general",
+                    "validator:events": "validators",
                 }
tests/integration/icontracts/conftest.py (1)

8-11: Avoid duplicating sys.path hacks here (keep centralized).

If top-level conftest already adds tests/integration/fixtures to sys.path, this is redundant. Prefer a single place to manage it, or switch to pytest_plugins = [...] to share fixture modules.

-# Add fixtures to path
-fixtures_path = Path(__file__).parent.parent / "fixtures"
-if str(fixtures_path) not in sys.path:
-    sys.path.insert(0, str(fixtures_path))
+# Path augmentation handled in the top-level conftest.
+# If specific fixture modules are needed here, use:
+# pytest_plugins = ("fixtures.common", "fixtures.web_mocks",)

Please confirm top-level conftest sets the path. If not, I can propose a pytest_plugins list with the actual fixture module names.

backend/validators/web.lua (2)

27-37: Enable mocks before URL validation.

web.check_url(payload.url) runs before the mock lookup; this prevents mocking non-HTTP/placeholder URLs in tests. Check mocks first, then validate only when falling through to real requests.

-function Render(ctx, payload)
+function Render(ctx, payload)
     ---@cast payload WebRenderPayload
-    web.check_url(payload.url)
-
-    -- Return mock response if web mocking is enabled
-    if should_mock_web_requests(ctx) then
+    -- Return mock response if web mocking is enabled
+    if should_mock_web_requests(ctx) then
         local mock_response = get_mock_web_response_from_table(
             ctx.host_data.mock_web_responses.render,
             payload.url
         )
         if mock_response then
@@
-        end
-    end
+        end
+    end
+
+    web.check_url(payload.url)

And similarly in Request():

-    web.check_url(payload.url)
-    -- Return mock response if web mocking is enabled
+    -- Return mock response if web mocking is enabled
     if should_mock_web_requests(ctx) then
@@
-    end
+    end
+    web.check_url(payload.url)

6-16: Pattern match order is nondeterministic (pairs).

pairs(table) iteration order is undefined; if multiple patterns match a URL, the chosen mock may vary between runs. Consider an ordered list or priority rules.

Example approach:

  • Accept an array { {pattern="...", response=...}, ... } and iterate in order, or
  • Sort keys by length descending before matching to prefer specific patterns.
backend/validators/__init__.py (1)

339-375: Improve error logging and narrow exception scope.

Use logger.exception to keep tracebacks; consider narrowing the catch where feasible.

-        except Exception as e:
-            logger.error(f"Failed to publish validator change event: {e}")
+        except Exception:
+            logger.exception("Failed to publish validator change event")

Optional: keep a small, long-lived Redis publisher instead of creating a client per event if events are frequent.

backend/protocol_rpc/app_lifespan.py (1)

320-331: Minor: drop unused arg and remove f-prefix.

Silences ARG001/F541.

-        async def handle_validator_change(event_data):
+        async def handle_validator_change(_event_data):
             """Reload validators when they change."""
-            logger.info(f"RPC worker reloading validators due to change event")
+            logger.info("RPC worker reloading validators due to change event")
             await validators_manager.restart()

Optional: If multiple events fire in quick succession, add a short debounce (e.g., gather events for ~250–500ms) to avoid repeated restarts.

tests/integration/icontracts/tests/test_web_mocking_contract.py (2)

16-19: Remove unused helper to keep tests lean.

_deployment_error_to_tx_receipt is not used. Delete it to reduce noise.

-def _deployment_error_to_tx_receipt(e: DeploymentError):
-    """Helper function to extract transaction receipt from deployment error"""
-    return e.transaction_result
+

81-91: Tidy: drop unused var and redundant f-string; prefer logging over prints.

  • is_mocking_llms is never used.
  • print(f"...") without placeholders is an unnecessary f-string.
  • Consider logging instead of printing in tests to avoid noisy output.
-    is_mocking = mock_web_requests()
-    is_mocking_llms = mock_llms()
+    is_mocking = mock_web_requests()

-    print(f"\nCurrent mocking configuration:")
+    print("\nCurrent mocking configuration:")

Optional: switch to logging.getLogger(__name__).info(...).

backend/protocol_rpc/validators_init.py (2)

18-22: Add return type and replace print with structured logging.

Annotate the coroutine’s return type and avoid print in backend code.

-import backend.validators as validators
+import logging
+import backend.validators as validators
@@
-async def initialize_validators(
+async def initialize_validators(
     validators_json: str,
     db_session: Session,
     validators_manager: validators.Manager,
-) :
+)-> None:
@@
-    if not validators_json:
-        print("No validators to initialize")
+    if not validators_json:
+        logging.getLogger(__name__).info("No validators to initialize")
         return

Also applies to: 32-35


36-40: Preserve traceback by chaining exceptions.

Use from e when re-raising to keep context.

-    except json.JSONDecodeError as e:
-        raise ValueError(f"Invalid JSON in validators_json: {str(e)}")
+    except json.JSONDecodeError as e:
+        raise ValueError(f"Invalid JSON in validators_json: {e}") from e
@@
-        except Exception as e:
-            raise ValueError(f"Failed to create validator `{validator_data}`: {str(e)}")
+        except Exception as e:
+            raise ValueError(
+                f"Failed to create validator `{validator_data}`: {e}"
+            ) from e

Also applies to: 91-92

tests/integration/conftest.py (1)

17-34: Add type hints and fix unused arg; aligns with project guidelines.

Annotate the fixture and rename unused config to _ (or type it) to satisfy Ruff and coding standards.

+from typing import Callable, Optional, Dict, Any
@@
-@pytest.fixture
-def setup_validators():
+@pytest.fixture
+def setup_validators() -> Callable[..., None]:
@@
-    def _setup(mock_response=None, mock_web_responses=None, n_validators=5):
+    def _setup(
+        mock_response: Optional[Dict[str, Any]] = None,
+        mock_web_responses: Optional[Dict[str, Any]] = None,
+        n_validators: int = 5,
+    ) -> None:
@@
-def pytest_configure(config):
+def pytest_configure(_: pytest.Config) -> None:

If you prefer minimalism, just add -> None returns and rename config to _.

Also applies to: 84-86

tests/integration/icontracts/tests/test_web_mocking.py (3)

1-7: Remove unused import.

get_contract_factory isn’t used here.

-import pytest
-import json
-from gltest import get_contract_factory
+import pytest
+import json

11-18: Avoid equality to True/False; add return annotations.

Use truthiness and not instead of == True/False, and annotate tests as returning None.

-def test_web_mocking_enabled():
+def test_web_mocking_enabled() -> None:
@@
-    assert mock_web_requests() == True
+    assert mock_web_requests()
@@
-def test_web_requests_with_real_urls():
+def test_web_requests_with_real_urls() -> None:
@@
-    assert mock_web_requests() == False
+    assert not mock_web_requests()

Also applies to: 20-27


29-61: Minor polish: annotate remaining tests with -> None.

Keeps tests consistent with project type-hint guidance.

-def test_web_mock_with_custom_responses(setup_validators):
+def test_web_mock_with_custom_responses(setup_validators) -> None:
@@
-def test_error_web_mock(setup_validators):
+def test_error_web_mock(setup_validators) -> None:
@@
-def test_default_mock_responses_loading(setup_validators):
+def test_default_mock_responses_loading(setup_validators) -> None:

Also applies to: 63-85, 87-99

backend/node/create_nodes/providers_schema.json (1)

156-162: Tighten schema for mock fields; add structure and descriptions.

mock_response and mock_web_responses are plain objects; define their shapes to improve validation and DX.

Within each plugin_config block, replace generic object types with refs and descriptions:

-              "mock_response": {
-                "type": "object"
-              },
-              "mock_web_responses": {
-                "type": "object"
-              }
+              "mock_response": {
+                "$ref": "#/$defs/MockLLMResponse",
+                "description": "Synthetic LLM response payload injected when TEST_WITH_MOCKS=true."
+              },
+              "mock_web_responses": {
+                "$ref": "#/$defs/MockWebResponses",
+                "description": "Synthetic web responses for render/request when TEST_WITH_MOCKS=true."
+              }

Add the following $defs at the root (once):

{
  "$defs": {
    "MockLLMResponse": {
      "type": "object",
      "additionalProperties": true
    },
    "MockWebResponses": {
      "type": "object",
      "properties": {
        "render": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "properties": { "text": { "type": "string" } },
            "required": ["text"],
            "additionalProperties": false
          }
        },
        "request": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "status": { "type": "integer", "minimum": 100, "maximum": 599 },
              "body": { "type": ["string", "object", "array", "null"] },
              "headers": {
                "type": "object",
                "additionalProperties": { "type": "string" }
              }
            },
            "required": ["status", "body"],
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    }
  }
}

This preserves flexibility while catching malformed fixtures early.

Also applies to: 311-316, 364-370, 418-424, 498-504

tests/unit/test_simulator_sessions.py (1)

344-358: Remove unused monkeypatch parameter.

The test doesn't use the monkeypatch fixture, so it can be removed from the function signature.

Apply this diff:

-async def test_delete_all_validators_uses_request_session(monkeypatch):
+async def test_delete_all_validators_uses_request_session():
     registry_instance = SimpleNamespace(
         delete_all_validators=AsyncMock(),
         get_all_validators=MagicMock(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 f2cb4c4 and 4b29275.

📒 Files selected for processing (40)
  • .env.example (1 hunks)
  • .github/workflows/backend_integration_tests_pr.yml (2 hunks)
  • backend/consensus/worker_service.py (1 hunks)
  • backend/database_handler/llm_providers.py (3 hunks)
  • backend/database_handler/migration/versions/96840ab9133a_add_blocked_at_and_worker_id_to_.py (1 hunks)
  • backend/database_handler/validators_registry.py (2 hunks)
  • backend/node/create_nodes/providers_schema.json (5 hunks)
  • backend/protocol_rpc/app_lifespan.py (3 hunks)
  • backend/protocol_rpc/endpoints.py (5 hunks)
  • backend/protocol_rpc/message_handler/redis_worker_handler.py (2 hunks)
  • backend/protocol_rpc/message_handler/worker_handler.py (2 hunks)
  • backend/protocol_rpc/redis_subscriber.py (1 hunks)
  • backend/protocol_rpc/rpc_methods.py (3 hunks)
  • backend/protocol_rpc/validators_init.py (3 hunks)
  • backend/validators/__init__.py (4 hunks)
  • backend/validators/greyboxing.lua (1 hunks)
  • backend/validators/web.lua (4 hunks)
  • docker-compose.yml (3 hunks)
  • tests/integration/conftest.py (1 hunks)
  • tests/integration/contracts/analytics/test_log_indexer.py (1 hunks)
  • tests/integration/contracts/conftest.py (1 hunks)
  • tests/integration/contracts/errors/test_error_llm.py (1 hunks)
  • tests/integration/contracts/errors/test_error_web.py (1 hunks)
  • tests/integration/contracts/storage/test_storage.py (1 hunks)
  • tests/integration/contracts/storage/test_user_storage.py (1 hunks)
  • tests/integration/features/concurrent_transactions.feature (1 hunks)
  • tests/integration/features/load_testing.feature (1 hunks)
  • tests/integration/features/parallel_deployment.feature (1 hunks)
  • tests/integration/features/security_resilience.feature (1 hunks)
  • tests/integration/features/transaction_state_machine.feature (1 hunks)
  • tests/integration/features/websocket_notifications.feature (1 hunks)
  • tests/integration/fixtures/conftest.py (1 hunks)
  • tests/integration/fixtures/schemas/call_contract_function.py (1 hunks)
  • tests/integration/icontracts/conftest.py (1 hunks)
  • tests/integration/icontracts/tests/test_web_mocking.py (1 hunks)
  • tests/integration/icontracts/tests/test_web_mocking_contract.py (1 hunks)
  • tests/unit/consensus/test_base.py (1 hunks)
  • tests/unit/consensus/test_helpers.py (2 hunks)
  • tests/unit/test_simulator_sessions.py (7 hunks)
  • tests/unit/test_validators_init.py (4 hunks)
✅ Files skipped from review due to trivial changes (6)
  • tests/integration/features/load_testing.feature
  • tests/integration/features/concurrent_transactions.feature
  • tests/integration/features/parallel_deployment.feature
  • tests/integration/features/websocket_notifications.feature
  • tests/integration/features/transaction_state_machine.feature
  • tests/integration/features/security_resilience.feature
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/backend_integration_tests_pr.yml
🧰 Additional context used
📓 Path-based instructions (10)
docker-compose*.yml

📄 CodeRabbit inference engine (AGENTS.md)

Use docker-compose*.yml files for composing services

Files:

  • docker-compose.yml
{backend,examples,tests}/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Python code must be formatted with Black and include type hints

Files:

  • tests/integration/contracts/storage/test_storage.py
  • tests/integration/icontracts/conftest.py
  • tests/unit/consensus/test_base.py
  • tests/integration/contracts/analytics/test_log_indexer.py
  • backend/protocol_rpc/app_lifespan.py
  • tests/integration/icontracts/tests/test_web_mocking_contract.py
  • backend/validators/__init__.py
  • tests/unit/test_validators_init.py
  • tests/integration/fixtures/conftest.py
  • backend/protocol_rpc/message_handler/redis_worker_handler.py
  • tests/integration/conftest.py
  • backend/protocol_rpc/redis_subscriber.py
  • tests/integration/contracts/storage/test_user_storage.py
  • backend/database_handler/validators_registry.py
  • tests/unit/test_simulator_sessions.py
  • backend/protocol_rpc/validators_init.py
  • tests/integration/fixtures/schemas/call_contract_function.py
  • tests/integration/contracts/conftest.py
  • backend/protocol_rpc/rpc_methods.py
  • tests/integration/contracts/errors/test_error_web.py
  • backend/consensus/worker_service.py
  • backend/protocol_rpc/message_handler/worker_handler.py
  • backend/database_handler/llm_providers.py
  • tests/integration/icontracts/tests/test_web_mocking.py
  • backend/database_handler/migration/versions/96840ab9133a_add_blocked_at_and_worker_id_to_.py
  • backend/protocol_rpc/endpoints.py
  • tests/integration/contracts/errors/test_error_llm.py
  • tests/unit/consensus/test_helpers.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Backend tests should be written and run with pytest (unit, integration, e2e)

Files:

  • tests/integration/contracts/storage/test_storage.py
  • tests/integration/icontracts/conftest.py
  • tests/unit/consensus/test_base.py
  • tests/integration/contracts/analytics/test_log_indexer.py
  • tests/integration/icontracts/tests/test_web_mocking_contract.py
  • tests/unit/test_validators_init.py
  • tests/integration/fixtures/conftest.py
  • tests/integration/conftest.py
  • tests/integration/contracts/storage/test_user_storage.py
  • tests/unit/test_simulator_sessions.py
  • tests/integration/fixtures/schemas/call_contract_function.py
  • tests/integration/contracts/conftest.py
  • tests/integration/contracts/errors/test_error_web.py
  • tests/integration/icontracts/tests/test_web_mocking.py
  • tests/integration/contracts/errors/test_error_llm.py
  • tests/unit/consensus/test_helpers.py
tests/{unit,integration,e2e,load}/**

📄 CodeRabbit inference engine (AGENTS.md)

Organize Pytest suites by scope under tests/{unit,integration,e2e,load}

Files:

  • tests/integration/contracts/storage/test_storage.py
  • tests/integration/icontracts/conftest.py
  • tests/unit/consensus/test_base.py
  • tests/integration/contracts/analytics/test_log_indexer.py
  • tests/integration/icontracts/tests/test_web_mocking_contract.py
  • tests/unit/test_validators_init.py
  • tests/integration/fixtures/conftest.py
  • tests/integration/conftest.py
  • tests/integration/contracts/storage/test_user_storage.py
  • tests/unit/test_simulator_sessions.py
  • tests/integration/fixtures/schemas/call_contract_function.py
  • tests/integration/contracts/conftest.py
  • tests/integration/contracts/errors/test_error_web.py
  • tests/integration/icontracts/tests/test_web_mocking.py
  • tests/integration/contracts/errors/test_error_llm.py
  • tests/unit/consensus/test_helpers.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Target Python 3.12, use 4-space indentation, and rely on Black (via pre-commit) for formatting

Files:

  • tests/integration/contracts/storage/test_storage.py
  • tests/integration/icontracts/conftest.py
  • tests/unit/consensus/test_base.py
  • tests/integration/contracts/analytics/test_log_indexer.py
  • backend/protocol_rpc/app_lifespan.py
  • tests/integration/icontracts/tests/test_web_mocking_contract.py
  • backend/validators/__init__.py
  • tests/unit/test_validators_init.py
  • tests/integration/fixtures/conftest.py
  • backend/protocol_rpc/message_handler/redis_worker_handler.py
  • tests/integration/conftest.py
  • backend/protocol_rpc/redis_subscriber.py
  • tests/integration/contracts/storage/test_user_storage.py
  • backend/database_handler/validators_registry.py
  • tests/unit/test_simulator_sessions.py
  • backend/protocol_rpc/validators_init.py
  • tests/integration/fixtures/schemas/call_contract_function.py
  • tests/integration/contracts/conftest.py
  • backend/protocol_rpc/rpc_methods.py
  • tests/integration/contracts/errors/test_error_web.py
  • backend/consensus/worker_service.py
  • backend/protocol_rpc/message_handler/worker_handler.py
  • backend/database_handler/llm_providers.py
  • tests/integration/icontracts/tests/test_web_mocking.py
  • backend/database_handler/migration/versions/96840ab9133a_add_blocked_at_and_worker_id_to_.py
  • backend/protocol_rpc/endpoints.py
  • tests/integration/contracts/errors/test_error_llm.py
  • tests/unit/consensus/test_helpers.py
backend/protocol_rpc/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Implement JSON-RPC endpoint handlers under backend/protocol_rpc/

Files:

  • backend/protocol_rpc/app_lifespan.py
  • backend/protocol_rpc/message_handler/redis_worker_handler.py
  • backend/protocol_rpc/redis_subscriber.py
  • backend/protocol_rpc/validators_init.py
  • backend/protocol_rpc/rpc_methods.py
  • backend/protocol_rpc/message_handler/worker_handler.py
  • backend/protocol_rpc/endpoints.py
backend/validators/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

LLM provider integrations should live under backend/validators/

Files:

  • backend/validators/__init__.py
tests/{unit,integration,e2e,load}/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

Name backend tests as test_.py in the nearest scope folder

Files:

  • tests/unit/test_validators_init.py
  • tests/unit/test_simulator_sessions.py
backend/consensus/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Consensus logic must reside under backend/consensus/

Files:

  • backend/consensus/worker_service.py
.env.example

📄 CodeRabbit inference engine (CLAUDE.md)

Define and document required environment variables in .env.example (POSTGRES_, PROVIDERS_, ENABLE_DEBUG_ENDPOINTS, DEFAULT_VALIDATORS_COUNT)

Files:

  • .env.example
🧬 Code graph analysis (15)
backend/protocol_rpc/app_lifespan.py (4)
backend/validators/__init__.py (2)
  • Manager (142-375)
  • restart (167-182)
backend/protocol_rpc/validators_init.py (1)
  • initialize_validators (18-92)
backend/consensus/worker_service.py (1)
  • handle_validator_change (105-108)
backend/protocol_rpc/redis_subscriber.py (1)
  • register_handler (239-248)
tests/integration/icontracts/tests/test_web_mocking_contract.py (2)
tests/integration/fixtures/conftest.py (2)
  • mock_web_requests (28-31)
  • mock_llms (20-25)
tests/integration/conftest.py (1)
  • setup_validators (18-81)
backend/validators/__init__.py (5)
backend/database_handler/validators_registry.py (2)
  • delete_validator (92-96)
  • delete_all_validators (98-100)
backend/protocol_rpc/endpoints.py (2)
  • delete_validator (333-342)
  • delete_all_validators (346-350)
backend/protocol_rpc/rpc_methods.py (2)
  • delete_validator (183-190)
  • delete_all_validators (194-197)
backend/protocol_rpc/broadcast.py (1)
  • publish (79-86)
backend/protocol_rpc/message_handler/redis_worker_handler.py (1)
  • close (234-239)
tests/unit/test_validators_init.py (6)
backend/protocol_rpc/validators_init.py (1)
  • initialize_validators (18-92)
backend/validators/__init__.py (2)
  • delete_all_validators (121-126)
  • create_validator (95-100)
backend/database_handler/validators_registry.py (2)
  • delete_all_validators (98-100)
  • create_validator (70-74)
backend/protocol_rpc/endpoints.py (2)
  • delete_all_validators (346-350)
  • create_validator (193-229)
backend/database_handler/accounts_manager.py (1)
  • create_new_account (24-31)
backend/domain/types.py (1)
  • LLMProvider (81-98)
backend/protocol_rpc/message_handler/redis_worker_handler.py (2)
backend/protocol_rpc/websocket.py (1)
  • unsubscribe (45-57)
backend/protocol_rpc/message_handler/worker_handler.py (1)
  • close (147-151)
tests/integration/conftest.py (3)
tests/common/request.py (2)
  • payload (21-27)
  • post_request_localhost (43-44)
tests/common/response.py (1)
  • has_success_status (23-24)
tests/integration/fixtures/conftest.py (3)
  • mock_llms (20-25)
  • mock_web_requests (28-31)
  • load_mock_web_responses (34-53)
backend/database_handler/validators_registry.py (4)
tests/db-sqlalchemy/conftest.py (1)
  • session (73-81)
backend/database_handler/models.py (1)
  • Validators (148-166)
backend/protocol_rpc/endpoints.py (1)
  • get_all_validators (353-354)
backend/protocol_rpc/rpc_methods.py (1)
  • get_all_validators (201-204)
tests/unit/test_simulator_sessions.py (4)
backend/validators/__init__.py (2)
  • delete_validator (112-119)
  • delete_all_validators (121-126)
backend/database_handler/validators_registry.py (3)
  • delete_validator (92-96)
  • delete_all_validators (98-100)
  • get_all_validators (51-57)
backend/protocol_rpc/endpoints.py (3)
  • delete_validator (333-342)
  • delete_all_validators (346-350)
  • get_all_validators (353-354)
backend/protocol_rpc/rpc_methods.py (3)
  • delete_validator (183-190)
  • delete_all_validators (194-197)
  • get_all_validators (201-204)
backend/protocol_rpc/validators_init.py (6)
backend/validators/__init__.py (3)
  • Manager (142-375)
  • delete_all_validators (121-126)
  • create_validator (95-100)
backend/database_handler/validators_registry.py (2)
  • delete_all_validators (98-100)
  • create_validator (70-74)
backend/protocol_rpc/endpoints.py (2)
  • delete_all_validators (346-350)
  • create_validator (193-229)
backend/database_handler/accounts_manager.py (2)
  • AccountsManager (12-82)
  • create_new_account (24-31)
backend/domain/types.py (2)
  • Validator (102-144)
  • LLMProvider (81-98)
backend/node/create_nodes/providers.py (1)
  • get_default_provider_for (99-110)
backend/protocol_rpc/rpc_methods.py (4)
backend/protocol_rpc/dependencies.py (1)
  • get_validators_manager (121-126)
backend/validators/__init__.py (4)
  • create_validator (95-100)
  • update_validator (102-110)
  • delete_validator (112-119)
  • delete_all_validators (121-126)
backend/database_handler/validators_registry.py (4)
  • create_validator (70-74)
  • update_validator (76-90)
  • delete_validator (92-96)
  • delete_all_validators (98-100)
backend/protocol_rpc/endpoints.py (4)
  • create_validator (193-229)
  • update_validator (292-329)
  • delete_validator (333-342)
  • delete_all_validators (346-350)
backend/consensus/worker_service.py (4)
backend/protocol_rpc/app_lifespan.py (1)
  • handle_validator_change (321-324)
backend/validators/__init__.py (1)
  • restart (167-182)
backend/validators/llm.py (1)
  • restart (167-169)
backend/protocol_rpc/message_handler/redis_worker_handler.py (1)
  • subscribe_to_validator_events (74-117)
backend/protocol_rpc/message_handler/worker_handler.py (5)
backend/protocol_rpc/configuration.py (1)
  • GlobalConfiguration (5-8)
backend/protocol_rpc/message_handler/types.py (2)
  • LogEvent (21-39)
  • to_dict (30-39)
backend/protocol_rpc/message_handler/fastapi_handler.py (3)
  • error (133-142)
  • _socket_emit (87-95)
  • send_message (166-172)
backend/protocol_rpc/message_handler/redis_worker_handler.py (3)
  • _socket_emit (172-193)
  • send_message (195-211)
  • close (234-239)
backend/protocol_rpc/message_handler/base.py (1)
  • _log_message (63-92)
backend/database_handler/llm_providers.py (1)
tests/db-sqlalchemy/conftest.py (1)
  • session (73-81)
tests/integration/icontracts/tests/test_web_mocking.py (4)
tests/integration/fixtures/conftest.py (1)
  • mock_web_requests (28-31)
tests/common/request.py (2)
  • payload (21-27)
  • post_request_localhost (43-44)
tests/common/response.py (1)
  • has_success_status (23-24)
tests/integration/conftest.py (1)
  • setup_validators (18-81)
backend/protocol_rpc/endpoints.py (3)
backend/validators/__init__.py (5)
  • Manager (142-375)
  • create_validator (95-100)
  • update_validator (102-110)
  • delete_validator (112-119)
  • delete_all_validators (121-126)
backend/database_handler/validators_registry.py (5)
  • create_validator (70-74)
  • update_validator (76-90)
  • delete_validator (92-96)
  • delete_all_validators (98-100)
  • get_all_validators (51-57)
backend/protocol_rpc/rpc_methods.py (5)
  • create_validator (103-122)
  • update_validator (160-179)
  • delete_validator (183-190)
  • delete_all_validators (194-197)
  • get_all_validators (201-204)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 167-167: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 Ruff (0.14.2)
tests/integration/icontracts/conftest.py

14-14: from conftest import * used; unable to detect undefined names

(F403)

backend/protocol_rpc/app_lifespan.py

321-321: Unused function argument: event_data

(ARG001)


323-323: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/integration/icontracts/tests/test_web_mocking_contract.py

82-82: Local variable is_mocking_llms is assigned to but never used

Remove assignment to unused variable is_mocking_llms

(F841)


84-84: f-string without any placeholders

Remove extraneous f prefix

(F541)

backend/validators/__init__.py

374-374: Do not catch blind exception: Exception

(BLE001)


375-375: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

backend/protocol_rpc/message_handler/redis_worker_handler.py

107-107: Do not catch blind exception: Exception

(BLE001)


117-117: Store a reference to the return value of asyncio.create_task

(RUF006)

tests/integration/conftest.py

84-84: Unused function argument: config

(ARG001)

backend/protocol_rpc/redis_subscriber.py

23-28: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

tests/unit/test_simulator_sessions.py

269-269: Unused function argument: provider

(ARG001)


269-269: Unused function argument: model

(ARG001)


345-345: Unused function argument: monkeypatch

(ARG001)

tests/integration/contracts/conftest.py

14-14: from conftest import * used; unable to detect undefined names

(F403)

backend/protocol_rpc/rpc_methods.py

111-111: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


168-168: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


185-185: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


195-195: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

backend/protocol_rpc/message_handler/worker_handler.py

103-103: Do not catch blind exception: Exception

(BLE001)

tests/integration/icontracts/tests/test_web_mocking.py

17-17: Avoid equality comparisons to True; use mock_web_requests(): for truth checks

Replace with mock_web_requests()

(E712)


26-26: Avoid equality comparisons to False; use not mock_web_requests(): for false checks

Replace with not mock_web_requests()

(E712)

🔇 Additional comments (28)
docker-compose.yml (2)

62-62: Verify image sourcing and versioning strategy for jsonrpc service.

The service now specifies an explicit image with :latest tag while retaining a build configuration. This creates ambiguity:

  1. Image availability: Is genlayer-studio-jsonrpc:latest pre-built and available (in a registry, CI cache, or locally)? If images aren't available, compose will fall back to building, but this is implicit and unclear.
  2. Non-deterministic tagging: Using :latest violates reproducibility best practices and can cause inconsistent deployments if the image is rebuilt.
  3. Dual configuration: Having both image and build specified is ambiguous—clarify whether this is transitional config or intentional for flexibility.

To clarify the intent, please verify:

  • Are these images built and pushed to a registry by the CI workflow as part of the mocking system PR changes?
  • Should local development use the pre-built images or build locally?
  • Is using :latest intentional, or should specific version tags be used instead?

If these images are expected to be pre-built for CI environments, consider:

  • Using specific version tags or digests instead of :latest.
  • Clarifying in comments whether this compose file is for local dev (use build:) or CI (use pre-built image:).
  • Providing a separate compose override or .env configuration to distinguish the two use cases.

203-203: Apply same image sourcing and versioning clarification to database-migration and consensus-worker services.

Both services at lines 203 and 219 have the same dual image:/build: configuration with :latest tagging. The same concerns and recommendations from the jsonrpc review apply here.

For consensus-worker specifically (a replicated service), ensure that all replicas pull the same image version to maintain consistency—using :latest could result in different replicas running different image versions if the tag is updated during deployment.

Also applies to: 219-219

backend/database_handler/migration/versions/96840ab9133a_add_blocked_at_and_worker_id_to_.py (3)

8-19: LGTM! Formatting changes aligned with Black.

The revision metadata has been reformatted to use double quotes, which is consistent with Black's formatting preferences.


34-38: LGTM! Downgrade logic correctly mirrors upgrade.

The downgrade function properly drops the columns added in the upgrade, and the single-line formatting is appropriate for these concise statements.


1-38: Verify whether this migration belongs in this PR.

This database migration adds blocked_at and worker_id columns to the transactions table, which appears unrelated to the PR's stated objective of introducing a web request mocking system for integration tests. While the formatting changes themselves are fine, please confirm whether this migration file should be included in this PR or moved to a separate one focused on transaction handling improvements.

tests/unit/consensus/test_helpers.py (2)

29-31: LGTM: Consistent with unified mocking approach.

The change from TEST_WITH_MOCK_LLMS to TEST_WITH_MOCKS correctly aligns with the PR's objective to unify LLM and web request mocking under a single environment variable.


874-880: LGTM: Explicit type annotation improves clarity.

Making the timeout parameter explicitly typed as int | None is good practice and aligns with the coding guidelines requiring type hints. The implementation correctly handles None by applying adaptive timeout values based on the mocking mode.

As per coding guidelines.

tests/integration/contracts/errors/test_error_llm.py (1)

5-9: Import path update verified and correct

The new import path tests.integration.contracts.errors.test_error_execution is correct—the file exists and all three functions are properly defined there. No stale references to the old path remain in the codebase.

backend/protocol_rpc/message_handler/worker_handler.py (1)

26-31: Looking at the web search results, the Optional type from the typing module should be used for parameters that might accept None, and whenever you have a keyword argument with default value None, you should use Optional. Additionally, the old implicit optional behavior is no longer recommended; type checkers should move towards requiring the optional type to be made explicit.

Now let me examine the specific file to verify the context and check imports:

Add Optional type hint for config parameter defaulting to None

Whenever you have a keyword argument with default value None, you should use Optional. The old implicit optional behavior is no longer recommended; type checkers should move towards requiring the optional type to be made explicit. Apply the suggested fix:

    def __init__(
        self,
-       config: GlobalConfiguration = None,
+       config: Optional[GlobalConfiguration] = None,
         jsonrpc_url: Optional[str] = None,
         worker_id: Optional[str] = None,
     ):

Ensure Optional is imported from typing at the top of the file.

tests/integration/contracts/analytics/test_log_indexer.py (1)

5-7: LGTM! Fixture import path consolidation.

The import path has been correctly updated to reference the centralized fixtures location, aligning with the PR's goal of unifying test infrastructure.

tests/integration/contracts/storage/test_user_storage.py (1)

5-7: LGTM! Consistent fixture import path update.

The import path has been updated to match the centralized fixtures location.

tests/integration/contracts/storage/test_storage.py (1)

5-7: LGTM! Fixture import path aligned with new structure.

The import path has been correctly updated to use the centralized fixtures location.

backend/consensus/worker_service.py (1)

104-111: Validator change handler looks good, but consider restart frequency.

The handler correctly reloads validators when notified of changes. The error handling is delegated to the subscription layer (in subscribe_to_validator_events, line 107), which catches exceptions during callback execution.

However, consider whether frequent validator changes could cause restart thrashing, as validators_manager.restart() acquires a writer lock and restarts both LLM and web modules. If this becomes an issue, you might want to implement debouncing or rate limiting.

tests/integration/contracts/errors/test_error_web.py (1)

4-8: LGTM! Import path updated for error execution utilities.

The import path has been correctly updated to reference the consolidated error execution module.

backend/protocol_rpc/message_handler/redis_worker_handler.py (1)

104-108: Acceptable exception handling in message processing loop.

Catching Exception here is appropriate since this is a continuous message processing loop that should remain resilient to individual message processing failures. The error is logged at line 108, which provides visibility.

backend/validators/web.lua (1)

97-111: LGTM on request/response mock shape and logging.

Mocked status/body/headers handling and debug logs are clear and compatible with callers.

backend/validators/__init__.py (1)

234-241: Good: mock_web_responses plumbed into host_data.

This cleanly mirrors existing mock_response handling and enables web.lua to switch behavior under TEST_WITH_MOCKS.

tests/integration/icontracts/tests/test_web_mocking_contract.py (1)

21-72: Scenario coverage looks good.

Three URL cases (404, example.com, nonexistent domain) exercise both render and request paths against mocks; assertions are appropriate.

tests/unit/test_validators_init.py (2)

1-42: LGTM! Clean migration to validators_manager pattern.

The tests correctly mock the new validators_manager structure using SimpleNamespace, and all assertions properly verify the registry interactions.


44-174: Excellent test coverage with proper error handling.

The tests thoroughly validate the new initialization flow, including:

  • Correct delegation to validators_manager.registry methods
  • Proper account creation for each validator
  • Exception propagation for invalid configurations
tests/unit/test_simulator_sessions.py (3)

170-214: LGTM! Consistent migration to validators_manager pattern.

The test correctly validates that create_validator uses the provided validators_manager for registry operations.


216-292: LGTM! Appropriate mocking for unit test isolation.

The test correctly mocks check_provider_is_available and uses the validators_manager pattern. The unused parameters in the mock function (line 269-270) are intentional—the signature must match the real function, but the stub implementation doesn't need the values.


294-342: LGTM! Consistent validator management updates.

Both tests correctly verify that the endpoints delegate to validators_manager.registry for update and delete operations.

backend/protocol_rpc/rpc_methods.py (1)

102-198: LGTM! Correct FastAPI dependency injection usage.

The RPC methods correctly accept validators_manager via FastAPI's Depends() and forward it to the implementation layer. The static analysis warnings about Depends() in argument defaults are false positives—this is the standard FastAPI pattern for dependency injection.

backend/protocol_rpc/endpoints.py (4)

193-230: LGTM! Clean migration to centralized validator management.

The function correctly delegates validator creation to validators_manager.registry while maintaining session-scoped account creation. This aligns with the broader architectural shift to a centralized validators.Manager.


232-289: LGTM! Consistent validator creation pattern.

Both random validator creation functions correctly use validators_manager.registry for all validator lifecycle operations.


291-330: LGTM! Update operation migrated correctly.

The function properly delegates validator updates to validators_manager.registry.update_validator while maintaining the existing provider validation logic.


332-351: LGTM! Simplified delete operations.

Both delete functions correctly use validators_manager.registry and no longer require a session parameter, resulting in a cleaner API surface. The delete_all_validators correctly returns the (empty) list of remaining validators.

Comment on lines +22 to +26
try:
self.session.commit()
except IntegrityError:
# Another worker already seeded the providers, rollback and continue
self.session.rollback()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add logging when IntegrityError occurs.

The silent handling of IntegrityError provides no observability when concurrent seeding conflicts occur. While the rollback strategy is sound for concurrent scenarios, the assumption that all IntegrityError exceptions stem from concurrent seeding may not hold—constraint violations or data integrity issues could also trigger this exception and would be silently masked.

Apply this diff to add logging:

         try:
             self.session.commit()
         except IntegrityError:
             # Another worker already seeded the providers, rollback and continue
+            import logging
+            logging.info("IntegrityError during reset_defaults, likely due to concurrent seeding. Rolling back.")
             self.session.rollback()

Alternatively, use a module-level logger if one exists.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/database_handler/llm_providers.py around lines 22 to 26, the
IntegrityError is currently swallowed after rollback; modify the except block to
log the exception (including stack/exception details) with a clear message that
a concurrent seeding conflict may have occurred but other integrity issues are
possible, then rollback as before; prefer using the module-level logger if one
exists (logger.exception or logger.warning with exc_info=True) so the error and
context are recorded for debugging.

Comment on lines +70 to +74
try:
self.session.commit()
except IntegrityError:
# Another worker already seeded the providers, rollback and continue
self.session.rollback()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add logging when IntegrityError occurs.

Similar to reset_defaults, this method silently handles IntegrityError without logging. Given that update_defaults performs more complex operations (deleting old defaults and adding/updating new ones), observability is especially important for diagnosing concurrent conflicts or unexpected constraint violations.

Apply this diff to add logging:

         try:
             self.session.commit()
         except IntegrityError:
             # Another worker already seeded the providers, rollback and continue
+            import logging
+            logging.info("IntegrityError during update_defaults, likely due to concurrent seeding. Rolling back.")
             self.session.rollback()

Alternatively, use a module-level logger if one exists.

📝 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:
self.session.commit()
except IntegrityError:
# Another worker already seeded the providers, rollback and continue
self.session.rollback()
try:
self.session.commit()
except IntegrityError:
# Another worker already seeded the providers, rollback and continue
logger.info(
"IntegrityError during update_defaults, likely due to concurrent seeding. "
"Rolling back."
)
self.session.rollback()
🤖 Prompt for AI Agents
In backend/database_handler/llm_providers.py around lines 70 to 74, the except
IntegrityError block silently swallows failures; update it to log the exception
and context before rolling back: catch the exception as e, call the module-level
logger (or create one via logging.getLogger(__name__)) to emit an error-level
message that includes a clear description (e.g., "IntegrityError while
committing update_defaults") and the exception details (e), then perform
self.session.rollback(); keep behavior otherwise unchanged.

Comment on lines +74 to +117
async def subscribe_to_validator_events(self, callback):
"""
Subscribe to validator change events.
Used by consensus-worker to reload validators when they change.
Args:
callback: Async function to call when validator event received
"""
if not self.redis_client:
await self.initialize()

if not self.redis_client:
logger.error(
"Cannot subscribe to validator events: Redis client not initialized"
)
return

redis_client = self.redis_client

async def listener():
"""Listen for validator events and call callback."""
redis_pubsub = redis_client.pubsub()
await redis_pubsub.subscribe(self.VALIDATOR_CHANNEL)
logger.info(
f"Worker {self.worker_id} subscribed to {self.VALIDATOR_CHANNEL}"
)

try:
async for message in redis_pubsub.listen():
if message["type"] == "message":
try:
event_data = json.loads(message["data"])
await callback(event_data)
except Exception as e:
logger.error(f"Error processing validator event: {e}")
except asyncio.CancelledError:
logger.info(
f"Worker {self.worker_id} validator event listener cancelled"
)
await redis_pubsub.unsubscribe(self.VALIDATOR_CHANNEL)
await redis_pubsub.close()

# Start listener in background
asyncio.create_task(listener())
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Store and manage the background listener task reference.

The asyncio.create_task(listener()) at line 117 creates a background task without storing its reference. This prevents proper cleanup when the handler is closed, potentially leaving the listener task running after the Redis client is closed (line 237 in close()).

Apply this diff to track and clean up the listener task:

 class RedisWorkerMessageHandler(MessageHandler):
     # Redis channels for different event types
     CONSENSUS_CHANNEL = "consensus:events"
     TRANSACTION_CHANNEL = "transaction:events"
     GENERAL_CHANNEL = "general:events"
     VALIDATOR_CHANNEL = "validator:events"
 
     def __init__(
         self,
         config: GlobalConfiguration = None,
         worker_id: Optional[str] = None,
         redis_url: Optional[str] = None,
     ):
         # ... existing init code ...
+        self._listener_tasks: list[asyncio.Task] = []

Then update the method to store the task:

-        # Start listener in background
-        asyncio.create_task(listener())
+        # Start listener in background and track it
+        task = asyncio.create_task(listener())
+        self._listener_tasks.append(task)

And update the close() method to clean up tasks:

     async def close(self):
         """Clean up resources."""
+        # Cancel all listener tasks
+        for task in self._listener_tasks:
+            if not task.done():
+                task.cancel()
+        
+        # Wait for all tasks to complete
+        if self._listener_tasks:
+            await asyncio.gather(*self._listener_tasks, return_exceptions=True)
+        self._listener_tasks.clear()
+        
         if self.redis_client:
             await self.redis_client.close()
             self.redis_client = None
             logger.info(f"Worker {self.worker_id} disconnected from Redis")

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.2)

107-107: Do not catch blind exception: Exception

(BLE001)


117-117: Store a reference to the return value of asyncio.create_task

(RUF006)

Comment on lines 102 to +105
except asyncio.TimeoutError:
logger.warning(f"Timeout sending event to JSON-RPC server: {log_event.name}")
logger.warning(
f"Timeout sending event to JSON-RPC server: {log_event.name}"
)
except Exception as e:
logger.error(f"Error sending event to JSON-RPC server: {e}")

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid broad Exception catch; handle aiohttp errors explicitly

Catching Exception trips Ruff BLE001 and can hide real bugs. Narrow the excepts.

Apply:

-        except asyncio.TimeoutError:
+        except asyncio.TimeoutError:
             logger.warning(
                 f"Timeout sending event to JSON-RPC server: {log_event.name}"
             )
-        except Exception as e:
-            logger.error(f"Error sending event to JSON-RPC server: {e}")
+        except aiohttp.ClientError as e:
+            logger.warning(f"HTTP client error sending event: {e}")

Optionally let unexpected exceptions bubble (preferred), or log and re‑raise:

+        except Exception as e:
+            logger.exception("Unexpected error sending event")
+            raise
🧰 Tools
🪛 Ruff (0.14.2)

103-103: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In backend/protocol_rpc/message_handler/worker_handler.py around lines 99 to
105, the current except Exception: is too broad and triggers Ruff BLE001 and can
hide real bugs; replace it with explicit handlers for expected network errors
(e.g., catch aiohttp.ClientError and log the error) and optionally handle
asyncio.CancelledError separately if cancellation semantics are needed, then
either re-raise unexpected exceptions or remove the broad catch so they bubble
up; ensure the log includes the error details and keep the asyncio.TimeoutError
handler as-is.

Comment on lines 170 to 201
-- Return mock response if it exists
if ctx.host_data.mock_response then
local result
local mock_data

if args.template == "EqComparative" then
-- Return the matching response to gl.eq_principle_prompt_comparative request which contains a principle key in the payload
result = get_mock_response_from_table(ctx.host_data.mock_response.eq_principle_prompt_comparative, mapped_prompt.prompt.user_message)
mock_data = get_mock_response_from_table(ctx.host_data.mock_response.eq_principle_prompt_comparative, mapped_prompt.prompt.user_message)
elseif args.template == "EqNonComparativeValidator" then
-- Return the matching response to gl.eq_principle_prompt_non_comparative request which contains an output key in the payload
result = get_mock_response_from_table(ctx.host_data.mock_response.eq_principle_prompt_non_comparative, mapped_prompt.prompt.user_message)
mock_data = get_mock_response_from_table(ctx.host_data.mock_response.eq_principle_prompt_non_comparative, mapped_prompt.prompt.user_message)
else
-- Return the matching response to gl.exec_prompt request which does not contain any specific key in the payload
-- EqNonComparativeLeader is essentially just exec_prompt
result = get_mock_response_from_table(ctx.host_data.mock_response.response, mapped_prompt.prompt.user_message)
mock_data = get_mock_response_from_table(ctx.host_data.mock_response.response, mapped_prompt.prompt.user_message)
end

-- Wrap mock response in the same format as exec_prompt_in_provider returns
-- Convert to JSON string if it's a table, otherwise use as-is
local data_value
if type(mock_data) == "table" then
data_value = lib.rs.json_stringify(mock_data)
else
data_value = mock_data
end

local result = {
data = data_value,
consumed_gen = 0
}
lib.log{level = "debug", message = "executed with", type = type(result), res = result}
return result
end
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Nil-safety on mock tables and ensure result.data is always a string

Accessing ctx.host_data.mock_response.* can be nil; pairs(nil) in get_mock_response_from_table will error. Also, result.data may become boolean/number, while downstream expects a string like exec_prompt_in_provider. Guard subtables and coerce non-strings.

Apply:

@@
-        if args.template == "EqComparative" then
+        if args.template == "EqComparative" then
             -- Return the matching response to gl.eq_principle_prompt_comparative request which contains a principle key in the payload
-            mock_data = get_mock_response_from_table(ctx.host_data.mock_response.eq_principle_prompt_comparative, mapped_prompt.prompt.user_message)
+            local src = (ctx.host_data.mock_response and ctx.host_data.mock_response.eq_principle_prompt_comparative) or {}
+            mock_data = get_mock_response_from_table(src, mapped_prompt.prompt.user_message)
         elseif args.template == "EqNonComparativeValidator" then
             -- Return the matching response to gl.eq_principle_prompt_non_comparative request which contains an output key in the payload
-            mock_data = get_mock_response_from_table(ctx.host_data.mock_response.eq_principle_prompt_non_comparative, mapped_prompt.prompt.user_message)
+            local src = (ctx.host_data.mock_response and ctx.host_data.mock_response.eq_principle_prompt_non_comparative) or {}
+            mock_data = get_mock_response_from_table(src, mapped_prompt.prompt.user_message)
         else
             -- Return the matching response to gl.exec_prompt request which does not contain any specific key in the payload
             -- EqNonComparativeLeader is essentially just exec_prompt
-            mock_data = get_mock_response_from_table(ctx.host_data.mock_response.response, mapped_prompt.prompt.user_message)
+            local src = (ctx.host_data.mock_response and ctx.host_data.mock_response.response) or {}
+            mock_data = get_mock_response_from_table(src, mapped_prompt.prompt.user_message)
         end
@@
-        local data_value
-        if type(mock_data) == "table" then
-            data_value = lib.rs.json_stringify(mock_data)
-        else
-            data_value = mock_data
-        end
+        local t = type(mock_data)
+        local data_value
+        if t == "table" then
+            data_value = lib.rs.json_stringify(mock_data)
+        elseif t ~= "string" then
+            data_value = tostring(mock_data)
+        else
+            data_value = mock_data
+        end

Comment on lines +17 to 26
"private_key": Optional[str],
"stake": int,
"primary_model": {
"config": dict,
"model": str,
"provider": str,
"plugin": str,
"plugin_config": dict,
"fallback_validator": str,
"fallback_validator": Optional[str],
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Optional[str] for schema fields looks consistent; add variable annotation and verify consumers handle typing.Union

The Optional updates align with existing Optional[...] usage in this schema file. To meet the type‑hints guideline and avoid mypy noise, annotate the top-level schema dict and import Any. Also please confirm any schema consumers don’t pass these typing objects into isinstance directly.

Apply:

-from typing import Optional
+from typing import Optional, Any
@@
-call_contract_function_response = {
+call_contract_function_response: dict[str, Any] = {

Run to spot fragile isinstance-usage on this schema:


🏁 Script executed:

#!/bin/bash
rg -nP --type=py -C2 '\bcall_contract_function_response\b'
rg -nP --type=py -C2 'isinstance\s*\(.*call_contract_function_response'

Length of output: 4181


🏁 Script executed:

#!/bin/bash
# Find the assert_dict_struct function definition
rg -nP --type=py -B2 -A10 'def\s+assert_dict_struct'

Length of output: 703


🏁 Script executed:

#!/bin/bash
# Check the actual file to see the full schema structure
cat tests/integration/fixtures/schemas/call_contract_function.py

Length of output: 1656


🏁 Script executed:

#!/bin/bash
# Get complete assert_dict_struct and assert_is_instance implementations
cat tests/common/response.py

Length of output: 727


Remove Optional[...] type hints from schema dict values—they cause runtime TypeError in assert_dict_struct

The schema contains Optional[str] and Optional[dict] as values (lines 19, 26-27). When assert_dict_struct() validates responses, it calls isinstance(data, structure) on leaf nodes. Typing constructs like Optional[str] are not valid arguments to isinstance()—this will raise TypeError: isinstance() arg 2 must be a type or tuple of types at runtime.

Replace Optional constructs with actual types in the schema. Either:

  • Use primitive types (str, dict, int, etc.) and handle None cases separately in validator logic, or
  • Omit optional fields from the schema structure entirely if they're truly optional

Apply to lines 19, 26, 27 in tests/integration/fixtures/schemas/call_contract_function.py:

  • Line 19: "private_key": Optional[str],"private_key": str,
  • Line 26: "fallback_validator": Optional[str],"fallback_validator": str,
  • Line 27: "secondary_model": Optional[dict],"secondary_model": dict,
🤖 Prompt for AI Agents
In tests/integration/fixtures/schemas/call_contract_function.py around lines 17
to 26, the schema uses typing.Optional[...] (lines 19, 26, 27) which causes
isinstance() TypeError at runtime; replace those Optional[...] entries with
regular types or remove optional fields: change "private_key": Optional[str] to
"private_key": str, change "fallback_validator": Optional[str] to
"fallback_validator": str, and change "secondary_model": Optional[dict] to
"secondary_model": dict (or remove any of these keys from the schema if the
field should be omitted when absent).

def pytest_configure(config):
load_dotenv(override=True)
# Import main configuration
from conftest import *
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not import from conftest; rely on pytest’s fixture discovery.

from conftest import * breaks pytest’s intended loading order and triggers Ruff F403; it can cause duplicate fixture registration/import cycles. Remove and let pytest auto-discover parent conftest fixtures.

-# Import main configuration
-from conftest import *
+# Rely on pytest hierarchical fixture discovery; no explicit import needed.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.2)

14-14: from conftest import * used; unable to detect undefined names

(F403)

🤖 Prompt for AI Agents
In tests/integration/icontracts/conftest.py around line 14, remove the line
"from conftest import *" because importing conftest directly breaks pytest
fixture discovery and triggers Ruff F403; instead rely on pytest's automatic
fixture discovery by deleting that import and ensuring any needed fixtures are
defined or imported via proper test modules or a shared conftest in a parent
package so fixtures are discovered without explicit imports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants