From 832fabe1c5cffbd8fdbec7e6e1fedfc94c7cc947 Mon Sep 17 00:00:00 2001 From: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:14:57 -0400 Subject: [PATCH] refactor(platform): updates to StreamInfo and ClusterInfo models (#127) * refactor(platform): remove configuration from `platform.create_cluster` * refactor(runner): make small adjustment to task kill logic * refactor(platform): add StreamInfo to client types * fix(platform): HMAC params in wrong order * feat(platform): add expiration to ClusterInfo record * refactor(tests): fix encoding test, add fuzzing --- setup.py | 3 ++- silverback/_cli.py | 1 - silverback/cluster/client.py | 8 +------- silverback/cluster/types.py | 11 ++++++++++- silverback/runner.py | 4 +++- tests/test_cluster.py | 26 +++++++++++++++++++------- 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/setup.py b/setup.py index d9f0cac7..27035f80 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,8 @@ "pytest>=6.0", # Core testing package "pytest-xdist", # Multi-process runner "pytest-cov", # Coverage analyzer plugin - # "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer + "hypothesis", # Strategy-based fuzzer + "hypothesis-jsonschema", # Generate strategies for pydantic models ], "lint": [ "black>=24", # Auto-formatter and linter diff --git a/silverback/_cli.py b/silverback/_cli.py index b234eaea..ea796eef 100644 --- a/silverback/_cli.py +++ b/silverback/_cli.py @@ -245,7 +245,6 @@ def new_cluster( cluster = workspace_client.create_cluster( cluster_name=cluster_name, cluster_slug=cluster_slug, - configuration=configuration, ) click.echo(f"{click.style('SUCCESS', fg='green')}: Created '{cluster.name}'") # TODO: Pay for cluster via new stream diff --git a/silverback/cluster/client.py b/silverback/cluster/client.py index f4ad5f53..36792bac 100644 --- a/silverback/cluster/client.py +++ b/silverback/cluster/client.py @@ -8,7 +8,6 @@ from .types import ( BotHealth, BotInfo, - ClusterConfiguration, ClusterHealth, ClusterInfo, ClusterState, @@ -286,16 +285,11 @@ def create_cluster( self, cluster_slug: str | None = None, cluster_name: str | None = None, - configuration: ClusterConfiguration = ClusterConfiguration(), ) -> ClusterInfo: response = self.client.post( "/clusters/", params=dict(workspace=str(self.id)), - json=dict( - name=cluster_name, - slug=cluster_slug, - configuration=configuration.model_dump(), - ), + json=dict(name=cluster_name, slug=cluster_slug), ) handle_error_with_response(response) diff --git a/silverback/cluster/types.py b/silverback/cluster/types.py index 4544e821..3e31a029 100644 --- a/silverback/cluster/types.py +++ b/silverback/cluster/types.py @@ -4,6 +4,7 @@ from datetime import datetime from typing import Annotated, Any +from ape.types import AddressType from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.hmac import HMAC, hashes from eth_pydantic_types import Address, HexBytes @@ -12,7 +13,7 @@ def normalize_bytes(val: bytes, length: int = 16) -> bytes: - return b"\x00" * (length - len(val)) + val + return val + b"\x00" * (length - len(val)) class WorkspaceInfo(BaseModel): @@ -269,6 +270,12 @@ def __str__(self) -> str: return self.name.capitalize() +class StreamInfo(BaseModel): + chain_id: int + manager: AddressType + stream_id: int + + class ClusterInfo(BaseModel): # NOTE: Raw API object (gets exported) id: uuid.UUID # NOTE: Keep this private, used as a temporary secret key for payment @@ -278,6 +285,8 @@ class ClusterInfo(BaseModel): name: str # User-friendly display name slug: str # Shorthand name, for CLI and URI usage + expiration: datetime | None = None # NOTE: self-hosted clusters have no expiration + created: datetime # When the resource was first created status: ResourceStatus last_updated: datetime # Last time the resource was changed (upgrade, provisioning, etc.) diff --git a/silverback/runner.py b/silverback/runner.py index af535df0..1bd4eda0 100644 --- a/silverback/runner.py +++ b/silverback/runner.py @@ -237,7 +237,9 @@ async def run(self): logger.warning(f"Runtime error(s) detected, shutting down:\n{runtime_errors}") # Cancel any still running - (task.cancel() for task in tasks_running) + for task in tasks_running: + task.cancel() + # NOTE: All listener tasks are shut down now # Execute Silverback shutdown task(s) before shutting down the broker and app diff --git a/tests/test_cluster.py b/tests/test_cluster.py index 75051033..8a9a909e 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -1,13 +1,25 @@ -import uuid +from eth_utils import to_checksum_address +from hypothesis import given +from hypothesis import strategies as st +from hypothesis_jsonschema import from_schema from silverback.cluster.types import ClusterConfiguration +CONFIG_SCHEMA = ClusterConfiguration.model_json_schema() -def test_hmac_signature(): - config = ClusterConfiguration() - cluster_id = uuid.uuid4() - owner = "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97" + +@given( # type: ignore[call-overload] + cluster_id=st.uuids(version=4), + owner=st.binary(min_size=20, max_size=20).map(to_checksum_address), + config_dict=from_schema(CONFIG_SCHEMA), +) +def test_hmac_signature(cluster_id, owner, config_dict): + # NOTE: Ignore `version` fuzzed value + config_dict["version"] = 1 + config = ClusterConfiguration(**config_dict) product_code = config.get_product_code(owner, cluster_id) + # NOTE: There is a gap of empty bytes between 8-16 + encoded_config, sig = product_code[:8], product_code[16:] # NOTE: Ensure we can properly decode the encoded product code into a configuration - assert config == ClusterConfiguration.decode(product_code[:16]) - assert config.validate_product_code(owner, product_code[16:], cluster_id) + assert config == ClusterConfiguration.decode(encoded_config) + assert config.validate_product_code(owner, sig, cluster_id)