flwr 1.16.0__py3-none-any.whl → 1.18.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 +1 -1
- flwr/cli/__init__.py +1 -1
- flwr/cli/app.py +21 -2
- flwr/cli/build.py +1 -1
- flwr/cli/cli_user_auth_interceptor.py +1 -1
- flwr/cli/config_utils.py +53 -17
- flwr/cli/example.py +1 -1
- flwr/cli/install.py +1 -1
- flwr/cli/log.py +1 -1
- flwr/cli/login/__init__.py +1 -1
- flwr/cli/login/login.py +12 -1
- flwr/cli/ls.py +1 -1
- flwr/cli/new/__init__.py +1 -1
- flwr/cli/new/new.py +4 -4
- flwr/cli/new/templates/__init__.py +1 -1
- flwr/cli/new/templates/app/__init__.py +1 -1
- flwr/cli/new/templates/app/code/__init__.py +1 -1
- flwr/cli/new/templates/app/code/flwr_tune/__init__.py +1 -1
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +5 -5
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +4 -4
- 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 +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/__init__.py +1 -1
- flwr/cli/run/run.py +6 -10
- flwr/cli/stop.py +1 -1
- flwr/cli/utils.py +11 -12
- flwr/client/__init__.py +1 -1
- flwr/client/app.py +58 -56
- flwr/client/client.py +1 -1
- flwr/client/client_app.py +231 -166
- flwr/client/clientapp/__init__.py +1 -1
- flwr/client/clientapp/app.py +3 -3
- flwr/client/clientapp/clientappio_servicer.py +1 -1
- flwr/client/clientapp/utils.py +1 -1
- flwr/client/dpfedavg_numpy_client.py +1 -1
- flwr/client/grpc_adapter_client/__init__.py +1 -1
- flwr/client/grpc_adapter_client/connection.py +1 -1
- flwr/client/grpc_client/__init__.py +1 -1
- flwr/client/grpc_client/connection.py +37 -34
- flwr/client/grpc_rere_client/__init__.py +1 -1
- flwr/client/grpc_rere_client/client_interceptor.py +1 -1
- flwr/client/grpc_rere_client/connection.py +1 -1
- flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
- flwr/client/heartbeat.py +1 -1
- flwr/client/message_handler/__init__.py +1 -1
- flwr/client/message_handler/message_handler.py +28 -28
- flwr/client/mod/__init__.py +3 -3
- flwr/client/mod/centraldp_mods.py +8 -8
- flwr/client/mod/comms_mods.py +17 -23
- flwr/client/mod/localdp_mod.py +10 -10
- flwr/client/mod/secure_aggregation/__init__.py +1 -1
- flwr/client/mod/secure_aggregation/secagg_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +32 -32
- flwr/client/mod/utils.py +1 -1
- flwr/client/nodestate/__init__.py +1 -1
- flwr/client/nodestate/in_memory_nodestate.py +1 -1
- flwr/client/nodestate/nodestate.py +1 -1
- flwr/client/nodestate/nodestate_factory.py +1 -1
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/__init__.py +1 -1
- flwr/client/rest_client/connection.py +1 -1
- flwr/client/run_info_store.py +3 -3
- flwr/client/supernode/__init__.py +1 -1
- flwr/client/supernode/app.py +1 -1
- flwr/client/typing.py +1 -1
- flwr/common/__init__.py +13 -5
- flwr/common/address.py +1 -1
- flwr/common/args.py +1 -1
- flwr/common/auth_plugin/__init__.py +1 -1
- flwr/common/auth_plugin/auth_plugin.py +1 -1
- flwr/common/config.py +5 -5
- flwr/common/constant.py +7 -7
- flwr/common/context.py +5 -5
- flwr/common/date.py +1 -1
- flwr/common/differential_privacy.py +1 -1
- flwr/common/differential_privacy_constants.py +1 -1
- flwr/common/dp.py +1 -1
- flwr/common/event_log_plugin/event_log_plugin.py +3 -3
- flwr/common/exit/exit.py +6 -6
- flwr/common/exit_handlers.py +1 -1
- flwr/common/grpc.py +1 -1
- flwr/common/logger.py +3 -3
- flwr/common/message.py +344 -102
- flwr/common/object_ref.py +1 -1
- flwr/common/parameter.py +1 -1
- flwr/common/pyproject.py +1 -1
- flwr/common/record/__init__.py +9 -5
- flwr/common/record/arrayrecord.py +626 -0
- flwr/common/record/{configsrecord.py → configrecord.py} +83 -37
- flwr/common/record/conversion_utils.py +2 -2
- flwr/common/record/{metricsrecord.py → metricrecord.py} +90 -44
- flwr/common/record/recorddict.py +337 -0
- flwr/common/record/typeddict.py +1 -1
- flwr/common/recorddict_compat.py +410 -0
- flwr/common/retry_invoker.py +10 -10
- flwr/common/secure_aggregation/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/__init__.py +1 -1
- flwr/common/secure_aggregation/crypto/shamir.py +52 -30
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -1
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +1 -1
- flwr/common/secure_aggregation/quantization.py +1 -1
- flwr/common/secure_aggregation/secaggplus_constants.py +2 -2
- flwr/common/secure_aggregation/secaggplus_utils.py +1 -1
- flwr/common/serde.py +67 -72
- flwr/common/telemetry.py +2 -2
- flwr/common/typing.py +9 -9
- flwr/common/version.py +1 -1
- flwr/proto/__init__.py +1 -1
- flwr/proto/exec_pb2.py +3 -3
- flwr/proto/exec_pb2.pyi +3 -3
- flwr/proto/message_pb2.py +12 -12
- flwr/proto/message_pb2.pyi +9 -9
- flwr/proto/recorddict_pb2.py +70 -0
- flwr/proto/{recordset_pb2.pyi → recorddict_pb2.pyi} +35 -35
- flwr/proto/run_pb2.py +31 -31
- flwr/proto/run_pb2.pyi +3 -3
- flwr/server/__init__.py +4 -2
- flwr/server/app.py +67 -12
- flwr/server/client_manager.py +1 -1
- flwr/server/client_proxy.py +1 -1
- flwr/server/compat/__init__.py +3 -3
- flwr/server/compat/app.py +12 -12
- flwr/server/compat/app_utils.py +17 -17
- flwr/server/compat/{driver_client_proxy.py → grid_client_proxy.py} +39 -39
- flwr/server/compat/legacy_context.py +1 -1
- flwr/server/criterion.py +1 -1
- flwr/server/fleet_event_log_interceptor.py +94 -0
- flwr/server/{driver → grid}/__init__.py +8 -7
- flwr/server/{driver/driver.py → grid/grid.py} +48 -19
- flwr/server/{driver/grpc_driver.py → grid/grpc_grid.py} +87 -64
- flwr/server/{driver/inmemory_driver.py → grid/inmemory_grid.py} +24 -34
- flwr/server/history.py +1 -1
- flwr/server/run_serverapp.py +5 -5
- flwr/server/server.py +1 -1
- flwr/server/server_app.py +98 -71
- flwr/server/server_config.py +1 -1
- flwr/server/serverapp/__init__.py +1 -1
- flwr/server/serverapp/app.py +11 -11
- flwr/server/serverapp_components.py +1 -1
- flwr/server/strategy/__init__.py +1 -1
- flwr/server/strategy/aggregate.py +1 -1
- flwr/server/strategy/bulyan.py +2 -2
- flwr/server/strategy/dp_adaptive_clipping.py +17 -17
- flwr/server/strategy/dp_fixed_clipping.py +17 -17
- flwr/server/strategy/dpfedavg_adaptive.py +1 -1
- flwr/server/strategy/dpfedavg_fixed.py +1 -1
- flwr/server/strategy/fault_tolerant_fedavg.py +1 -1
- flwr/server/strategy/fedadagrad.py +1 -1
- flwr/server/strategy/fedadam.py +1 -1
- flwr/server/strategy/fedavg.py +1 -1
- flwr/server/strategy/fedavg_android.py +1 -1
- flwr/server/strategy/fedavgm.py +1 -1
- flwr/server/strategy/fedmedian.py +1 -1
- flwr/server/strategy/fedopt.py +1 -1
- flwr/server/strategy/fedprox.py +1 -1
- flwr/server/strategy/fedtrimmedavg.py +1 -1
- flwr/server/strategy/fedxgb_bagging.py +1 -1
- flwr/server/strategy/fedxgb_cyclic.py +1 -1
- flwr/server/strategy/fedxgb_nn_avg.py +3 -2
- flwr/server/strategy/fedyogi.py +1 -1
- flwr/server/strategy/krum.py +1 -1
- flwr/server/strategy/qfedavg.py +1 -1
- flwr/server/strategy/strategy.py +1 -1
- flwr/server/superlink/__init__.py +1 -1
- flwr/server/superlink/ffs/__init__.py +1 -1
- flwr/server/superlink/ffs/disk_ffs.py +1 -1
- flwr/server/superlink/ffs/ffs.py +1 -1
- flwr/server/superlink/ffs/ffs_factory.py +1 -1
- flwr/server/superlink/fleet/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +1 -1
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +13 -13
- flwr/server/superlink/fleet/grpc_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +1 -1
- flwr/server/superlink/fleet/message_handler/__init__.py +1 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +1 -1
- flwr/server/superlink/fleet/rest_rere/__init__.py +1 -1
- flwr/server/superlink/fleet/rest_rere/rest_api.py +1 -1
- flwr/server/superlink/fleet/vce/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/__init__.py +1 -1
- flwr/server/superlink/fleet/vce/backend/backend.py +3 -3
- flwr/server/superlink/fleet/vce/backend/raybackend.py +3 -3
- flwr/server/superlink/fleet/vce/vce_api.py +2 -4
- flwr/server/superlink/linkstate/__init__.py +1 -1
- flwr/server/superlink/linkstate/in_memory_linkstate.py +34 -9
- flwr/server/superlink/linkstate/linkstate.py +5 -5
- flwr/server/superlink/linkstate/linkstate_factory.py +1 -1
- flwr/server/superlink/linkstate/sqlite_linkstate.py +62 -28
- flwr/server/superlink/linkstate/utils.py +94 -28
- flwr/server/superlink/{driver → serverappio}/__init__.py +1 -1
- flwr/server/superlink/{driver → serverappio}/serverappio_grpc.py +1 -1
- flwr/server/superlink/{driver → serverappio}/serverappio_servicer.py +4 -4
- flwr/server/superlink/simulation/__init__.py +1 -1
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -1
- flwr/server/superlink/simulation/simulationio_servicer.py +3 -3
- flwr/server/superlink/utils.py +1 -1
- flwr/server/typing.py +4 -4
- flwr/server/utils/__init__.py +1 -1
- flwr/server/utils/tensorboard.py +1 -1
- flwr/server/utils/validator.py +5 -5
- flwr/server/workflow/__init__.py +1 -1
- flwr/server/workflow/constant.py +1 -1
- flwr/server/workflow/default_workflows.py +49 -58
- flwr/server/workflow/secure_aggregation/__init__.py +1 -1
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +1 -1
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +49 -51
- flwr/simulation/__init__.py +1 -1
- flwr/simulation/app.py +3 -3
- flwr/simulation/legacy_app.py +1 -1
- flwr/simulation/ray_transport/__init__.py +1 -1
- flwr/simulation/ray_transport/ray_actor.py +5 -3
- flwr/simulation/ray_transport/ray_client_proxy.py +35 -33
- flwr/simulation/ray_transport/utils.py +1 -1
- flwr/simulation/run_simulation.py +17 -17
- flwr/simulation/simulationio_connection.py +1 -1
- flwr/superexec/__init__.py +1 -1
- flwr/superexec/app.py +1 -1
- flwr/superexec/deployment.py +5 -5
- flwr/superexec/exec_event_log_interceptor.py +135 -0
- flwr/superexec/exec_grpc.py +11 -5
- flwr/superexec/exec_servicer.py +3 -3
- flwr/superexec/exec_user_auth_interceptor.py +19 -3
- flwr/superexec/executor.py +4 -4
- flwr/superexec/simulation.py +4 -4
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/METADATA +3 -3
- flwr-1.18.0.dist-info/RECORD +332 -0
- flwr/common/record/parametersrecord.py +0 -339
- flwr/common/record/recordset.py +0 -209
- flwr/common/recordset_compat.py +0 -418
- flwr/proto/recordset_pb2.py +0 -70
- flwr-1.16.0.dist-info/LICENSE +0 -202
- flwr-1.16.0.dist-info/RECORD +0 -331
- /flwr/proto/{recordset_pb2_grpc.py → recorddict_pb2_grpc.py} +0 -0
- /flwr/proto/{recordset_pb2_grpc.pyi → recorddict_pb2_grpc.pyi} +0 -0
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/WHEEL +0 -0
- {flwr-1.16.0.dist-info → flwr-1.18.0.dist-info}/entry_points.txt +0 -0
flwr/server/superlink/ffs/ffs.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -96,18 +96,18 @@ def start_grpc_server( # pylint: disable=too-many-arguments,R0917
|
|
|
96
96
|
|
|
97
97
|
Examples
|
|
98
98
|
--------
|
|
99
|
-
Starting a
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
99
|
+
Starting a TLS-enabled server::
|
|
100
|
+
|
|
101
|
+
from pathlib import Path
|
|
102
|
+
start_grpc_server(
|
|
103
|
+
client_manager=ClientManager(),
|
|
104
|
+
server_address="localhost:8080",
|
|
105
|
+
certificates=(
|
|
106
|
+
Path("/crts/root.pem").read_bytes(),
|
|
107
|
+
Path("/crts/localhost.crt").read_bytes(),
|
|
108
|
+
Path("/crts/localhost.key").read_bytes(),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
111
|
"""
|
|
112
112
|
servicer = FlowerServiceServicer(client_manager)
|
|
113
113
|
add_servicer_to_server_fn = add_FlowerServiceServicer_to_server
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -21,9 +21,9 @@ from typing import Callable
|
|
|
21
21
|
from flwr.client.client_app import ClientApp
|
|
22
22
|
from flwr.common.context import Context
|
|
23
23
|
from flwr.common.message import Message
|
|
24
|
-
from flwr.common.typing import
|
|
24
|
+
from flwr.common.typing import ConfigRecordValues
|
|
25
25
|
|
|
26
|
-
BackendConfig = dict[str, dict[str,
|
|
26
|
+
BackendConfig = dict[str, dict[str, ConfigRecordValues]]
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class Backend(ABC):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -26,7 +26,7 @@ from flwr.common.constant import PARTITION_ID_KEY
|
|
|
26
26
|
from flwr.common.context import Context
|
|
27
27
|
from flwr.common.logger import log
|
|
28
28
|
from flwr.common.message import Message
|
|
29
|
-
from flwr.common.typing import
|
|
29
|
+
from flwr.common.typing import ConfigRecordValues
|
|
30
30
|
from flwr.simulation.ray_transport.ray_actor import BasicActorPool, ClientAppActor
|
|
31
31
|
from flwr.simulation.ray_transport.utils import enable_tf_gpu_growth
|
|
32
32
|
|
|
@@ -104,7 +104,7 @@ class RayBackend(Backend):
|
|
|
104
104
|
if not ray.is_initialized():
|
|
105
105
|
ray_init_args: dict[
|
|
106
106
|
str,
|
|
107
|
-
|
|
107
|
+
ConfigRecordValues,
|
|
108
108
|
] = {}
|
|
109
109
|
|
|
110
110
|
if backend_config.get(self.init_args_key):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -130,9 +130,7 @@ def worker(
|
|
|
130
130
|
e_code = ErrorCode.UNKNOWN
|
|
131
131
|
|
|
132
132
|
reason = str(type(ex)) + ":<'" + str(ex) + "'>"
|
|
133
|
-
out_mssg = message
|
|
134
|
-
error=Error(code=e_code, reason=reason)
|
|
135
|
-
)
|
|
133
|
+
out_mssg = Message(Error(code=e_code, reason=reason), reply_to=message)
|
|
136
134
|
|
|
137
135
|
finally:
|
|
138
136
|
if out_mssg:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -27,16 +27,18 @@ from flwr.common import Context, Message, log, now
|
|
|
27
27
|
from flwr.common.constant import (
|
|
28
28
|
MESSAGE_TTL_TOLERANCE,
|
|
29
29
|
NODE_ID_NUM_BYTES,
|
|
30
|
+
PING_PATIENCE,
|
|
30
31
|
RUN_ID_NUM_BYTES,
|
|
31
32
|
SUPERLINK_NODE_ID,
|
|
32
33
|
Status,
|
|
33
34
|
)
|
|
34
|
-
from flwr.common.record import
|
|
35
|
+
from flwr.common.record import ConfigRecord
|
|
35
36
|
from flwr.common.typing import Run, RunStatus, UserConfig
|
|
36
37
|
from flwr.server.superlink.linkstate.linkstate import LinkState
|
|
37
38
|
from flwr.server.utils import validate_message
|
|
38
39
|
|
|
39
40
|
from .utils import (
|
|
41
|
+
check_node_availability_for_in_message,
|
|
40
42
|
generate_rand_int_from_bytes,
|
|
41
43
|
has_valid_sub_status,
|
|
42
44
|
is_valid_transition,
|
|
@@ -67,7 +69,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
67
69
|
# Map run_id to RunRecord
|
|
68
70
|
self.run_ids: dict[int, RunRecord] = {}
|
|
69
71
|
self.contexts: dict[int, Context] = {}
|
|
70
|
-
self.federation_options: dict[int,
|
|
72
|
+
self.federation_options: dict[int, ConfigRecord] = {}
|
|
71
73
|
self.message_ins_store: dict[UUID, Message] = {}
|
|
72
74
|
self.message_res_store: dict[UUID, Message] = {}
|
|
73
75
|
self.message_ins_id_to_message_res_id: dict[UUID, UUID] = {}
|
|
@@ -156,7 +158,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
156
158
|
res_metadata = message.metadata
|
|
157
159
|
with self.lock:
|
|
158
160
|
# Check if the Message it is replying to exists and is valid
|
|
159
|
-
msg_ins_id = res_metadata.
|
|
161
|
+
msg_ins_id = res_metadata.reply_to_message_id
|
|
160
162
|
msg_ins = self.message_ins_store.get(UUID(msg_ins_id))
|
|
161
163
|
|
|
162
164
|
# Ensure that dst_node_id of original Message matches the src_node_id of
|
|
@@ -232,13 +234,28 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
232
234
|
with self.lock:
|
|
233
235
|
current = time.time()
|
|
234
236
|
|
|
235
|
-
# Verify
|
|
237
|
+
# Verify Message IDs
|
|
236
238
|
ret = verify_message_ids(
|
|
237
239
|
inquired_message_ids=message_ids,
|
|
238
240
|
found_message_ins_dict=self.message_ins_store,
|
|
239
241
|
current_time=current,
|
|
240
242
|
)
|
|
241
243
|
|
|
244
|
+
# Check node availability
|
|
245
|
+
dst_node_ids = {
|
|
246
|
+
self.message_ins_store[message_id].metadata.dst_node_id
|
|
247
|
+
for message_id in message_ids
|
|
248
|
+
}
|
|
249
|
+
tmp_ret_dict = check_node_availability_for_in_message(
|
|
250
|
+
inquired_in_message_ids=message_ids,
|
|
251
|
+
found_in_message_dict=self.message_ins_store,
|
|
252
|
+
node_id_to_online_until={
|
|
253
|
+
node_id: self.node_ids[node_id][0] for node_id in dst_node_ids
|
|
254
|
+
},
|
|
255
|
+
current_time=current,
|
|
256
|
+
)
|
|
257
|
+
ret.update(tmp_ret_dict)
|
|
258
|
+
|
|
242
259
|
# Find all reply Messages
|
|
243
260
|
message_res_found: list[Message] = []
|
|
244
261
|
for message_id in message_ids:
|
|
@@ -317,6 +334,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
317
334
|
log(ERROR, "Unexpected node registration failure.")
|
|
318
335
|
return 0
|
|
319
336
|
|
|
337
|
+
# Mark the node online util time.time() + ping_interval
|
|
320
338
|
self.node_ids[node_id] = (time.time() + ping_interval, ping_interval)
|
|
321
339
|
return node_id
|
|
322
340
|
|
|
@@ -381,7 +399,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
381
399
|
fab_version: Optional[str],
|
|
382
400
|
fab_hash: Optional[str],
|
|
383
401
|
override_config: UserConfig,
|
|
384
|
-
federation_options:
|
|
402
|
+
federation_options: ConfigRecord,
|
|
385
403
|
) -> int:
|
|
386
404
|
"""Create a new run for the specified `fab_hash`."""
|
|
387
405
|
# Sample a random int64 as run_id
|
|
@@ -510,7 +528,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
510
528
|
|
|
511
529
|
return pending_run_id
|
|
512
530
|
|
|
513
|
-
def get_federation_options(self, run_id: int) -> Optional[
|
|
531
|
+
def get_federation_options(self, run_id: int) -> Optional[ConfigRecord]:
|
|
514
532
|
"""Retrieve the federation options for the specified `run_id`."""
|
|
515
533
|
with self.lock:
|
|
516
534
|
if run_id not in self.run_ids:
|
|
@@ -519,10 +537,17 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
519
537
|
return self.federation_options[run_id]
|
|
520
538
|
|
|
521
539
|
def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
|
|
522
|
-
"""Acknowledge a ping received from a node, serving as a heartbeat.
|
|
540
|
+
"""Acknowledge a ping received from a node, serving as a heartbeat.
|
|
541
|
+
|
|
542
|
+
It allows for one missed ping (in a PING_PATIENCE * ping_interval) before
|
|
543
|
+
marking the node as offline, where PING_PATIENCE = 2 in default.
|
|
544
|
+
"""
|
|
523
545
|
with self.lock:
|
|
524
546
|
if node_id in self.node_ids:
|
|
525
|
-
self.node_ids[node_id] = (
|
|
547
|
+
self.node_ids[node_id] = (
|
|
548
|
+
time.time() + PING_PATIENCE * ping_interval,
|
|
549
|
+
ping_interval,
|
|
550
|
+
)
|
|
526
551
|
return True
|
|
527
552
|
return False
|
|
528
553
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -20,7 +20,7 @@ from typing import Optional
|
|
|
20
20
|
from uuid import UUID
|
|
21
21
|
|
|
22
22
|
from flwr.common import Context, Message
|
|
23
|
-
from flwr.common.record import
|
|
23
|
+
from flwr.common.record import ConfigRecord
|
|
24
24
|
from flwr.common.typing import Run, RunStatus, UserConfig
|
|
25
25
|
|
|
26
26
|
|
|
@@ -164,7 +164,7 @@ class LinkState(abc.ABC): # pylint: disable=R0904
|
|
|
164
164
|
fab_version: Optional[str],
|
|
165
165
|
fab_hash: Optional[str],
|
|
166
166
|
override_config: UserConfig,
|
|
167
|
-
federation_options:
|
|
167
|
+
federation_options: ConfigRecord,
|
|
168
168
|
) -> int:
|
|
169
169
|
"""Create a new run for the specified `fab_hash`."""
|
|
170
170
|
|
|
@@ -236,7 +236,7 @@ class LinkState(abc.ABC): # pylint: disable=R0904
|
|
|
236
236
|
"""
|
|
237
237
|
|
|
238
238
|
@abc.abstractmethod
|
|
239
|
-
def get_federation_options(self, run_id: int) -> Optional[
|
|
239
|
+
def get_federation_options(self, run_id: int) -> Optional[ConfigRecord]:
|
|
240
240
|
"""Retrieve the federation options for the specified `run_id`.
|
|
241
241
|
|
|
242
242
|
Parameters
|
|
@@ -246,7 +246,7 @@ class LinkState(abc.ABC): # pylint: disable=R0904
|
|
|
246
246
|
|
|
247
247
|
Returns
|
|
248
248
|
-------
|
|
249
|
-
Optional[
|
|
249
|
+
Optional[ConfigRecord]
|
|
250
250
|
The federation options for the run if it exists; None otherwise.
|
|
251
251
|
"""
|
|
252
252
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -30,30 +30,33 @@ from flwr.common import Context, Message, Metadata, log, now
|
|
|
30
30
|
from flwr.common.constant import (
|
|
31
31
|
MESSAGE_TTL_TOLERANCE,
|
|
32
32
|
NODE_ID_NUM_BYTES,
|
|
33
|
+
PING_PATIENCE,
|
|
33
34
|
RUN_ID_NUM_BYTES,
|
|
34
35
|
SUPERLINK_NODE_ID,
|
|
35
36
|
Status,
|
|
36
37
|
)
|
|
37
|
-
from flwr.common.
|
|
38
|
+
from flwr.common.message import make_message
|
|
39
|
+
from flwr.common.record import ConfigRecord
|
|
38
40
|
from flwr.common.serde import (
|
|
39
41
|
error_from_proto,
|
|
40
42
|
error_to_proto,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
recorddict_from_proto,
|
|
44
|
+
recorddict_to_proto,
|
|
43
45
|
)
|
|
44
46
|
from flwr.common.typing import Run, RunStatus, UserConfig
|
|
45
47
|
|
|
46
48
|
# pylint: disable=E0611
|
|
47
49
|
from flwr.proto.error_pb2 import Error as ProtoError
|
|
48
|
-
from flwr.proto.
|
|
50
|
+
from flwr.proto.recorddict_pb2 import RecordDict as ProtoRecordDict
|
|
49
51
|
|
|
50
52
|
# pylint: enable=E0611
|
|
51
53
|
from flwr.server.utils.validator import validate_message
|
|
52
54
|
|
|
53
55
|
from .linkstate import LinkState
|
|
54
56
|
from .utils import (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
check_node_availability_for_in_message,
|
|
58
|
+
configrecord_from_bytes,
|
|
59
|
+
configrecord_to_bytes,
|
|
57
60
|
context_from_bytes,
|
|
58
61
|
context_to_bytes,
|
|
59
62
|
convert_sint64_to_uint64,
|
|
@@ -129,7 +132,7 @@ CREATE TABLE IF NOT EXISTS message_ins(
|
|
|
129
132
|
run_id INTEGER,
|
|
130
133
|
src_node_id INTEGER,
|
|
131
134
|
dst_node_id INTEGER,
|
|
132
|
-
|
|
135
|
+
reply_to_message_id TEXT,
|
|
133
136
|
created_at REAL,
|
|
134
137
|
delivered_at TEXT,
|
|
135
138
|
ttl REAL,
|
|
@@ -148,7 +151,7 @@ CREATE TABLE IF NOT EXISTS message_res(
|
|
|
148
151
|
run_id INTEGER,
|
|
149
152
|
src_node_id INTEGER,
|
|
150
153
|
dst_node_id INTEGER,
|
|
151
|
-
|
|
154
|
+
reply_to_message_id TEXT,
|
|
152
155
|
created_at REAL,
|
|
153
156
|
delivered_at TEXT,
|
|
154
157
|
ttl REAL,
|
|
@@ -371,7 +374,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
371
374
|
return None
|
|
372
375
|
|
|
373
376
|
res_metadata = message.metadata
|
|
374
|
-
msg_ins_id = res_metadata.
|
|
377
|
+
msg_ins_id = res_metadata.reply_to_message_id
|
|
375
378
|
msg_ins = self.get_valid_message_ins(msg_ins_id)
|
|
376
379
|
if msg_ins is None:
|
|
377
380
|
log(
|
|
@@ -442,6 +445,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
442
445
|
|
|
443
446
|
def get_message_res(self, message_ids: set[UUID]) -> list[Message]:
|
|
444
447
|
"""Get reply Messages for the given Message IDs."""
|
|
448
|
+
# pylint: disable-msg=too-many-locals
|
|
445
449
|
ret: dict[UUID, Message] = {}
|
|
446
450
|
|
|
447
451
|
# Verify Message IDs
|
|
@@ -465,11 +469,34 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
465
469
|
current_time=current,
|
|
466
470
|
)
|
|
467
471
|
|
|
472
|
+
# Check node availability
|
|
473
|
+
dst_node_ids: set[int] = set()
|
|
474
|
+
for message_id in message_ids:
|
|
475
|
+
in_message = found_message_ins_dict[message_id]
|
|
476
|
+
sint_node_id = convert_uint64_to_sint64(in_message.metadata.dst_node_id)
|
|
477
|
+
dst_node_ids.add(sint_node_id)
|
|
478
|
+
query = f"""
|
|
479
|
+
SELECT node_id, online_until
|
|
480
|
+
FROM node
|
|
481
|
+
WHERE node_id IN ({",".join(["?"] * len(dst_node_ids))});
|
|
482
|
+
"""
|
|
483
|
+
rows = self.query(query, tuple(dst_node_ids))
|
|
484
|
+
tmp_ret_dict = check_node_availability_for_in_message(
|
|
485
|
+
inquired_in_message_ids=message_ids,
|
|
486
|
+
found_in_message_dict=found_message_ins_dict,
|
|
487
|
+
node_id_to_online_until={
|
|
488
|
+
convert_sint64_to_uint64(row["node_id"]): row["online_until"]
|
|
489
|
+
for row in rows
|
|
490
|
+
},
|
|
491
|
+
current_time=current,
|
|
492
|
+
)
|
|
493
|
+
ret.update(tmp_ret_dict)
|
|
494
|
+
|
|
468
495
|
# Find all reply Messages
|
|
469
496
|
query = f"""
|
|
470
497
|
SELECT *
|
|
471
498
|
FROM message_res
|
|
472
|
-
WHERE
|
|
499
|
+
WHERE reply_to_message_id IN ({",".join(["?"] * len(message_ids))})
|
|
473
500
|
AND delivered_at = "";
|
|
474
501
|
"""
|
|
475
502
|
rows = self.query(query, tuple(str(message_id) for message_id in message_ids))
|
|
@@ -542,7 +569,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
542
569
|
# Delete reply Message
|
|
543
570
|
query_2 = f"""
|
|
544
571
|
DELETE FROM message_res
|
|
545
|
-
WHERE
|
|
572
|
+
WHERE reply_to_message_id IN ({placeholders});
|
|
546
573
|
"""
|
|
547
574
|
|
|
548
575
|
with self.conn:
|
|
@@ -584,6 +611,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
584
611
|
"VALUES (?, ?, ?, ?)"
|
|
585
612
|
)
|
|
586
613
|
|
|
614
|
+
# Mark the node online util time.time() + ping_interval
|
|
587
615
|
try:
|
|
588
616
|
self.query(
|
|
589
617
|
query,
|
|
@@ -699,7 +727,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
699
727
|
fab_version: Optional[str],
|
|
700
728
|
fab_hash: Optional[str],
|
|
701
729
|
override_config: UserConfig,
|
|
702
|
-
federation_options:
|
|
730
|
+
federation_options: ConfigRecord,
|
|
703
731
|
) -> int:
|
|
704
732
|
"""Create a new run for the specified `fab_id` and `fab_version`."""
|
|
705
733
|
# Sample a random int64 as run_id
|
|
@@ -725,7 +753,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
725
753
|
fab_version,
|
|
726
754
|
fab_hash,
|
|
727
755
|
override_config_json,
|
|
728
|
-
|
|
756
|
+
configrecord_to_bytes(federation_options),
|
|
729
757
|
]
|
|
730
758
|
data += [
|
|
731
759
|
now().isoformat(),
|
|
@@ -883,7 +911,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
883
911
|
|
|
884
912
|
return pending_run_id
|
|
885
913
|
|
|
886
|
-
def get_federation_options(self, run_id: int) -> Optional[
|
|
914
|
+
def get_federation_options(self, run_id: int) -> Optional[ConfigRecord]:
|
|
887
915
|
"""Retrieve the federation options for the specified `run_id`."""
|
|
888
916
|
# Convert the uint64 value to sint64 for SQLite
|
|
889
917
|
sint64_run_id = convert_uint64_to_sint64(run_id)
|
|
@@ -896,10 +924,14 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
896
924
|
return None
|
|
897
925
|
|
|
898
926
|
row = rows[0]
|
|
899
|
-
return
|
|
927
|
+
return configrecord_from_bytes(row["federation_options"])
|
|
900
928
|
|
|
901
929
|
def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool:
|
|
902
|
-
"""Acknowledge a ping received from a node, serving as a heartbeat.
|
|
930
|
+
"""Acknowledge a ping received from a node, serving as a heartbeat.
|
|
931
|
+
|
|
932
|
+
It allows for one missed ping (in a PING_PATIENCE * ping_interval) before
|
|
933
|
+
marking the node as offline, where PING_PATIENCE = 2 in default.
|
|
934
|
+
"""
|
|
903
935
|
sint64_node_id = convert_uint64_to_sint64(node_id)
|
|
904
936
|
|
|
905
937
|
# Check if the node exists in the `node` table
|
|
@@ -909,7 +941,14 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
909
941
|
|
|
910
942
|
# Update `online_until` and `ping_interval` for the given `node_id`
|
|
911
943
|
query = "UPDATE node SET online_until = ?, ping_interval = ? WHERE node_id = ?"
|
|
912
|
-
self.query(
|
|
944
|
+
self.query(
|
|
945
|
+
query,
|
|
946
|
+
(
|
|
947
|
+
time.time() + PING_PATIENCE * ping_interval,
|
|
948
|
+
ping_interval,
|
|
949
|
+
sint64_node_id,
|
|
950
|
+
),
|
|
951
|
+
)
|
|
913
952
|
return True
|
|
914
953
|
|
|
915
954
|
def get_serverapp_context(self, run_id: int) -> Optional[Context]:
|
|
@@ -1026,7 +1065,7 @@ def message_to_dict(message: Message) -> dict[str, Any]:
|
|
|
1026
1065
|
"run_id": message.metadata.run_id,
|
|
1027
1066
|
"src_node_id": message.metadata.src_node_id,
|
|
1028
1067
|
"dst_node_id": message.metadata.dst_node_id,
|
|
1029
|
-
"
|
|
1068
|
+
"reply_to_message_id": message.metadata.reply_to_message_id,
|
|
1030
1069
|
"created_at": message.metadata.created_at,
|
|
1031
1070
|
"delivered_at": message.metadata.delivered_at,
|
|
1032
1071
|
"ttl": message.metadata.ttl,
|
|
@@ -1036,7 +1075,7 @@ def message_to_dict(message: Message) -> dict[str, Any]:
|
|
|
1036
1075
|
}
|
|
1037
1076
|
|
|
1038
1077
|
if message.has_content():
|
|
1039
|
-
result["content"] =
|
|
1078
|
+
result["content"] = recorddict_to_proto(message.content).SerializeToString()
|
|
1040
1079
|
else:
|
|
1041
1080
|
result["error"] = error_to_proto(message.error).SerializeToString()
|
|
1042
1081
|
|
|
@@ -1047,20 +1086,15 @@ def dict_to_message(message_dict: dict[str, Any]) -> Message:
|
|
|
1047
1086
|
"""Transform dict to Message."""
|
|
1048
1087
|
content, error = None, None
|
|
1049
1088
|
if (b_content := message_dict.pop("content")) is not None:
|
|
1050
|
-
content =
|
|
1089
|
+
content = recorddict_from_proto(ProtoRecordDict.FromString(b_content))
|
|
1051
1090
|
if (b_error := message_dict.pop("error")) is not None:
|
|
1052
1091
|
error = error_from_proto(ProtoError.FromString(b_error))
|
|
1053
1092
|
|
|
1054
1093
|
# Metadata constructor doesn't allow passing created_at. We set it later
|
|
1055
1094
|
metadata = Metadata(
|
|
1056
|
-
**{
|
|
1057
|
-
k: v
|
|
1058
|
-
for k, v in message_dict.items()
|
|
1059
|
-
if k not in ["created_at", "delivered_at"]
|
|
1060
|
-
}
|
|
1095
|
+
**{k: v for k, v in message_dict.items() if k not in ["delivered_at"]}
|
|
1061
1096
|
)
|
|
1062
|
-
msg =
|
|
1063
|
-
msg.metadata.__dict__["_created_at"] = message_dict["created_at"]
|
|
1097
|
+
msg = make_message(metadata=metadata, content=content, error=error)
|
|
1064
1098
|
msg.metadata.delivered_at = message_dict["delivered_at"]
|
|
1065
1099
|
return msg
|
|
1066
1100
|
|