diff --git a/src/config/settings.py b/src/config/settings.py index 6cf2ad5..f4751fe 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -50,11 +50,12 @@ ssv_api_base_url = 'https://api.ssv.network/api/v4' ssv_api_timeout = 10 -remote_signer_url: str = config('remote_signer_url', default='') +remote_signer_url: str = config('REMOTE_SIGNER_URL', default='') remote_signer_timeout: int = config('REMOTE_SIGNER_TIMEOUT', cast=int, default=10) # validations +# Check OBOL_KEYSTORES_DIR if ( not remote_signer_url and cluster_type == OBOL @@ -63,6 +64,15 @@ ): raise RuntimeError('OBOL_KEYSTORES_DIR or OBOL_KEYSTORES_DIR_TEMPLATE must be set') +# Check cluster type for remote signer +if remote_signer_url and cluster_type != OBOL: + raise RuntimeError('Remote signer keystore is implemented for Obol only') + +# Check OBOL_CLUSTER_LOCK_FILE +if cluster_type == OBOL and not obol_cluster_lock_file: + raise RuntimeError('OBOL_CLUSTER_LOCK_FILE must be set') + +# Check SSV_OPERATOR_KEY_FILE if ( not remote_signer_url and cluster_type == SSV @@ -71,6 +81,7 @@ ): raise RuntimeError('SSV_OPERATOR_KEY_FILE or SSV_OPERATOR_KEY_FILE_TEMPLATE must be set') +# Check SSV_OPERATOR_PASSWORD_FILE if ( not remote_signer_url and cluster_type == SSV diff --git a/src/validators/keystores/load.py b/src/validators/keystores/load.py index f5152c9..cd0a193 100644 --- a/src/validators/keystores/load.py +++ b/src/validators/keystores/load.py @@ -6,7 +6,7 @@ from src.config.settings import OBOL, SSV from src.validators.keystores.base import BaseKeystore from src.validators.keystores.obol import ObolKeystore -from src.validators.keystores.remote import RemoteSignerKeystore +from src.validators.keystores.obol_remote import ObolRemoteKeystore from src.validators.keystores.ssv import SSVKeystore logger = cast(ExtendedLogger, logging.getLogger(__name__)) @@ -14,7 +14,7 @@ async def load_keystore() -> BaseKeystore: if settings.remote_signer_url: - remote_keystore = await RemoteSignerKeystore.load() + remote_keystore = await ObolRemoteKeystore.load() logger.info( 'Using remote signer at %s for %i public keys', settings.remote_signer_url, diff --git a/src/validators/keystores/remote.py b/src/validators/keystores/obol_remote.py similarity index 75% rename from src/validators/keystores/remote.py rename to src/validators/keystores/obol_remote.py index 66b98fa..1069a05 100644 --- a/src/validators/keystores/remote.py +++ b/src/validators/keystores/obol_remote.py @@ -1,4 +1,5 @@ import dataclasses +import json import logging from dataclasses import dataclass from typing import cast @@ -45,14 +46,43 @@ class VoluntaryExitRequestModel: voluntary_exit: VoluntaryExitMessage -class RemoteSignerKeystore(BaseKeystore): - def __init__(self, public_keys: list[HexStr]): +class ObolRemoteKeystore(BaseKeystore): + """ + Similar to RemoteKeystore from Stakewise Operator. + Also pubkey_to_share attribute is filled using cluster lock file. + """ + + def __init__(self, public_keys: list[HexStr], pubkey_to_share: dict[HexStr, HexStr]): self._public_keys = public_keys + self.pubkey_to_share = pubkey_to_share @staticmethod async def load() -> 'BaseKeystore': - public_keys = await RemoteSignerKeystore._get_remote_signer_public_keys() - return RemoteSignerKeystore(public_keys) + if settings.obol_node_index is None: + raise RuntimeError('OBOL_NODE_INDEX must be set') + + public_keys = await ObolRemoteKeystore._get_remote_signer_public_keys() + pubkey_to_share = ObolRemoteKeystore.get_pubkey_to_share(settings.obol_node_index) + return ObolRemoteKeystore(public_keys, pubkey_to_share) + + @staticmethod + def load_cluster_lock() -> dict: + if not settings.obol_cluster_lock_file: + raise RuntimeError('OBOL_CLUSTER_LOCK_FILE must be set') + + return json.load(open(settings.obol_cluster_lock_file, encoding='ascii')) + + @staticmethod + def get_pubkey_to_share(node_index: int) -> dict[HexStr, HexStr]: + cluster_lock = ObolRemoteKeystore.load_cluster_lock() + + pub_key_to_share = {} + for dv in cluster_lock['distributed_validators']: + public_key = dv['distributed_public_key'] + public_key_share = dv['public_shares'][node_index] + pub_key_to_share[public_key] = public_key_share + + return pub_key_to_share def __bool__(self) -> bool: return bool(self._public_keys)