flwr 1.22.0__py3-none-any.whl → 1.24.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +34 -1
- flwr/cli/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -0
- flwr/cli/auth_plugin/__init__.py +15 -6
- flwr/cli/auth_plugin/auth_plugin.py +94 -0
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
- flwr/cli/config_utils.py +101 -13
- flwr/cli/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +54 -11
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +177 -133
- flwr/cli/new/new.py +175 -40
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +12 -7
- flwr/cli/run/run.py +82 -31
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +27 -9
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +268 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/utils.py +464 -81
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +12 -15
- flwr/client/grpc_rere_client/connection.py +68 -41
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +94 -51
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → clientapp}/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/{client/clientapp → clientapp}/utils.py +4 -4
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +56 -13
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +39 -10
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +48 -31
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +6 -6
- flwr/common/record/arrayrecord.py +18 -21
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +9 -6
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +59 -43
- flwr/compat/client/app.py +39 -38
- flwr/compat/client/grpc_client/connection.py +13 -13
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +72 -40
- flwr/proto/control_pb2.pyi +319 -87
- flwr/proto/control_pb2_grpc.py +339 -28
- flwr/proto/control_pb2_grpc.pyi +209 -37
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +24 -10
- flwr/proto/fab_pb2.pyi +68 -20
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +45 -27
- flwr/proto/fleet_pb2.pyi +186 -70
- flwr/proto/fleet_pb2_grpc.py +277 -66
- flwr/proto/fleet_pb2_grpc.pyi +201 -55
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +16 -4
- flwr/proto/node_pb2.pyi +77 -4
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +149 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +173 -127
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +19 -8
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
- flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
- flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
- flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
- flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
- flwr/server/superlink/fleet/vce/vce_api.py +32 -13
- flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
- flwr/server/superlink/linkstate/linkstate.py +161 -62
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
- flwr/server/superlink/linkstate/utils.py +9 -60
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/utils/validator.py +2 -3
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +11 -12
- flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
- flwr/simulation/run_simulation.py +44 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +52 -0
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -6
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +27 -8
- flwr/supercore/object_store/sqlite_object_store.py +253 -0
- flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sqlite_mixin.py +159 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +20 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
- flwr/superlink/auth_plugin/auth_plugin.py +88 -0
- flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
- flwr/superlink/federation/__init__.py +24 -0
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +18 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +239 -63
- flwr/supernode/cli/flower_supernode.py +74 -26
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +175 -51
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/common/auth_plugin/auth_plugin.py +0 -149
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.22.0.dist-info/RECORD +0 -428
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
|
@@ -15,23 +15,26 @@
|
|
|
15
15
|
"""Main loop for Flower SuperNode."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
18
20
|
import os
|
|
19
21
|
import subprocess
|
|
20
22
|
import time
|
|
21
|
-
from collections.abc import Iterator
|
|
23
|
+
from collections.abc import Callable, Iterator
|
|
22
24
|
from contextlib import contextmanager
|
|
23
25
|
from functools import partial
|
|
24
|
-
from logging import INFO
|
|
26
|
+
from logging import ERROR, INFO, WARN
|
|
25
27
|
from pathlib import Path
|
|
26
|
-
from typing import
|
|
28
|
+
from typing import cast
|
|
27
29
|
|
|
28
30
|
import grpc
|
|
29
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
31
|
+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
|
|
32
|
+
from cryptography.hazmat.primitives.serialization.ssh import load_ssh_public_key
|
|
30
33
|
from grpc import RpcError
|
|
31
34
|
|
|
32
35
|
from flwr.client.grpc_adapter_client.connection import grpc_adapter
|
|
33
36
|
from flwr.client.grpc_rere_client.connection import grpc_request_response
|
|
34
|
-
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Message, RecordDict
|
|
37
|
+
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, Error, Message, RecordDict
|
|
35
38
|
from flwr.common.address import parse_address
|
|
36
39
|
from flwr.common.config import get_flwr_dir, get_fused_config_from_fab
|
|
37
40
|
from flwr.common.constant import (
|
|
@@ -41,11 +44,17 @@ from flwr.common.constant import (
|
|
|
41
44
|
TRANSPORT_TYPE_GRPC_RERE,
|
|
42
45
|
TRANSPORT_TYPE_REST,
|
|
43
46
|
TRANSPORT_TYPES,
|
|
47
|
+
ErrorCode,
|
|
44
48
|
ExecPluginType,
|
|
45
49
|
)
|
|
46
50
|
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
47
51
|
from flwr.common.grpc import generic_create_grpc_server
|
|
48
|
-
from flwr.common.inflatable import
|
|
52
|
+
from flwr.common.inflatable import (
|
|
53
|
+
get_all_nested_objects,
|
|
54
|
+
get_object_tree,
|
|
55
|
+
iterate_object_tree,
|
|
56
|
+
no_object_id_recompute,
|
|
57
|
+
)
|
|
49
58
|
from flwr.common.inflatable_utils import (
|
|
50
59
|
pull_objects,
|
|
51
60
|
push_object_contents_from_iterable,
|
|
@@ -54,16 +63,24 @@ from flwr.common.logger import log
|
|
|
54
63
|
from flwr.common.retry_invoker import RetryInvoker, _make_simple_grpc_retry_invoker
|
|
55
64
|
from flwr.common.telemetry import EventType
|
|
56
65
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
|
66
|
+
from flwr.common.version import package_version
|
|
57
67
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
|
58
68
|
from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
59
69
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
|
60
70
|
from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
|
|
61
71
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
72
|
+
from flwr.supercore.primitives.asymmetric_ed25519 import (
|
|
73
|
+
create_message_to_sign,
|
|
74
|
+
decode_base64url,
|
|
75
|
+
verify_signature,
|
|
76
|
+
)
|
|
62
77
|
from flwr.supernode.nodestate import NodeState, NodeStateFactory
|
|
63
78
|
from flwr.supernode.servicer.clientappio import ClientAppIoServicer
|
|
64
79
|
|
|
65
80
|
DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
|
|
66
81
|
|
|
82
|
+
FAB_VERIFICATION_ERROR = Error(ErrorCode.INVALID_FAB, "The FAB could not be verified.")
|
|
83
|
+
|
|
67
84
|
|
|
68
85
|
# pylint: disable=import-outside-toplevel
|
|
69
86
|
# pylint: disable=too-many-branches
|
|
@@ -74,18 +91,19 @@ def start_client_internal(
|
|
|
74
91
|
*,
|
|
75
92
|
server_address: str,
|
|
76
93
|
node_config: UserConfig,
|
|
77
|
-
root_certificates:
|
|
78
|
-
insecure:
|
|
94
|
+
root_certificates: bytes | str | None = None,
|
|
95
|
+
insecure: bool | None = None,
|
|
79
96
|
transport: str,
|
|
80
|
-
authentication_keys:
|
|
81
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
82
|
-
|
|
83
|
-
max_retries:
|
|
84
|
-
max_wait_time:
|
|
85
|
-
flwr_path:
|
|
97
|
+
authentication_keys: (
|
|
98
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
99
|
+
) = None,
|
|
100
|
+
max_retries: int | None = None,
|
|
101
|
+
max_wait_time: float | None = None,
|
|
102
|
+
flwr_path: Path | None = None,
|
|
86
103
|
isolation: str = ISOLATION_MODE_SUBPROCESS,
|
|
87
104
|
clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
88
|
-
health_server_address:
|
|
105
|
+
health_server_address: str | None = None,
|
|
106
|
+
trusted_entities: dict[str, str] | None = None,
|
|
89
107
|
) -> None:
|
|
90
108
|
"""Start a Flower client node which connects to a Flower server.
|
|
91
109
|
|
|
@@ -137,14 +155,31 @@ def start_client_internal(
|
|
|
137
155
|
health_server_address : Optional[str] (default: None)
|
|
138
156
|
The address of the health server. If `None` is provided, the health server will
|
|
139
157
|
NOT be started.
|
|
158
|
+
trusted_entities : Optional[dict[str, str]] (default: None)
|
|
159
|
+
A dictionary mapping public key IDs to public keys.
|
|
160
|
+
Only apps verified by at least one of these
|
|
161
|
+
entities can run on a supernode.
|
|
140
162
|
"""
|
|
141
163
|
if insecure is None:
|
|
142
164
|
insecure = root_certificates is None
|
|
143
165
|
|
|
166
|
+
# Insecure HTTP is incompatible with authentication
|
|
167
|
+
if insecure and authentication_keys is not None:
|
|
168
|
+
url_v = f"https://flower.ai/docs/framework/v{package_version}/en/"
|
|
169
|
+
page = "how-to-authenticate-supernodes.html"
|
|
170
|
+
flwr_exit(
|
|
171
|
+
ExitCode.SUPERNODE_STARTED_WITHOUT_TLS_BUT_NODE_AUTH_ENABLED,
|
|
172
|
+
"Insecure connection is enabled, but the SuperNode's private key is "
|
|
173
|
+
"provided for authentication. SuperNode authentication requires a "
|
|
174
|
+
"secure TLS connection with the SuperLink. Please enable TLS by "
|
|
175
|
+
"providing the certificate via `--root-certificates`. Please refer "
|
|
176
|
+
f"to the Flower documentation for more information: {url_v}{page}",
|
|
177
|
+
)
|
|
178
|
+
|
|
144
179
|
# Initialize factories
|
|
145
|
-
state_factory = NodeStateFactory()
|
|
146
|
-
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
|
147
180
|
object_store_factory = ObjectStoreFactory()
|
|
181
|
+
state_factory = NodeStateFactory(objectstore_factory=object_store_factory)
|
|
182
|
+
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
|
148
183
|
|
|
149
184
|
# Launch ClientAppIo API server
|
|
150
185
|
grpc_servers = []
|
|
@@ -193,22 +228,18 @@ def start_client_internal(
|
|
|
193
228
|
max_wait_time=max_wait_time,
|
|
194
229
|
) as conn:
|
|
195
230
|
(
|
|
231
|
+
node_id,
|
|
196
232
|
receive,
|
|
197
233
|
send,
|
|
198
|
-
create_node,
|
|
199
|
-
_,
|
|
200
234
|
get_run,
|
|
201
235
|
get_fab,
|
|
202
236
|
pull_object,
|
|
203
237
|
push_object,
|
|
204
238
|
confirm_message_received,
|
|
205
239
|
) = conn
|
|
206
|
-
|
|
207
|
-
# Call create_node fn to register node
|
|
208
|
-
# and store node_id in state
|
|
209
|
-
if (node_id := create_node()) is None:
|
|
210
|
-
raise ValueError("Failed to register SuperNode with the SuperLink")
|
|
240
|
+
# Store node_id in state
|
|
211
241
|
state.set_node_id(node_id)
|
|
242
|
+
log(INFO, "SuperNode ID: %s", node_id)
|
|
212
243
|
|
|
213
244
|
# pylint: disable=too-many-nested-blocks
|
|
214
245
|
while True:
|
|
@@ -224,6 +255,7 @@ def start_client_internal(
|
|
|
224
255
|
get_fab=get_fab,
|
|
225
256
|
pull_object=pull_object,
|
|
226
257
|
confirm_message_received=confirm_message_received,
|
|
258
|
+
trusted_entities=trusted_entities,
|
|
227
259
|
)
|
|
228
260
|
|
|
229
261
|
# No message has been pulled therefore we can skip the push stage.
|
|
@@ -240,17 +272,34 @@ def start_client_internal(
|
|
|
240
272
|
)
|
|
241
273
|
|
|
242
274
|
|
|
275
|
+
def _insert_message(msg: Message, state: NodeState, store: ObjectStore) -> None:
|
|
276
|
+
"""Insert a message into the NodeState and ObjectStore."""
|
|
277
|
+
with no_object_id_recompute():
|
|
278
|
+
# Store message in state
|
|
279
|
+
msg.metadata.__dict__["_message_id"] = msg.object_id # Set message_id
|
|
280
|
+
state.store_message(msg)
|
|
281
|
+
|
|
282
|
+
# Preregister objects in ObjectStore
|
|
283
|
+
store.preregister(msg.metadata.run_id, get_object_tree(msg))
|
|
284
|
+
|
|
285
|
+
# Store all objects in ObjectStore
|
|
286
|
+
all_objects = get_all_nested_objects(msg)
|
|
287
|
+
for obj_id, obj in all_objects.items():
|
|
288
|
+
store.put(obj_id, obj.deflate())
|
|
289
|
+
|
|
290
|
+
|
|
243
291
|
def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
244
292
|
state: NodeState,
|
|
245
293
|
ffs: Ffs,
|
|
246
294
|
object_store: ObjectStore,
|
|
247
295
|
node_config: UserConfig,
|
|
248
|
-
receive: Callable[[],
|
|
296
|
+
receive: Callable[[], tuple[Message, ObjectTree] | None],
|
|
249
297
|
get_run: Callable[[int], Run],
|
|
250
298
|
get_fab: Callable[[str, int], Fab],
|
|
251
299
|
pull_object: Callable[[int, str], bytes],
|
|
252
300
|
confirm_message_received: Callable[[int, str], None],
|
|
253
|
-
|
|
301
|
+
trusted_entities: dict[str, str] | None,
|
|
302
|
+
) -> int | None:
|
|
254
303
|
"""Pull a message from the SuperLink and store it in the state.
|
|
255
304
|
|
|
256
305
|
This function current returns None if no message is received,
|
|
@@ -258,6 +307,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
258
307
|
This behavior will change in the future to return None after
|
|
259
308
|
completing transition to the `NodeState`-based SuperNode.
|
|
260
309
|
"""
|
|
310
|
+
# pylint: disable=too-many-nested-blocks
|
|
261
311
|
message = None
|
|
262
312
|
try:
|
|
263
313
|
# Pull message
|
|
@@ -278,7 +328,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
278
328
|
log(INFO, "[RUN %s]", message.metadata.run_id)
|
|
279
329
|
log(
|
|
280
330
|
INFO,
|
|
281
|
-
"
|
|
331
|
+
"Receiving: %s message (ID: %s)",
|
|
282
332
|
message.metadata.message_type,
|
|
283
333
|
message.metadata.message_id,
|
|
284
334
|
)
|
|
@@ -290,11 +340,32 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
290
340
|
if (run_info := state.get_run(run_id)) is None:
|
|
291
341
|
# Pull run info from SuperLink
|
|
292
342
|
run_info = get_run(run_id)
|
|
293
|
-
state.store_run(run_info)
|
|
294
343
|
|
|
295
344
|
# Pull and store the FAB
|
|
296
345
|
fab = get_fab(run_info.fab_hash, run_id)
|
|
297
|
-
|
|
346
|
+
|
|
347
|
+
# Verify the received FAB
|
|
348
|
+
# FAB must be signed if trust entities provided
|
|
349
|
+
if trusted_entities:
|
|
350
|
+
if not fab.verifications.get("valid_license", ""):
|
|
351
|
+
log(
|
|
352
|
+
WARN,
|
|
353
|
+
"App verification is not supported by the connected SuperLink.",
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
fab_verified = _verify_fab(fab, trusted_entities)
|
|
357
|
+
if not fab_verified:
|
|
358
|
+
# Insert an error message in the state
|
|
359
|
+
# when FAB verification fails
|
|
360
|
+
log(
|
|
361
|
+
ERROR,
|
|
362
|
+
"FAB verification failed: the provided trusted entities "
|
|
363
|
+
"could not verify the FAB. An error reply "
|
|
364
|
+
"has been generated.",
|
|
365
|
+
)
|
|
366
|
+
reply = Message(FAB_VERIFICATION_ERROR, reply_to=message)
|
|
367
|
+
_insert_message(reply, state, object_store)
|
|
368
|
+
return run_id
|
|
298
369
|
|
|
299
370
|
# Initialize the context
|
|
300
371
|
run_cfg = get_fused_config_from_fab(fab.content, run_info)
|
|
@@ -305,7 +376,11 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
305
376
|
state=RecordDict(),
|
|
306
377
|
run_config=run_cfg,
|
|
307
378
|
)
|
|
379
|
+
|
|
380
|
+
# Store in the state
|
|
308
381
|
state.store_context(run_ctx)
|
|
382
|
+
state.store_run(run_info)
|
|
383
|
+
ffs.put(fab.content, fab.verifications)
|
|
309
384
|
|
|
310
385
|
# Preregister the object tree of the message
|
|
311
386
|
obj_ids_to_pull = object_store.preregister(run_id, object_tree)
|
|
@@ -313,16 +388,27 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
313
388
|
# Store the message in the state (note this message has no content)
|
|
314
389
|
state.store_message(message)
|
|
315
390
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
391
|
+
try:
|
|
392
|
+
# Pull and store objects of the message in the ObjectStore
|
|
393
|
+
obj_contents = pull_objects(
|
|
394
|
+
obj_ids_to_pull,
|
|
395
|
+
pull_object_fn=lambda obj_id: pull_object(run_id, obj_id),
|
|
396
|
+
)
|
|
397
|
+
for obj_id in list(obj_contents.keys()):
|
|
398
|
+
object_store.put(obj_id, obj_contents.pop(obj_id))
|
|
323
399
|
|
|
324
|
-
|
|
325
|
-
|
|
400
|
+
# Confirm that the message was received
|
|
401
|
+
confirm_message_received(run_id, message.metadata.message_id)
|
|
402
|
+
log(INFO, "Received successfully")
|
|
403
|
+
except Exception as err: # pylint: disable=broad-except
|
|
404
|
+
log(
|
|
405
|
+
ERROR,
|
|
406
|
+
"Failed to receive message %s: %s",
|
|
407
|
+
message.metadata.message_id,
|
|
408
|
+
err,
|
|
409
|
+
)
|
|
410
|
+
state.delete_messages(message_ids=[message.metadata.message_id])
|
|
411
|
+
object_store.delete(message.metadata.message_id)
|
|
326
412
|
|
|
327
413
|
except RunNotRunningException:
|
|
328
414
|
if message is None:
|
|
@@ -369,8 +455,9 @@ def _push_messages(
|
|
|
369
455
|
log(INFO, "[RUN %s]", message.metadata.run_id)
|
|
370
456
|
log(
|
|
371
457
|
INFO,
|
|
372
|
-
"Sending: %s message",
|
|
458
|
+
"Sending: %s message (ID: %s)",
|
|
373
459
|
message.metadata.message_type,
|
|
460
|
+
message.metadata.message_id,
|
|
374
461
|
)
|
|
375
462
|
|
|
376
463
|
# Get the object tree for the message
|
|
@@ -415,6 +502,13 @@ def _push_messages(
|
|
|
415
502
|
message.metadata.run_id,
|
|
416
503
|
message.metadata.message_id,
|
|
417
504
|
)
|
|
505
|
+
except Exception as err: # pylint: disable=broad-except
|
|
506
|
+
log(
|
|
507
|
+
ERROR,
|
|
508
|
+
"Failed to send message %s: %s",
|
|
509
|
+
message.metadata.message_id,
|
|
510
|
+
err,
|
|
511
|
+
)
|
|
418
512
|
finally:
|
|
419
513
|
# Delete the message from the state
|
|
420
514
|
state.delete_messages(
|
|
@@ -435,18 +529,17 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
|
435
529
|
transport: str,
|
|
436
530
|
server_address: str,
|
|
437
531
|
insecure: bool,
|
|
438
|
-
root_certificates:
|
|
439
|
-
authentication_keys:
|
|
440
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
441
|
-
|
|
442
|
-
max_retries:
|
|
443
|
-
max_wait_time:
|
|
532
|
+
root_certificates: bytes | str | None = None,
|
|
533
|
+
authentication_keys: (
|
|
534
|
+
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None
|
|
535
|
+
) = None,
|
|
536
|
+
max_retries: int | None = None,
|
|
537
|
+
max_wait_time: float | None = None,
|
|
444
538
|
) -> Iterator[
|
|
445
539
|
tuple[
|
|
446
|
-
|
|
540
|
+
int,
|
|
541
|
+
Callable[[], tuple[Message, ObjectTree] | None],
|
|
447
542
|
Callable[[Message, ObjectTree], set[str]],
|
|
448
|
-
Callable[[], Optional[int]],
|
|
449
|
-
Callable[[], None],
|
|
450
543
|
Callable[[int], Run],
|
|
451
544
|
Callable[[str, int], Fab],
|
|
452
545
|
Callable[[int, str], bytes],
|
|
@@ -505,8 +598,8 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
|
505
598
|
|
|
506
599
|
|
|
507
600
|
def _make_fleet_connection_retry_invoker(
|
|
508
|
-
max_retries:
|
|
509
|
-
max_wait_time:
|
|
601
|
+
max_retries: int | None = None,
|
|
602
|
+
max_wait_time: float | None = None,
|
|
510
603
|
connection_error_type: type[Exception] = RpcError,
|
|
511
604
|
) -> RetryInvoker:
|
|
512
605
|
"""Create a retry invoker for fleet connection."""
|
|
@@ -525,7 +618,7 @@ def run_clientappio_api_grpc(
|
|
|
525
618
|
state_factory: NodeStateFactory,
|
|
526
619
|
ffs_factory: FfsFactory,
|
|
527
620
|
objectstore_factory: ObjectStoreFactory,
|
|
528
|
-
certificates:
|
|
621
|
+
certificates: tuple[bytes, bytes, bytes] | None,
|
|
529
622
|
) -> grpc.Server:
|
|
530
623
|
"""Run ClientAppIo API gRPC server."""
|
|
531
624
|
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
|
@@ -546,3 +639,34 @@ def run_clientappio_api_grpc(
|
|
|
546
639
|
log(INFO, "Flower Deployment Runtime: Starting ClientAppIo API on %s", address)
|
|
547
640
|
clientappio_grpc_server.start()
|
|
548
641
|
return clientappio_grpc_server
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _verify_fab(fab: Fab, trusted_entities: dict[str, str]) -> bool:
|
|
645
|
+
"""Verify a FAB using its verification data and the provided trusted entities.
|
|
646
|
+
|
|
647
|
+
The FAB is considered verified if at least one trusted entity matches the
|
|
648
|
+
information contained in its verification records.
|
|
649
|
+
"""
|
|
650
|
+
verifications = fab.verifications
|
|
651
|
+
verif_full = {
|
|
652
|
+
k: json.loads(v) for k, v in verifications.items() if k != "valid_license"
|
|
653
|
+
}
|
|
654
|
+
fab_verified = False
|
|
655
|
+
for public_key_id, verif in verif_full.items():
|
|
656
|
+
if public_key_id in trusted_entities:
|
|
657
|
+
verifier_public_key = load_ssh_public_key(
|
|
658
|
+
trusted_entities[public_key_id].encode("utf-8")
|
|
659
|
+
)
|
|
660
|
+
message_to_verify = create_message_to_sign(
|
|
661
|
+
hashlib.sha256(fab.content).digest(),
|
|
662
|
+
verif["signed_at"],
|
|
663
|
+
)
|
|
664
|
+
assert isinstance(verifier_public_key, ed25519.Ed25519PublicKey)
|
|
665
|
+
if verify_signature(
|
|
666
|
+
verifier_public_key,
|
|
667
|
+
message_to_verify,
|
|
668
|
+
decode_base64url(verif["signature"]),
|
|
669
|
+
):
|
|
670
|
+
fab_verified = True
|
|
671
|
+
break
|
|
672
|
+
return fab_verified
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flwr
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.24.0
|
|
4
4
|
Summary: Flower: A Friendly Federated AI Framework
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: Artificial Intelligence,Federated AI,Federated Analytics,Federated Evaluation,Federated Learning,Flower,Machine Learning
|
|
7
7
|
Author: The Flower Authors
|
|
8
8
|
Author-email: hello@flower.ai
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -20,7 +20,6 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
24
23
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
24
|
Classifier: Topic :: Scientific/Engineering
|
|
26
25
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
@@ -33,21 +32,22 @@ Provides-Extra: rest
|
|
|
33
32
|
Provides-Extra: simulation
|
|
34
33
|
Requires-Dist: click (<8.2.0)
|
|
35
34
|
Requires-Dist: cryptography (>=44.0.1,<45.0.0)
|
|
36
|
-
Requires-Dist: grpcio (>=1.
|
|
37
|
-
Requires-Dist: grpcio-health-checking (>=1.
|
|
35
|
+
Requires-Dist: grpcio (>=1.70.0,<2.0.0)
|
|
36
|
+
Requires-Dist: grpcio-health-checking (>=1.70.0,<2.0.0)
|
|
38
37
|
Requires-Dist: iterators (>=0.0.2,<0.0.3)
|
|
39
38
|
Requires-Dist: numpy (>=1.26.0,<3.0.0)
|
|
40
39
|
Requires-Dist: pathspec (>=0.12.1,<0.13.0)
|
|
41
|
-
Requires-Dist: protobuf (>=
|
|
40
|
+
Requires-Dist: protobuf (>=5.28.0,<7.0.0)
|
|
42
41
|
Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
|
|
43
42
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
44
|
-
Requires-Dist: ray (==2.
|
|
43
|
+
Requires-Dist: ray (==2.51.1) ; (python_version >= "3.10" and python_version < "3.13") and (extra == "simulation")
|
|
44
|
+
Requires-Dist: ray (==2.51.1) ; (sys_platform != "win32" and python_version == "3.13") and (extra == "simulation")
|
|
45
45
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
46
46
|
Requires-Dist: rich (>=13.5.0,<14.0.0)
|
|
47
47
|
Requires-Dist: starlette (>=0.45.2,<0.46.0) ; extra == "rest"
|
|
48
48
|
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
|
49
49
|
Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
|
|
50
|
-
Requires-Dist: typer (>=0.12.5,<0.
|
|
50
|
+
Requires-Dist: typer (>=0.12.5,<0.21.0)
|
|
51
51
|
Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0) ; extra == "rest"
|
|
52
52
|
Project-URL: Documentation, https://flower.ai
|
|
53
53
|
Project-URL: Homepage, https://flower.ai
|