Source code for rizemind.authentication.mod

from typing import cast

from eth_typing import ChecksumAddress
from flwr.client.typing import ClientAppCallable
from flwr.common import (
    Context,
    Message,
)
from flwr.common.constant import MessageTypeLegacy
from flwr.common.recorddict_compat import (
    getpropertiesres_to_recorddict,
    recorddict_to_getpropertiesins,
)

from rizemind.authentication.config import AccountConfig
from rizemind.authentication.signatures.auth import sign_auth_message
from rizemind.authentication.train_auth import (
    parse_train_auth_ins,
    prepare_train_auth_res,
)
from rizemind.exception import ParseException, RizemindException
from rizemind.swarm.config import SwarmConfig


[docs] class NoAccountAuthenticationModError(RizemindException): """No account configuration is available in the context state.""" def __init__(self): super().__init__( code="no_account_config", message="AccountConfig cannot be found in context state.", )
[docs] class NoSwarmAuthenticationModError(RizemindException): """No swarm configuration is available in the context state.""" def __init__(self): super().__init__( code="no_swarm_config", message="SwarmConfig cannot be found in context state.", )
[docs] class WrongSwarmAuthenticationModError(RizemindException): """The requested swarm domain does not match the configured domain.""" def __init__(self, expected: ChecksumAddress | None, received: ChecksumAddress): super().__init__( code="wrong_swarm_domain", message=f"Swarm domain {received} does not match configured domain {expected}.", )
[docs] def authentication_mod( msg: Message, ctx: Context, call_next: ClientAppCallable, ) -> Message: """Handle authentication for GET_PROPERTIES messages. Invokes the next callable to populate the context, then, for messages carrying training-auth instructions, verifies the swarm domain and returns a signed authentication response. Args: msg: Incoming message to process. ctx: Flower context carrying client state. call_next: Next app callable to delegate to. Returns: The resulting message. If authentication applies, this is a GET_PROPERTIES response containing the signature; otherwise, the delegated reply. Raises: NoAccountAuthenticationModError: Account configuration is missing from context. NoSwarmAuthenticationModError: Swarm configuration is missing from context. WrongSwarmAuthenticationModError: Request domain differs from the configured swarm domain. """ # Weird behavior, but if you don't `call_next`, # then ctx won't contain values defined # in the `client_fn` reply = call_next(msg, ctx) if msg.metadata.message_type == MessageTypeLegacy.GET_PROPERTIES: account_config = AccountConfig.from_context(ctx) if account_config is None: raise NoAccountAuthenticationModError() account = account_config.get_account() get_properties_ins = recorddict_to_getpropertiesins(msg.content) try: train_auth_ins = parse_train_auth_ins(get_properties_ins) swarm_config = SwarmConfig.from_context( ctx, fallback_address=train_auth_ins.domain.verifyingContract ) if swarm_config is None: raise NoSwarmAuthenticationModError() if swarm_config.address != train_auth_ins.domain.verifyingContract: raise WrongSwarmAuthenticationModError( cast(ChecksumAddress, swarm_config.address), train_auth_ins.domain.verifyingContract, ) signature = sign_auth_message( account=account, round=train_auth_ins.round_id, nonce=train_auth_ins.nonce, domain=train_auth_ins.domain, ) res = prepare_train_auth_res(signature) return Message(getpropertiesres_to_recorddict(res), reply_to=msg) except ParseException: pass return reply