-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: allow configuring validators from envvar (#585)
* init feature Signed-off-by: Agustín Ramiro Díaz <[email protected]> * add unit tests Signed-off-by: Agustín Ramiro Díaz <[email protected]> * handle amount and fix Signed-off-by: Agustín Ramiro Díaz <[email protected]> * improve example Signed-off-by: Agustín Ramiro Díaz <[email protected]> * more retries in healthcheck Signed-off-by: Agustín Ramiro Díaz <[email protected]> * fix e2e test Signed-off-by: Agustín Ramiro Díaz <[email protected]> --------- Signed-off-by: Agustín Ramiro Díaz <[email protected]>
- Loading branch information
1 parent
1e66f9d
commit b3e83d0
Showing
6 changed files
with
264 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import json | ||
from dataclasses import dataclass | ||
from backend.database_handler.accounts_manager import AccountsManager | ||
from backend.database_handler.validators_registry import ValidatorsRegistry | ||
|
||
|
||
@dataclass | ||
class ValidatorConfig: | ||
stake: int | ||
provider: str | ||
model: str | ||
config: dict | None = None | ||
plugin: str | None = None | ||
plugin_config: dict | None = None | ||
amount: int = 1 | ||
|
||
|
||
def initialize_validators( | ||
validators_json: str, | ||
validators_registry: ValidatorsRegistry, | ||
accounts_manager: AccountsManager, | ||
validator_creator=None, | ||
): | ||
""" | ||
Idempotently initialize validators from a JSON string by deleting all existing validators and creating new ones. | ||
Args: | ||
validators_json: JSON string containing validator configurations | ||
validators_registry: Registry to store validator information | ||
accounts_manager: AccountsManager to create validator accounts | ||
validator_creator: Function to create validators (defaults to endpoints.create_validator) | ||
""" | ||
|
||
if not validators_json: | ||
print("No validators to initialize") | ||
return | ||
|
||
# If no validator_creator is provided, import the default one | ||
if validator_creator is None: | ||
from backend.protocol_rpc.endpoints import create_validator | ||
|
||
validator_creator = create_validator | ||
|
||
try: | ||
validators_data = json.loads(validators_json) | ||
except json.JSONDecodeError as e: | ||
raise ValueError(f"Invalid JSON in validators_json: {str(e)}") | ||
|
||
if not isinstance(validators_data, list): | ||
raise ValueError("validators_json must contain a JSON array") | ||
|
||
# Delete all existing validators | ||
validators_registry.delete_all_validators() | ||
|
||
# Create new validators | ||
for validator_data in validators_data: | ||
try: | ||
validator = ValidatorConfig(**validator_data) | ||
|
||
for _ in range(validator.amount): | ||
validator_creator( | ||
validators_registry, | ||
accounts_manager, | ||
validator.stake, | ||
validator.provider, | ||
validator.model, | ||
validator.config, | ||
validator.plugin, | ||
validator.plugin_config, | ||
) | ||
|
||
except Exception as e: | ||
raise ValueError(f"Failed to create validator `{validator_data}`: {str(e)}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import pytest | ||
from unittest.mock import Mock | ||
from backend.protocol_rpc.validators_init import initialize_validators | ||
|
||
|
||
def test_initialize_validators_empty_json(): | ||
"""Test that empty JSON string returns without doing anything""" | ||
mock_registry = Mock() | ||
mock_accounts = Mock() | ||
mock_creator = Mock() | ||
|
||
initialize_validators("", mock_registry, mock_accounts, mock_creator) | ||
|
||
mock_registry.delete_all_validators.assert_not_called() | ||
mock_creator.assert_not_called() | ||
|
||
|
||
def test_initialize_validators_invalid_json(): | ||
"""Test that invalid JSON raises ValueError""" | ||
mock_registry = Mock() | ||
mock_accounts = Mock() | ||
mock_creator = Mock() | ||
|
||
with pytest.raises(ValueError, match="Invalid JSON"): | ||
initialize_validators( | ||
"{invalid json", mock_registry, mock_accounts, mock_creator | ||
) | ||
|
||
|
||
def test_initialize_validators_non_array_json(): | ||
"""Test that non-array JSON raises ValueError""" | ||
mock_registry = Mock() | ||
mock_accounts = Mock() | ||
mock_creator = Mock() | ||
|
||
with pytest.raises(ValueError, match="must contain a JSON array"): | ||
initialize_validators("{}", mock_registry, mock_accounts, mock_creator) | ||
|
||
|
||
def test_initialize_validators_success(): | ||
"""Test successful initialization of validators""" | ||
mock_registry = Mock() | ||
mock_accounts = Mock() | ||
mock_creator = Mock() | ||
|
||
validators_json = """[ | ||
{ | ||
"stake": 100, | ||
"provider": "test-provider", | ||
"model": "test-model", | ||
"config": {"key": "value"}, | ||
"plugin": "test-plugin", | ||
"plugin_config": {"plugin_key": "plugin_value"} | ||
}, | ||
{ | ||
"stake": 200, | ||
"provider": "another-provider", | ||
"model": "another-model", | ||
"amount": 2 | ||
} | ||
]""" | ||
|
||
initialize_validators(validators_json, mock_registry, mock_accounts, mock_creator) | ||
|
||
# Verify that existing validators were deleted | ||
mock_registry.delete_all_validators.assert_called_once() | ||
|
||
# Verify that creator was called for each validator with correct arguments | ||
assert mock_creator.call_count == 3 | ||
|
||
# Check first validator creation call | ||
mock_creator.assert_any_call( | ||
mock_registry, | ||
mock_accounts, | ||
100, | ||
"test-provider", | ||
"test-model", | ||
{"key": "value"}, | ||
"test-plugin", | ||
{"plugin_key": "plugin_value"}, | ||
) | ||
|
||
# Check second validator creation call | ||
mock_creator.assert_any_call( | ||
mock_registry, | ||
mock_accounts, | ||
200, | ||
"another-provider", | ||
"another-model", | ||
None, | ||
None, | ||
None, | ||
) | ||
|
||
mock_creator.assert_any_call( | ||
mock_registry, | ||
mock_accounts, | ||
200, | ||
"another-provider", | ||
"another-model", | ||
None, | ||
None, | ||
None, | ||
) | ||
|
||
|
||
def test_initialize_validators_invalid_config(): | ||
"""Test that invalid validator configuration raises ValueError""" | ||
mock_registry = Mock() | ||
mock_accounts = Mock() | ||
mock_creator = Mock() | ||
|
||
# Missing required field 'model' | ||
validators_json = """[ | ||
{ | ||
"stake": 100, | ||
"provider": "test-provider" | ||
} | ||
]""" | ||
|
||
with pytest.raises(ValueError, match="Failed to create validator"): | ||
initialize_validators( | ||
validators_json, mock_registry, mock_accounts, mock_creator | ||
) | ||
|
||
|
||
def test_initialize_validators_creator_error(): | ||
"""Test that creator function errors are properly handled""" | ||
mock_registry = Mock() | ||
mock_accounts = Mock() | ||
mock_creator = Mock(side_effect=Exception("Creator error")) | ||
|
||
validators_json = """[ | ||
{ | ||
"stake": 100, | ||
"provider": "test-provider", | ||
"model": "test-model" | ||
} | ||
]""" | ||
|
||
with pytest.raises(ValueError, match="Failed to create validator.*Creator error"): | ||
initialize_validators( | ||
validators_json, mock_registry, mock_accounts, mock_creator | ||
) |