flwr 1.23.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 +19 -0
- 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/auth_plugin.py +4 -5
- flwr/cli/auth_plugin/noop_auth_plugin.py +54 -11
- flwr/cli/auth_plugin/oidc_cli_plugin.py +32 -9
- flwr/cli/build.py +60 -18
- flwr/cli/cli_account_auth_interceptor.py +24 -7
- 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 +52 -9
- flwr/cli/login/login.py +7 -4
- flwr/cli/ls.py +170 -130
- flwr/cli/new/new.py +33 -50
- 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 +10 -5
- flwr/cli/run/run.py +77 -30
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +25 -7
- flwr/cli/supernode/ls.py +16 -8
- flwr/cli/supernode/register.py +9 -4
- flwr/cli/supernode/unregister.py +5 -3
- flwr/cli/utils.py +376 -16
- flwr/client/__init__.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +6 -7
- flwr/client/grpc_rere_client/connection.py +10 -11
- flwr/client/grpc_rere_client/grpc_adapter.py +6 -2
- flwr/client/grpc_rere_client/node_auth_client_interceptor.py +2 -1
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +3 -3
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +12 -14
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/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/clientapp/utils.py +3 -3
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +5 -2
- 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 +19 -0
- 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 +38 -21
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +3 -3
- flwr/common/record/arrayrecord.py +18 -30
- 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/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +5 -4
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +52 -37
- flwr/compat/client/app.py +38 -37
- flwr/compat/client/grpc_client/connection.py +11 -11
- 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 +71 -52
- flwr/proto/control_pb2.pyi +277 -111
- flwr/proto/control_pb2_grpc.py +249 -40
- flwr/proto/control_pb2_grpc.pyi +185 -52
- 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 +14 -4
- flwr/proto/fab_pb2.pyi +59 -31
- 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 +14 -4
- flwr/proto/fleet_pb2.pyi +137 -61
- flwr/proto/fleet_pb2_grpc.py +189 -48
- flwr/proto/fleet_pb2_grpc.pyi +175 -61
- 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 +15 -5
- flwr/proto/node_pb2.pyi +50 -25
- 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 +38 -17
- 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 +2 -1
- 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 +4 -4
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +3 -2
- flwr/server/superlink/fleet/message_handler/message_handler.py +34 -28
- flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -2
- flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
- flwr/server/superlink/fleet/vce/backend/raybackend.py +5 -5
- flwr/server/superlink/fleet/vce/vce_api.py +15 -9
- flwr/server/superlink/linkstate/in_memory_linkstate.py +115 -150
- flwr/server/superlink/linkstate/linkstate.py +59 -43
- flwr/server/superlink/linkstate/linkstate_factory.py +22 -5
- flwr/server/superlink/linkstate/sqlite_linkstate.py +447 -438
- flwr/server/superlink/linkstate/utils.py +6 -6
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +26 -21
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +18 -13
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- 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 +8 -8
- 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 +10 -11
- flwr/simulation/ray_transport/ray_client_proxy.py +11 -12
- flwr/simulation/run_simulation.py +43 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +31 -1
- 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 -2
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +1 -2
- flwr/supercore/object_store/sqlite_object_store.py +8 -7
- flwr/supercore/primitives/asymmetric.py +1 -1
- flwr/supercore/primitives/asymmetric_ed25519.py +11 -1
- flwr/supercore/sqlite_mixin.py +37 -34
- 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/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/superlink/auth_plugin/auth_plugin.py +6 -9
- flwr/superlink/auth_plugin/noop_auth_plugin.py +6 -9
- 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_account_auth_interceptor.py +22 -13
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +5 -6
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +102 -18
- flwr/supernode/cli/flower_supernode.py +58 -3
- 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 +41 -22
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +158 -42
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.23.0.dist-info/RECORD +0 -439
- {flwr-1.23.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.23.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,
|
|
@@ -60,11 +69,18 @@ from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
|
|
|
60
69
|
from flwr.supercore.ffs import Ffs, FfsFactory
|
|
61
70
|
from flwr.supercore.grpc_health import run_health_server_grpc_no_tls
|
|
62
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
|
+
)
|
|
63
77
|
from flwr.supernode.nodestate import NodeState, NodeStateFactory
|
|
64
78
|
from flwr.supernode.servicer.clientappio import ClientAppIoServicer
|
|
65
79
|
|
|
66
80
|
DEFAULT_FFS_DIR = get_flwr_dir() / "supernode" / "ffs"
|
|
67
81
|
|
|
82
|
+
FAB_VERIFICATION_ERROR = Error(ErrorCode.INVALID_FAB, "The FAB could not be verified.")
|
|
83
|
+
|
|
68
84
|
|
|
69
85
|
# pylint: disable=import-outside-toplevel
|
|
70
86
|
# pylint: disable=too-many-branches
|
|
@@ -75,18 +91,19 @@ def start_client_internal(
|
|
|
75
91
|
*,
|
|
76
92
|
server_address: str,
|
|
77
93
|
node_config: UserConfig,
|
|
78
|
-
root_certificates:
|
|
79
|
-
insecure:
|
|
94
|
+
root_certificates: bytes | str | None = None,
|
|
95
|
+
insecure: bool | None = None,
|
|
80
96
|
transport: str,
|
|
81
|
-
authentication_keys:
|
|
82
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
83
|
-
|
|
84
|
-
max_retries:
|
|
85
|
-
max_wait_time:
|
|
86
|
-
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,
|
|
87
103
|
isolation: str = ISOLATION_MODE_SUBPROCESS,
|
|
88
104
|
clientappio_api_address: str = CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS,
|
|
89
|
-
health_server_address:
|
|
105
|
+
health_server_address: str | None = None,
|
|
106
|
+
trusted_entities: dict[str, str] | None = None,
|
|
90
107
|
) -> None:
|
|
91
108
|
"""Start a Flower client node which connects to a Flower server.
|
|
92
109
|
|
|
@@ -138,6 +155,10 @@ def start_client_internal(
|
|
|
138
155
|
health_server_address : Optional[str] (default: None)
|
|
139
156
|
The address of the health server. If `None` is provided, the health server will
|
|
140
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.
|
|
141
162
|
"""
|
|
142
163
|
if insecure is None:
|
|
143
164
|
insecure = root_certificates is None
|
|
@@ -156,9 +177,9 @@ def start_client_internal(
|
|
|
156
177
|
)
|
|
157
178
|
|
|
158
179
|
# Initialize factories
|
|
159
|
-
state_factory = NodeStateFactory()
|
|
160
|
-
ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
|
|
161
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
|
|
162
183
|
|
|
163
184
|
# Launch ClientAppIo API server
|
|
164
185
|
grpc_servers = []
|
|
@@ -218,6 +239,7 @@ def start_client_internal(
|
|
|
218
239
|
) = conn
|
|
219
240
|
# Store node_id in state
|
|
220
241
|
state.set_node_id(node_id)
|
|
242
|
+
log(INFO, "SuperNode ID: %s", node_id)
|
|
221
243
|
|
|
222
244
|
# pylint: disable=too-many-nested-blocks
|
|
223
245
|
while True:
|
|
@@ -233,6 +255,7 @@ def start_client_internal(
|
|
|
233
255
|
get_fab=get_fab,
|
|
234
256
|
pull_object=pull_object,
|
|
235
257
|
confirm_message_received=confirm_message_received,
|
|
258
|
+
trusted_entities=trusted_entities,
|
|
236
259
|
)
|
|
237
260
|
|
|
238
261
|
# No message has been pulled therefore we can skip the push stage.
|
|
@@ -249,17 +272,34 @@ def start_client_internal(
|
|
|
249
272
|
)
|
|
250
273
|
|
|
251
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
|
+
|
|
252
291
|
def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
253
292
|
state: NodeState,
|
|
254
293
|
ffs: Ffs,
|
|
255
294
|
object_store: ObjectStore,
|
|
256
295
|
node_config: UserConfig,
|
|
257
|
-
receive: Callable[[],
|
|
296
|
+
receive: Callable[[], tuple[Message, ObjectTree] | None],
|
|
258
297
|
get_run: Callable[[int], Run],
|
|
259
298
|
get_fab: Callable[[str, int], Fab],
|
|
260
299
|
pull_object: Callable[[int, str], bytes],
|
|
261
300
|
confirm_message_received: Callable[[int, str], None],
|
|
262
|
-
|
|
301
|
+
trusted_entities: dict[str, str] | None,
|
|
302
|
+
) -> int | None:
|
|
263
303
|
"""Pull a message from the SuperLink and store it in the state.
|
|
264
304
|
|
|
265
305
|
This function current returns None if no message is received,
|
|
@@ -267,6 +307,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
267
307
|
This behavior will change in the future to return None after
|
|
268
308
|
completing transition to the `NodeState`-based SuperNode.
|
|
269
309
|
"""
|
|
310
|
+
# pylint: disable=too-many-nested-blocks
|
|
270
311
|
message = None
|
|
271
312
|
try:
|
|
272
313
|
# Pull message
|
|
@@ -287,7 +328,7 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
287
328
|
log(INFO, "[RUN %s]", message.metadata.run_id)
|
|
288
329
|
log(
|
|
289
330
|
INFO,
|
|
290
|
-
"
|
|
331
|
+
"Receiving: %s message (ID: %s)",
|
|
291
332
|
message.metadata.message_type,
|
|
292
333
|
message.metadata.message_id,
|
|
293
334
|
)
|
|
@@ -299,11 +340,32 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
299
340
|
if (run_info := state.get_run(run_id)) is None:
|
|
300
341
|
# Pull run info from SuperLink
|
|
301
342
|
run_info = get_run(run_id)
|
|
302
|
-
state.store_run(run_info)
|
|
303
343
|
|
|
304
344
|
# Pull and store the FAB
|
|
305
345
|
fab = get_fab(run_info.fab_hash, run_id)
|
|
306
|
-
|
|
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
|
|
307
369
|
|
|
308
370
|
# Initialize the context
|
|
309
371
|
run_cfg = get_fused_config_from_fab(fab.content, run_info)
|
|
@@ -314,7 +376,11 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
314
376
|
state=RecordDict(),
|
|
315
377
|
run_config=run_cfg,
|
|
316
378
|
)
|
|
379
|
+
|
|
380
|
+
# Store in the state
|
|
317
381
|
state.store_context(run_ctx)
|
|
382
|
+
state.store_run(run_info)
|
|
383
|
+
ffs.put(fab.content, fab.verifications)
|
|
318
384
|
|
|
319
385
|
# Preregister the object tree of the message
|
|
320
386
|
obj_ids_to_pull = object_store.preregister(run_id, object_tree)
|
|
@@ -322,16 +388,27 @@ def _pull_and_store_message( # pylint: disable=too-many-positional-arguments
|
|
|
322
388
|
# Store the message in the state (note this message has no content)
|
|
323
389
|
state.store_message(message)
|
|
324
390
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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))
|
|
332
399
|
|
|
333
|
-
|
|
334
|
-
|
|
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)
|
|
335
412
|
|
|
336
413
|
except RunNotRunningException:
|
|
337
414
|
if message is None:
|
|
@@ -378,8 +455,9 @@ def _push_messages(
|
|
|
378
455
|
log(INFO, "[RUN %s]", message.metadata.run_id)
|
|
379
456
|
log(
|
|
380
457
|
INFO,
|
|
381
|
-
"Sending: %s message",
|
|
458
|
+
"Sending: %s message (ID: %s)",
|
|
382
459
|
message.metadata.message_type,
|
|
460
|
+
message.metadata.message_id,
|
|
383
461
|
)
|
|
384
462
|
|
|
385
463
|
# Get the object tree for the message
|
|
@@ -424,6 +502,13 @@ def _push_messages(
|
|
|
424
502
|
message.metadata.run_id,
|
|
425
503
|
message.metadata.message_id,
|
|
426
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
|
+
)
|
|
427
512
|
finally:
|
|
428
513
|
# Delete the message from the state
|
|
429
514
|
state.delete_messages(
|
|
@@ -444,16 +529,16 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
|
444
529
|
transport: str,
|
|
445
530
|
server_address: str,
|
|
446
531
|
insecure: bool,
|
|
447
|
-
root_certificates:
|
|
448
|
-
authentication_keys:
|
|
449
|
-
tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
|
|
450
|
-
|
|
451
|
-
max_retries:
|
|
452
|
-
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,
|
|
453
538
|
) -> Iterator[
|
|
454
539
|
tuple[
|
|
455
540
|
int,
|
|
456
|
-
Callable[[],
|
|
541
|
+
Callable[[], tuple[Message, ObjectTree] | None],
|
|
457
542
|
Callable[[Message, ObjectTree], set[str]],
|
|
458
543
|
Callable[[int], Run],
|
|
459
544
|
Callable[[str, int], Fab],
|
|
@@ -513,8 +598,8 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
|
|
|
513
598
|
|
|
514
599
|
|
|
515
600
|
def _make_fleet_connection_retry_invoker(
|
|
516
|
-
max_retries:
|
|
517
|
-
max_wait_time:
|
|
601
|
+
max_retries: int | None = None,
|
|
602
|
+
max_wait_time: float | None = None,
|
|
518
603
|
connection_error_type: type[Exception] = RpcError,
|
|
519
604
|
) -> RetryInvoker:
|
|
520
605
|
"""Create a retry invoker for fleet connection."""
|
|
@@ -533,7 +618,7 @@ def run_clientappio_api_grpc(
|
|
|
533
618
|
state_factory: NodeStateFactory,
|
|
534
619
|
ffs_factory: FfsFactory,
|
|
535
620
|
objectstore_factory: ObjectStoreFactory,
|
|
536
|
-
certificates:
|
|
621
|
+
certificates: tuple[bytes, bytes, bytes] | None,
|
|
537
622
|
) -> grpc.Server:
|
|
538
623
|
"""Run ClientAppIo API gRPC server."""
|
|
539
624
|
clientappio_servicer: grpc.Server = ClientAppIoServicer(
|
|
@@ -554,3 +639,34 @@ def run_clientappio_api_grpc(
|
|
|
554
639
|
log(INFO, "Flower Deployment Runtime: Starting ClientAppIo API on %s", address)
|
|
555
640
|
clientappio_grpc_server.start()
|
|
556
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
|