flwr-nightly 1.14.0.dev20241216__py3-none-any.whl → 1.15.0.dev20250107__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/cli/log.py +8 -6
- flwr/cli/ls.py +7 -4
- flwr/cli/new/templates/app/.gitignore.tpl +3 -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.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
- flwr/cli/run/run.py +7 -2
- flwr/cli/stop.py +3 -2
- flwr/cli/utils.py +79 -10
- flwr/client/client.py +0 -32
- flwr/client/message_handler/message_handler.py +0 -2
- flwr/client/numpy_client.py +0 -44
- flwr/client/supernode/app.py +1 -2
- flwr/common/record/recordset.py +1 -1
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +45 -0
- flwr/common/telemetry.py +13 -3
- flwr/server/app.py +1 -0
- flwr/server/run_serverapp.py +8 -9
- flwr/server/serverapp/app.py +17 -2
- flwr/server/superlink/driver/serverappio_servicer.py +9 -0
- flwr/server/superlink/fleet/vce/vce_api.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +10 -2
- flwr/server/superlink/linkstate/linkstate.py +4 -0
- flwr/server/superlink/linkstate/sqlite_linkstate.py +6 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +13 -0
- flwr/simulation/app.py +15 -4
- flwr/simulation/run_simulation.py +35 -7
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/METADATA +2 -2
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/RECORD +38 -38
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/LICENSE +0 -0
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.14.0.dev20241216.dist-info → flwr_nightly-1.15.0.dev20250107.dist-info}/entry_points.txt +0 -0
flwr/cli/log.py
CHANGED
@@ -34,7 +34,7 @@ from flwr.common.logger import log as logger
|
|
34
34
|
from flwr.proto.exec_pb2 import StreamLogsRequest # pylint: disable=E0611
|
35
35
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
36
36
|
|
37
|
-
from .utils import init_channel, try_obtain_cli_auth_plugin
|
37
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
38
38
|
|
39
39
|
|
40
40
|
def start_stream(
|
@@ -88,8 +88,9 @@ def stream_logs(
|
|
88
88
|
latest_timestamp = 0.0
|
89
89
|
res = None
|
90
90
|
try:
|
91
|
-
|
92
|
-
|
91
|
+
with unauthenticated_exc_handler():
|
92
|
+
for res in stub.StreamLogs(req, timeout=duration):
|
93
|
+
print(res.log_output, end="")
|
93
94
|
except grpc.RpcError as e:
|
94
95
|
# pylint: disable=E1101
|
95
96
|
if e.code() != grpc.StatusCode.DEADLINE_EXCEEDED:
|
@@ -109,9 +110,10 @@ def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None:
|
|
109
110
|
try:
|
110
111
|
while True:
|
111
112
|
try:
|
112
|
-
|
113
|
-
|
114
|
-
|
113
|
+
with unauthenticated_exc_handler():
|
114
|
+
# Enforce timeout for graceful exit
|
115
|
+
for res in stub.StreamLogs(req, timeout=timeout):
|
116
|
+
print(res.log_output)
|
115
117
|
except grpc.RpcError as e:
|
116
118
|
# pylint: disable=E1101
|
117
119
|
if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
|
flwr/cli/ls.py
CHANGED
@@ -43,7 +43,7 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611
|
|
43
43
|
)
|
44
44
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
45
45
|
|
46
|
-
from .utils import init_channel, try_obtain_cli_auth_plugin
|
46
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
47
47
|
|
48
48
|
_RunListType = tuple[int, str, str, str, str, str, str, str, str]
|
49
49
|
|
@@ -99,7 +99,6 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
|
|
99
99
|
try:
|
100
100
|
if suppress_output:
|
101
101
|
redirect_output(captured_output)
|
102
|
-
|
103
102
|
# Load and validate federation config
|
104
103
|
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
105
104
|
|
@@ -132,6 +131,8 @@ def ls( # pylint: disable=too-many-locals, too-many-branches
|
|
132
131
|
_list_runs(stub, output_format)
|
133
132
|
|
134
133
|
except ValueError as err:
|
134
|
+
if suppress_output:
|
135
|
+
redirect_output(captured_output)
|
135
136
|
typer.secho(
|
136
137
|
f"❌ {err}",
|
137
138
|
fg=typer.colors.RED,
|
@@ -295,7 +296,8 @@ def _list_runs(
|
|
295
296
|
output_format: str = CliOutputFormat.DEFAULT,
|
296
297
|
) -> None:
|
297
298
|
"""List all runs."""
|
298
|
-
|
299
|
+
with unauthenticated_exc_handler():
|
300
|
+
res: ListRunsResponse = stub.ListRuns(ListRunsRequest())
|
299
301
|
run_dict = {run_id: run_from_proto(proto) for run_id, proto in res.run_dict.items()}
|
300
302
|
|
301
303
|
formatted_runs = _format_runs(run_dict, res.now)
|
@@ -311,7 +313,8 @@ def _display_one_run(
|
|
311
313
|
output_format: str = CliOutputFormat.DEFAULT,
|
312
314
|
) -> None:
|
313
315
|
"""Display information about a specific run."""
|
314
|
-
|
316
|
+
with unauthenticated_exc_handler():
|
317
|
+
res: ListRunsResponse = stub.ListRuns(ListRunsRequest(run_id=run_id))
|
315
318
|
if not res.run_dict:
|
316
319
|
raise ValueError(f"Run ID {run_id} not found")
|
317
320
|
|
@@ -8,10 +8,10 @@ version = "1.0.0"
|
|
8
8
|
description = ""
|
9
9
|
license = "Apache-2.0"
|
10
10
|
dependencies = [
|
11
|
-
"flwr[simulation]>=1.
|
11
|
+
"flwr[simulation]>=1.14.0",
|
12
12
|
"flwr-datasets[vision]>=0.3.0",
|
13
|
-
"torch==2.
|
14
|
-
"torchvision==0.
|
13
|
+
"torch==2.5.1",
|
14
|
+
"torchvision==0.20.1",
|
15
15
|
]
|
16
16
|
|
17
17
|
[tool.hatch.build.targets.wheel]
|
flwr/cli/run/run.py
CHANGED
@@ -48,7 +48,11 @@ from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611
|
|
48
48
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
49
49
|
|
50
50
|
from ..log import start_stream
|
51
|
-
from ..utils import
|
51
|
+
from ..utils import (
|
52
|
+
init_channel,
|
53
|
+
try_obtain_cli_auth_plugin,
|
54
|
+
unauthenticated_exc_handler,
|
55
|
+
)
|
52
56
|
|
53
57
|
CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds)
|
54
58
|
|
@@ -166,7 +170,8 @@ def _run_with_exec_api(
|
|
166
170
|
override_config=user_config_to_proto(parse_config_args(config_overrides)),
|
167
171
|
federation_options=configs_record_to_proto(c_record),
|
168
172
|
)
|
169
|
-
|
173
|
+
with unauthenticated_exc_handler():
|
174
|
+
res = stub.StartRun(req)
|
170
175
|
|
171
176
|
if res.HasField("run_id"):
|
172
177
|
typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN)
|
flwr/cli/stop.py
CHANGED
@@ -34,7 +34,7 @@ from flwr.common.logger import print_json_error, redirect_output, restore_output
|
|
34
34
|
from flwr.proto.exec_pb2 import StopRunRequest, StopRunResponse # pylint: disable=E0611
|
35
35
|
from flwr.proto.exec_pb2_grpc import ExecStub
|
36
36
|
|
37
|
-
from .utils import init_channel, try_obtain_cli_auth_plugin
|
37
|
+
from .utils import init_channel, try_obtain_cli_auth_plugin, unauthenticated_exc_handler
|
38
38
|
|
39
39
|
|
40
40
|
def stop( # pylint: disable=R0914
|
@@ -113,7 +113,8 @@ def stop( # pylint: disable=R0914
|
|
113
113
|
|
114
114
|
def _stop_run(stub: ExecStub, run_id: int, output_format: str) -> None:
|
115
115
|
"""Stop a run."""
|
116
|
-
|
116
|
+
with unauthenticated_exc_handler():
|
117
|
+
response: StopRunResponse = stub.StopRun(request=StopRunRequest(run_id=run_id))
|
117
118
|
if response.success:
|
118
119
|
typer.secho(f"✅ Run {run_id} successfully stopped.", fg=typer.colors.GREEN)
|
119
120
|
if output_format == CliOutputFormat.JSON:
|
flwr/cli/utils.py
CHANGED
@@ -18,9 +18,11 @@
|
|
18
18
|
import hashlib
|
19
19
|
import json
|
20
20
|
import re
|
21
|
+
from collections.abc import Iterator
|
22
|
+
from contextlib import contextmanager
|
21
23
|
from logging import DEBUG
|
22
24
|
from pathlib import Path
|
23
|
-
from typing import Any, Callable, Optional, cast
|
25
|
+
from typing import Any, Callable, Optional, Union, cast
|
24
26
|
|
25
27
|
import grpc
|
26
28
|
import typer
|
@@ -146,23 +148,69 @@ def sanitize_project_name(name: str) -> str:
|
|
146
148
|
return sanitized_name
|
147
149
|
|
148
150
|
|
149
|
-
def get_sha256_hash(
|
151
|
+
def get_sha256_hash(file_path_or_int: Union[Path, int]) -> str:
|
150
152
|
"""Calculate the SHA-256 hash of a file."""
|
151
153
|
sha256 = hashlib.sha256()
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
154
|
+
if isinstance(file_path_or_int, Path):
|
155
|
+
with open(file_path_or_int, "rb") as f:
|
156
|
+
while True:
|
157
|
+
data = f.read(65536) # Read in 64kB blocks
|
158
|
+
if not data:
|
159
|
+
break
|
160
|
+
sha256.update(data)
|
161
|
+
elif isinstance(file_path_or_int, int):
|
162
|
+
sha256.update(str(file_path_or_int).encode())
|
158
163
|
return sha256.hexdigest()
|
159
164
|
|
160
165
|
|
161
166
|
def get_user_auth_config_path(root_dir: Path, federation: str) -> Path:
|
162
|
-
"""Return the path to the user auth config file.
|
167
|
+
"""Return the path to the user auth config file.
|
168
|
+
|
169
|
+
Additionally, a `.gitignore` file will be created in the Flower directory to
|
170
|
+
include the `.credentials` folder to be excluded from git. If the `.gitignore`
|
171
|
+
file already exists, a warning will be displayed if the `.credentials` entry is
|
172
|
+
not found.
|
173
|
+
"""
|
163
174
|
# Locate the credentials directory
|
164
|
-
|
175
|
+
abs_flwr_dir = root_dir.absolute() / FLWR_DIR
|
176
|
+
credentials_dir = abs_flwr_dir / CREDENTIALS_DIR
|
165
177
|
credentials_dir.mkdir(parents=True, exist_ok=True)
|
178
|
+
|
179
|
+
# Determine the absolute path of the Flower directory for .gitignore
|
180
|
+
gitignore_path = abs_flwr_dir / ".gitignore"
|
181
|
+
credential_entry = CREDENTIALS_DIR
|
182
|
+
|
183
|
+
try:
|
184
|
+
if gitignore_path.exists():
|
185
|
+
with open(gitignore_path, encoding="utf-8") as gitignore_file:
|
186
|
+
lines = gitignore_file.read().splitlines()
|
187
|
+
|
188
|
+
# Warn if .credentials is not already in .gitignore
|
189
|
+
if credential_entry not in lines:
|
190
|
+
typer.secho(
|
191
|
+
f"`.gitignore` exists, but `{credential_entry}` entry not found. "
|
192
|
+
"Consider adding it to your `.gitignore` to exclude Flower "
|
193
|
+
"credentials from git.",
|
194
|
+
fg=typer.colors.YELLOW,
|
195
|
+
bold=True,
|
196
|
+
)
|
197
|
+
else:
|
198
|
+
typer.secho(
|
199
|
+
f"Creating a new `.gitignore` with `{credential_entry}` entry...",
|
200
|
+
fg=typer.colors.BLUE,
|
201
|
+
)
|
202
|
+
# Create a new .gitignore with .credentials
|
203
|
+
with open(gitignore_path, "w", encoding="utf-8") as gitignore_file:
|
204
|
+
gitignore_file.write(f"{credential_entry}\n")
|
205
|
+
except Exception as err:
|
206
|
+
typer.secho(
|
207
|
+
"❌ An error occurred while handling `.gitignore.` "
|
208
|
+
f"Please check the permissions of `{gitignore_path}` and try again.",
|
209
|
+
fg=typer.colors.RED,
|
210
|
+
bold=True,
|
211
|
+
)
|
212
|
+
raise typer.Exit(code=1) from err
|
213
|
+
|
166
214
|
return credentials_dir / f"{federation}.json"
|
167
215
|
|
168
216
|
|
@@ -231,3 +279,24 @@ def init_channel(
|
|
231
279
|
)
|
232
280
|
channel.subscribe(on_channel_state_change)
|
233
281
|
return channel
|
282
|
+
|
283
|
+
|
284
|
+
@contextmanager
|
285
|
+
def unauthenticated_exc_handler() -> Iterator[None]:
|
286
|
+
"""Context manager to handle gRPC UNAUTHENTICATED errors.
|
287
|
+
|
288
|
+
It catches grpc.RpcError exceptions with UNAUTHENTICATED status, informs the user,
|
289
|
+
and exits the application. All other exceptions will be allowed to escape.
|
290
|
+
"""
|
291
|
+
try:
|
292
|
+
yield
|
293
|
+
except grpc.RpcError as e:
|
294
|
+
if e.code() != grpc.StatusCode.UNAUTHENTICATED:
|
295
|
+
raise
|
296
|
+
typer.secho(
|
297
|
+
"❌ Authentication failed. Please run `flwr login`"
|
298
|
+
" to authenticate and try again.",
|
299
|
+
fg=typer.colors.RED,
|
300
|
+
bold=True,
|
301
|
+
)
|
302
|
+
raise typer.Exit(code=1) from None
|
flwr/client/client.py
CHANGED
@@ -22,7 +22,6 @@ from abc import ABC
|
|
22
22
|
|
23
23
|
from flwr.common import (
|
24
24
|
Code,
|
25
|
-
Context,
|
26
25
|
EvaluateIns,
|
27
26
|
EvaluateRes,
|
28
27
|
FitIns,
|
@@ -34,14 +33,11 @@ from flwr.common import (
|
|
34
33
|
Parameters,
|
35
34
|
Status,
|
36
35
|
)
|
37
|
-
from flwr.common.logger import warn_deprecated_feature_with_example
|
38
36
|
|
39
37
|
|
40
38
|
class Client(ABC):
|
41
39
|
"""Abstract base class for Flower clients."""
|
42
40
|
|
43
|
-
_context: Context
|
44
|
-
|
45
41
|
def get_properties(self, ins: GetPropertiesIns) -> GetPropertiesRes:
|
46
42
|
"""Return set of client's properties.
|
47
43
|
|
@@ -143,34 +139,6 @@ class Client(ABC):
|
|
143
139
|
metrics={},
|
144
140
|
)
|
145
141
|
|
146
|
-
@property
|
147
|
-
def context(self) -> Context:
|
148
|
-
"""Getter for `Context` client attribute."""
|
149
|
-
warn_deprecated_feature_with_example(
|
150
|
-
"Accessing the context via the client's attribute is deprecated.",
|
151
|
-
example_message="Instead, pass it to the client's "
|
152
|
-
"constructor in your `client_fn()` which already "
|
153
|
-
"receives a context object.",
|
154
|
-
code_example="def client_fn(context: Context) -> Client:\n\n"
|
155
|
-
"\t\t# Your existing client_fn\n\n"
|
156
|
-
"\t\t# Pass `context` to the constructor\n"
|
157
|
-
"\t\treturn FlowerClient(context).to_client()",
|
158
|
-
)
|
159
|
-
return self._context
|
160
|
-
|
161
|
-
@context.setter
|
162
|
-
def context(self, context: Context) -> None:
|
163
|
-
"""Setter for `Context` client attribute."""
|
164
|
-
self._context = context
|
165
|
-
|
166
|
-
def get_context(self) -> Context:
|
167
|
-
"""Get the run context from this client."""
|
168
|
-
return self.context
|
169
|
-
|
170
|
-
def set_context(self, context: Context) -> None:
|
171
|
-
"""Apply a run context to this client."""
|
172
|
-
self.context = context
|
173
|
-
|
174
142
|
def to_client(self) -> Client:
|
175
143
|
"""Return client (itself)."""
|
176
144
|
return self
|
flwr/client/numpy_client.py
CHANGED
@@ -21,13 +21,11 @@ from typing import Callable
|
|
21
21
|
from flwr.client.client import Client
|
22
22
|
from flwr.common import (
|
23
23
|
Config,
|
24
|
-
Context,
|
25
24
|
NDArrays,
|
26
25
|
Scalar,
|
27
26
|
ndarrays_to_parameters,
|
28
27
|
parameters_to_ndarrays,
|
29
28
|
)
|
30
|
-
from flwr.common.logger import warn_deprecated_feature_with_example
|
31
29
|
from flwr.common.typing import (
|
32
30
|
Code,
|
33
31
|
EvaluateIns,
|
@@ -71,8 +69,6 @@ Example
|
|
71
69
|
class NumPyClient(ABC):
|
72
70
|
"""Abstract base class for Flower clients using NumPy."""
|
73
71
|
|
74
|
-
_context: Context
|
75
|
-
|
76
72
|
def get_properties(self, config: Config) -> dict[str, Scalar]:
|
77
73
|
"""Return a client's set of properties.
|
78
74
|
|
@@ -175,34 +171,6 @@ class NumPyClient(ABC):
|
|
175
171
|
_ = (self, parameters, config)
|
176
172
|
return 0.0, 0, {}
|
177
173
|
|
178
|
-
@property
|
179
|
-
def context(self) -> Context:
|
180
|
-
"""Getter for `Context` client attribute."""
|
181
|
-
warn_deprecated_feature_with_example(
|
182
|
-
"Accessing the context via the client's attribute is deprecated.",
|
183
|
-
example_message="Instead, pass it to the client's "
|
184
|
-
"constructor in your `client_fn()` which already "
|
185
|
-
"receives a context object.",
|
186
|
-
code_example="def client_fn(context: Context) -> Client:\n\n"
|
187
|
-
"\t\t# Your existing client_fn\n\n"
|
188
|
-
"\t\t# Pass `context` to the constructor\n"
|
189
|
-
"\t\treturn FlowerClient(context).to_client()",
|
190
|
-
)
|
191
|
-
return self._context
|
192
|
-
|
193
|
-
@context.setter
|
194
|
-
def context(self, context: Context) -> None:
|
195
|
-
"""Setter for `Context` client attribute."""
|
196
|
-
self._context = context
|
197
|
-
|
198
|
-
def get_context(self) -> Context:
|
199
|
-
"""Get the run context from this client."""
|
200
|
-
return self.context
|
201
|
-
|
202
|
-
def set_context(self, context: Context) -> None:
|
203
|
-
"""Apply a run context to this client."""
|
204
|
-
self.context = context
|
205
|
-
|
206
174
|
def to_client(self) -> Client:
|
207
175
|
"""Convert to object to Client type and return it."""
|
208
176
|
return _wrap_numpy_client(client=self)
|
@@ -299,21 +267,9 @@ def _evaluate(self: Client, ins: EvaluateIns) -> EvaluateRes:
|
|
299
267
|
)
|
300
268
|
|
301
269
|
|
302
|
-
def _get_context(self: Client) -> Context:
|
303
|
-
"""Return context of underlying NumPyClient."""
|
304
|
-
return self.numpy_client.get_context() # type: ignore
|
305
|
-
|
306
|
-
|
307
|
-
def _set_context(self: Client, context: Context) -> None:
|
308
|
-
"""Apply context to underlying NumPyClient."""
|
309
|
-
self.numpy_client.set_context(context) # type: ignore
|
310
|
-
|
311
|
-
|
312
270
|
def _wrap_numpy_client(client: NumPyClient) -> Client:
|
313
271
|
member_dict: dict[str, Callable] = { # type: ignore
|
314
272
|
"__init__": _constructor,
|
315
|
-
"get_context": _get_context,
|
316
|
-
"set_context": _set_context,
|
317
273
|
}
|
318
274
|
|
319
275
|
# Add wrapper type methods (if overridden)
|
flwr/client/supernode/app.py
CHANGED
@@ -114,9 +114,8 @@ def run_client_app() -> None:
|
|
114
114
|
event(EventType.RUN_CLIENT_APP_ENTER)
|
115
115
|
log(
|
116
116
|
ERROR,
|
117
|
-
"The command `flower-client-app` has been replaced by `
|
117
|
+
"The command `flower-client-app` has been replaced by `flwr run`.",
|
118
118
|
)
|
119
|
-
log(INFO, "Execute `flower-supernode --help` to learn how to use it.")
|
120
119
|
register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE)
|
121
120
|
|
122
121
|
|
flwr/common/record/recordset.py
CHANGED
@@ -151,7 +151,7 @@ class RecordSet:
|
|
151
151
|
>>> p_record = ParametersRecord({"my_array": arr})
|
152
152
|
>>>
|
153
153
|
>>> # Adding it to the record_set would look like this
|
154
|
-
>>> my_recordset.
|
154
|
+
>>> my_recordset.parameters_records["my_parameters"] = p_record
|
155
155
|
|
156
156
|
For additional examples on how to construct each of the records types shown
|
157
157
|
above, please refer to the documentation for :code:`ConfigsRecord`,
|
@@ -117,3 +117,48 @@ def verify_hmac(key: bytes, message: bytes, hmac_value: bytes) -> bool:
|
|
117
117
|
return True
|
118
118
|
except InvalidSignature:
|
119
119
|
return False
|
120
|
+
|
121
|
+
|
122
|
+
def sign_message(private_key: ec.EllipticCurvePrivateKey, message: bytes) -> bytes:
|
123
|
+
"""Sign a message using the provided EC private key.
|
124
|
+
|
125
|
+
Parameters
|
126
|
+
----------
|
127
|
+
private_key : ec.EllipticCurvePrivateKey
|
128
|
+
The EC private key to sign the message with.
|
129
|
+
message : bytes
|
130
|
+
The message to be signed.
|
131
|
+
|
132
|
+
Returns
|
133
|
+
-------
|
134
|
+
bytes
|
135
|
+
The signature of the message.
|
136
|
+
"""
|
137
|
+
signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
|
138
|
+
return signature
|
139
|
+
|
140
|
+
|
141
|
+
def verify_signature(
|
142
|
+
public_key: ec.EllipticCurvePublicKey, message: bytes, signature: bytes
|
143
|
+
) -> bool:
|
144
|
+
"""Verify a signature against a message using the provided EC public key.
|
145
|
+
|
146
|
+
Parameters
|
147
|
+
----------
|
148
|
+
public_key : ec.EllipticCurvePublicKey
|
149
|
+
The EC public key to verify the signature.
|
150
|
+
message : bytes
|
151
|
+
The original message.
|
152
|
+
signature : bytes
|
153
|
+
The signature to verify.
|
154
|
+
|
155
|
+
Returns
|
156
|
+
-------
|
157
|
+
bool
|
158
|
+
True if the signature is valid, False otherwise.
|
159
|
+
"""
|
160
|
+
try:
|
161
|
+
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
|
162
|
+
return True
|
163
|
+
except InvalidSignature:
|
164
|
+
return False
|
flwr/common/telemetry.py
CHANGED
@@ -151,6 +151,16 @@ class EventType(str, Enum):
|
|
151
151
|
|
152
152
|
# Not yet implemented
|
153
153
|
|
154
|
+
# --- `flwr-*` commands ------------------------------------------------------------
|
155
|
+
|
156
|
+
# CLI: flwr-simulation
|
157
|
+
FLWR_SIMULATION_RUN_ENTER = auto()
|
158
|
+
FLWR_SIMULATION_RUN_LEAVE = auto()
|
159
|
+
|
160
|
+
# CLI: flwr-serverapp
|
161
|
+
FLWR_SERVERAPP_RUN_ENTER = auto()
|
162
|
+
FLWR_SERVERAPP_RUN_LEAVE = auto()
|
163
|
+
|
154
164
|
# --- Simulation Engine ------------------------------------------------------------
|
155
165
|
|
156
166
|
# CLI: flower-simulation
|
@@ -171,12 +181,12 @@ class EventType(str, Enum):
|
|
171
181
|
RUN_SUPERNODE_ENTER = auto()
|
172
182
|
RUN_SUPERNODE_LEAVE = auto()
|
173
183
|
|
174
|
-
#
|
184
|
+
# --- DEPRECATED -------------------------------------------------------------------
|
185
|
+
|
186
|
+
# [DEPRECATED] CLI: `flower-server-app`
|
175
187
|
RUN_SERVER_APP_ENTER = auto()
|
176
188
|
RUN_SERVER_APP_LEAVE = auto()
|
177
189
|
|
178
|
-
# --- DEPRECATED -------------------------------------------------------------------
|
179
|
-
|
180
190
|
# [DEPRECATED] CLI: `flower-client-app`
|
181
191
|
RUN_CLIENT_APP_ENTER = auto()
|
182
192
|
RUN_CLIENT_APP_LEAVE = auto()
|
flwr/server/app.py
CHANGED
@@ -374,6 +374,7 @@ def run_superlink() -> None:
|
|
374
374
|
server_public_key,
|
375
375
|
) = maybe_keys
|
376
376
|
state = state_factory.state()
|
377
|
+
state.clear_supernode_auth_keys_and_credentials()
|
377
378
|
state.store_node_public_keys(node_public_keys)
|
378
379
|
state.store_server_private_public_key(
|
379
380
|
private_key_to_bytes(server_private_key),
|
flwr/server/run_serverapp.py
CHANGED
@@ -15,12 +15,12 @@
|
|
15
15
|
"""Run ServerApp."""
|
16
16
|
|
17
17
|
|
18
|
-
import sys
|
19
18
|
from logging import DEBUG, ERROR
|
20
19
|
from typing import Optional
|
21
20
|
|
22
|
-
from flwr.common import Context
|
23
|
-
from flwr.common.
|
21
|
+
from flwr.common import Context, EventType, event
|
22
|
+
from flwr.common.exit_handlers import register_exit_handlers
|
23
|
+
from flwr.common.logger import log
|
24
24
|
from flwr.common.object_ref import load_app
|
25
25
|
|
26
26
|
from .driver import Driver
|
@@ -66,12 +66,11 @@ def run(
|
|
66
66
|
return context
|
67
67
|
|
68
68
|
|
69
|
-
# pylint: disable-next=too-many-branches,too-many-statements,too-many-locals
|
70
69
|
def run_server_app() -> None:
|
71
70
|
"""Run Flower server app."""
|
72
|
-
|
73
|
-
|
74
|
-
|
71
|
+
event(EventType.RUN_SERVER_APP_ENTER)
|
72
|
+
log(
|
73
|
+
ERROR,
|
74
|
+
"The command `flower-server-app` has been replaced by `flwr run`.",
|
75
75
|
)
|
76
|
-
|
77
|
-
sys.exit()
|
76
|
+
register_exit_handlers(event_type=EventType.RUN_SERVER_APP_LEAVE)
|