flwr 1.21.0__py3-none-any.whl → 1.23.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.
Files changed (175) hide show
  1. flwr/cli/app.py +17 -1
  2. flwr/cli/auth_plugin/__init__.py +15 -6
  3. flwr/cli/auth_plugin/auth_plugin.py +95 -0
  4. flwr/cli/auth_plugin/noop_auth_plugin.py +58 -0
  5. flwr/cli/auth_plugin/oidc_cli_plugin.py +16 -25
  6. flwr/cli/build.py +118 -47
  7. flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +6 -5
  8. flwr/cli/log.py +2 -2
  9. flwr/cli/login/login.py +34 -23
  10. flwr/cli/ls.py +13 -9
  11. flwr/cli/new/new.py +196 -42
  12. flwr/cli/new/templates/app/README.flowertune.md.tpl +1 -1
  13. flwr/cli/new/templates/app/code/client.baseline.py.tpl +64 -47
  14. flwr/cli/new/templates/app/code/client.huggingface.py.tpl +68 -30
  15. flwr/cli/new/templates/app/code/client.jax.py.tpl +63 -42
  16. flwr/cli/new/templates/app/code/client.mlx.py.tpl +80 -51
  17. flwr/cli/new/templates/app/code/client.numpy.py.tpl +36 -13
  18. flwr/cli/new/templates/app/code/client.pytorch.py.tpl +71 -46
  19. flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +55 -0
  20. flwr/cli/new/templates/app/code/client.sklearn.py.tpl +75 -30
  21. flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +69 -44
  22. flwr/cli/new/templates/app/code/client.xgboost.py.tpl +110 -0
  23. flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +56 -90
  24. flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +1 -23
  25. flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +37 -58
  26. flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +39 -44
  27. flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -14
  28. flwr/cli/new/templates/app/code/server.baseline.py.tpl +27 -29
  29. flwr/cli/new/templates/app/code/server.huggingface.py.tpl +23 -19
  30. flwr/cli/new/templates/app/code/server.jax.py.tpl +27 -14
  31. flwr/cli/new/templates/app/code/server.mlx.py.tpl +29 -19
  32. flwr/cli/new/templates/app/code/server.numpy.py.tpl +30 -17
  33. flwr/cli/new/templates/app/code/server.pytorch.py.tpl +36 -26
  34. flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +31 -0
  35. flwr/cli/new/templates/app/code/server.sklearn.py.tpl +29 -21
  36. flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +28 -19
  37. flwr/cli/new/templates/app/code/server.xgboost.py.tpl +56 -0
  38. flwr/cli/new/templates/app/code/task.huggingface.py.tpl +16 -20
  39. flwr/cli/new/templates/app/code/task.jax.py.tpl +1 -1
  40. flwr/cli/new/templates/app/code/task.numpy.py.tpl +1 -1
  41. flwr/cli/new/templates/app/code/task.pytorch.py.tpl +14 -27
  42. flwr/cli/new/templates/app/code/{task.pytorch_msg_api.py.tpl → task.pytorch_legacy_api.py.tpl} +27 -14
  43. flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +1 -2
  44. flwr/cli/new/templates/app/code/task.xgboost.py.tpl +67 -0
  45. flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +4 -4
  46. flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +2 -2
  47. flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +4 -4
  48. flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
  49. flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +2 -2
  50. flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
  51. flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
  52. flwr/cli/new/templates/app/{pyproject.pytorch_msg_api.toml.tpl → pyproject.pytorch_legacy_api.toml.tpl} +3 -3
  53. flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
  54. flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +1 -1
  55. flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +61 -0
  56. flwr/cli/pull.py +100 -0
  57. flwr/cli/run/run.py +11 -7
  58. flwr/cli/stop.py +2 -2
  59. flwr/cli/supernode/__init__.py +25 -0
  60. flwr/cli/supernode/ls.py +260 -0
  61. flwr/cli/supernode/register.py +185 -0
  62. flwr/cli/supernode/unregister.py +138 -0
  63. flwr/cli/utils.py +109 -69
  64. flwr/client/__init__.py +2 -1
  65. flwr/client/grpc_adapter_client/connection.py +6 -8
  66. flwr/client/grpc_rere_client/connection.py +59 -31
  67. flwr/client/grpc_rere_client/grpc_adapter.py +28 -12
  68. flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +3 -6
  69. flwr/client/mod/secure_aggregation/secaggplus_mod.py +7 -5
  70. flwr/client/rest_client/connection.py +82 -37
  71. flwr/clientapp/__init__.py +1 -2
  72. flwr/clientapp/mod/__init__.py +4 -1
  73. flwr/clientapp/mod/centraldp_mods.py +156 -40
  74. flwr/clientapp/mod/localdp_mod.py +169 -0
  75. flwr/clientapp/typing.py +22 -0
  76. flwr/{client/clientapp → clientapp}/utils.py +1 -1
  77. flwr/common/constant.py +56 -13
  78. flwr/common/exit/exit_code.py +24 -10
  79. flwr/common/inflatable_utils.py +10 -10
  80. flwr/common/record/array.py +3 -3
  81. flwr/common/record/arrayrecord.py +10 -1
  82. flwr/common/record/typeddict.py +12 -0
  83. flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
  84. flwr/common/serde.py +4 -2
  85. flwr/common/typing.py +7 -6
  86. flwr/compat/client/app.py +1 -1
  87. flwr/compat/client/grpc_client/connection.py +2 -2
  88. flwr/proto/control_pb2.py +48 -31
  89. flwr/proto/control_pb2.pyi +95 -5
  90. flwr/proto/control_pb2_grpc.py +136 -0
  91. flwr/proto/control_pb2_grpc.pyi +52 -0
  92. flwr/proto/fab_pb2.py +11 -7
  93. flwr/proto/fab_pb2.pyi +21 -1
  94. flwr/proto/fleet_pb2.py +31 -23
  95. flwr/proto/fleet_pb2.pyi +63 -23
  96. flwr/proto/fleet_pb2_grpc.py +98 -28
  97. flwr/proto/fleet_pb2_grpc.pyi +45 -13
  98. flwr/proto/node_pb2.py +3 -1
  99. flwr/proto/node_pb2.pyi +48 -0
  100. flwr/server/app.py +152 -114
  101. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +17 -7
  102. flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +132 -38
  103. flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +27 -51
  104. flwr/server/superlink/fleet/message_handler/message_handler.py +67 -22
  105. flwr/server/superlink/fleet/rest_rere/rest_api.py +52 -31
  106. flwr/server/superlink/fleet/vce/backend/backend.py +1 -1
  107. flwr/server/superlink/fleet/vce/backend/raybackend.py +1 -1
  108. flwr/server/superlink/fleet/vce/vce_api.py +18 -5
  109. flwr/server/superlink/linkstate/in_memory_linkstate.py +167 -73
  110. flwr/server/superlink/linkstate/linkstate.py +107 -24
  111. flwr/server/superlink/linkstate/linkstate_factory.py +2 -1
  112. flwr/server/superlink/linkstate/sqlite_linkstate.py +306 -255
  113. flwr/server/superlink/linkstate/utils.py +3 -54
  114. flwr/server/superlink/serverappio/serverappio_servicer.py +2 -2
  115. flwr/server/superlink/simulation/simulationio_servicer.py +1 -1
  116. flwr/server/utils/validator.py +2 -3
  117. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +4 -2
  118. flwr/serverapp/strategy/__init__.py +26 -0
  119. flwr/serverapp/strategy/bulyan.py +238 -0
  120. flwr/serverapp/strategy/dp_adaptive_clipping.py +335 -0
  121. flwr/serverapp/strategy/dp_fixed_clipping.py +71 -49
  122. flwr/serverapp/strategy/fedadagrad.py +0 -3
  123. flwr/serverapp/strategy/fedadam.py +0 -3
  124. flwr/serverapp/strategy/fedavg.py +89 -64
  125. flwr/serverapp/strategy/fedavgm.py +198 -0
  126. flwr/serverapp/strategy/fedmedian.py +105 -0
  127. flwr/serverapp/strategy/fedprox.py +174 -0
  128. flwr/serverapp/strategy/fedtrimmedavg.py +176 -0
  129. flwr/serverapp/strategy/fedxgb_bagging.py +117 -0
  130. flwr/serverapp/strategy/fedxgb_cyclic.py +220 -0
  131. flwr/serverapp/strategy/fedyogi.py +0 -3
  132. flwr/serverapp/strategy/krum.py +112 -0
  133. flwr/serverapp/strategy/multikrum.py +247 -0
  134. flwr/serverapp/strategy/qfedavg.py +252 -0
  135. flwr/serverapp/strategy/strategy_utils.py +48 -0
  136. flwr/simulation/app.py +1 -1
  137. flwr/simulation/ray_transport/ray_actor.py +1 -1
  138. flwr/simulation/ray_transport/ray_client_proxy.py +1 -1
  139. flwr/simulation/run_simulation.py +28 -32
  140. flwr/supercore/cli/flower_superexec.py +26 -1
  141. flwr/supercore/constant.py +41 -0
  142. flwr/supercore/object_store/in_memory_object_store.py +0 -4
  143. flwr/supercore/object_store/object_store_factory.py +26 -6
  144. flwr/supercore/object_store/sqlite_object_store.py +252 -0
  145. flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
  146. flwr/supercore/primitives/asymmetric.py +117 -0
  147. flwr/supercore/primitives/asymmetric_ed25519.py +165 -0
  148. flwr/supercore/sqlite_mixin.py +156 -0
  149. flwr/supercore/superexec/plugin/exec_plugin.py +11 -1
  150. flwr/supercore/superexec/run_superexec.py +16 -2
  151. flwr/supercore/utils.py +20 -0
  152. flwr/superlink/artifact_provider/__init__.py +22 -0
  153. flwr/superlink/artifact_provider/artifact_provider.py +37 -0
  154. flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
  155. flwr/superlink/auth_plugin/auth_plugin.py +91 -0
  156. flwr/superlink/auth_plugin/noop_auth_plugin.py +87 -0
  157. flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +19 -19
  158. flwr/superlink/servicer/control/control_event_log_interceptor.py +1 -1
  159. flwr/superlink/servicer/control/control_grpc.py +16 -11
  160. flwr/superlink/servicer/control/control_servicer.py +207 -58
  161. flwr/supernode/cli/flower_supernode.py +19 -26
  162. flwr/supernode/runtime/run_clientapp.py +2 -2
  163. flwr/supernode/servicer/clientappio/clientappio_servicer.py +1 -1
  164. flwr/supernode/start_client_internal.py +17 -9
  165. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/METADATA +6 -16
  166. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/RECORD +170 -140
  167. flwr/cli/new/templates/app/code/client.pytorch_msg_api.py.tpl +0 -80
  168. flwr/cli/new/templates/app/code/server.pytorch_msg_api.py.tpl +0 -41
  169. flwr/common/auth_plugin/auth_plugin.py +0 -149
  170. flwr/serverapp/dp_fixed_clipping.py +0 -352
  171. flwr/serverapp/strategy/strategy_utils_tests.py +0 -304
  172. /flwr/cli/new/templates/app/code/{__init__.pytorch_msg_api.py.tpl → __init__.pytorch_legacy_api.py.tpl} +0 -0
  173. /flwr/{client → clientapp}/client_app.py +0 -0
  174. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/WHEEL +0 -0
  175. {flwr-1.21.0.dist-info → flwr-1.23.0.dist-info}/entry_points.txt +0 -0
@@ -17,7 +17,7 @@
17
17
 
18
18
  import hashlib
19
19
  import time
20
- from collections.abc import Generator
20
+ from collections.abc import Generator, Sequence
21
21
  from logging import ERROR, INFO
22
22
  from typing import Any, Optional, cast
23
23
 
@@ -25,11 +25,16 @@ import grpc
25
25
 
26
26
  from flwr.cli.config_utils import get_fab_metadata
27
27
  from flwr.common import Context, RecordDict, now
28
- from flwr.common.auth_plugin import ControlAuthPlugin
29
28
  from flwr.common.constant import (
30
29
  FAB_MAX_SIZE,
30
+ HEARTBEAT_DEFAULT_INTERVAL,
31
31
  LOG_STREAM_INTERVAL,
32
- NO_USER_AUTH_MESSAGE,
32
+ NO_ACCOUNT_AUTH_MESSAGE,
33
+ NO_ARTIFACT_PROVIDER_MESSAGE,
34
+ NODE_NOT_FOUND_MESSAGE,
35
+ PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
36
+ PUBLIC_KEY_NOT_VALID,
37
+ PULL_UNFINISHED_RUN_MESSAGE,
33
38
  RUN_ID_NOT_FOUND_MESSAGE,
34
39
  Status,
35
40
  SubStatus,
@@ -47,20 +52,32 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
47
52
  GetAuthTokensResponse,
48
53
  GetLoginDetailsRequest,
49
54
  GetLoginDetailsResponse,
55
+ ListNodesRequest,
56
+ ListNodesResponse,
50
57
  ListRunsRequest,
51
58
  ListRunsResponse,
59
+ PullArtifactsRequest,
60
+ PullArtifactsResponse,
61
+ RegisterNodeRequest,
62
+ RegisterNodeResponse,
52
63
  StartRunRequest,
53
64
  StartRunResponse,
54
65
  StopRunRequest,
55
66
  StopRunResponse,
56
67
  StreamLogsRequest,
57
68
  StreamLogsResponse,
69
+ UnregisterNodeRequest,
70
+ UnregisterNodeResponse,
58
71
  )
72
+ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
59
73
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
60
74
  from flwr.supercore.ffs import FfsFactory
61
75
  from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
76
+ from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
77
+ from flwr.superlink.artifact_provider import ArtifactProvider
78
+ from flwr.superlink.auth_plugin import ControlAuthnPlugin
62
79
 
63
- from .control_user_auth_interceptor import shared_account_info
80
+ from .control_account_auth_interceptor import shared_account_info
64
81
 
65
82
 
66
83
  class ControlServicer(control_pb2_grpc.ControlServicer):
@@ -72,15 +89,17 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
72
89
  ffs_factory: FfsFactory,
73
90
  objectstore_factory: ObjectStoreFactory,
74
91
  is_simulation: bool,
75
- auth_plugin: Optional[ControlAuthPlugin] = None,
92
+ authn_plugin: ControlAuthnPlugin,
93
+ artifact_provider: Optional[ArtifactProvider] = None,
76
94
  ) -> None:
77
95
  self.linkstate_factory = linkstate_factory
78
96
  self.ffs_factory = ffs_factory
79
97
  self.objectstore_factory = objectstore_factory
80
98
  self.is_simulation = is_simulation
81
- self.auth_plugin = auth_plugin
99
+ self.authn_plugin = authn_plugin
100
+ self.artifact_provider = artifact_provider
82
101
 
83
- def StartRun(
102
+ def StartRun( # pylint: disable=too-many-locals
84
103
  self, request: StartRunRequest, context: grpc.ServicerContext
85
104
  ) -> StartRunResponse:
86
105
  """Create run ID."""
@@ -96,7 +115,8 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
96
115
  )
97
116
  return StartRunResponse()
98
117
 
99
- flwr_aid = shared_account_info.get().flwr_aid if self.auth_plugin else None
118
+ flwr_aid = shared_account_info.get().flwr_aid
119
+ _check_flwr_aid_exists(flwr_aid, context)
100
120
  override_config = user_config_from_proto(request.override_config)
101
121
  federation_options = config_record_from_proto(request.federation_options)
102
122
  fab_file = request.fab.content
@@ -109,7 +129,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
109
129
  )
110
130
 
111
131
  # Create run
112
- fab = Fab(hashlib.sha256(fab_file).hexdigest(), fab_file)
132
+ fab = Fab(
133
+ hashlib.sha256(fab_file).hexdigest(),
134
+ fab_file,
135
+ dict(request.fab.verifications),
136
+ )
113
137
  fab_hash = ffs.put(fab.content, {})
114
138
  if fab_hash != fab.hash_str:
115
139
  raise RuntimeError(
@@ -126,11 +150,20 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
126
150
  flwr_aid,
127
151
  )
128
152
 
153
+ # Initialize node config
154
+ node_config = {}
155
+ if self.artifact_provider is not None:
156
+ node_config = {
157
+ "output_dir": self.artifact_provider.output_dir,
158
+ "tmp_dir": self.artifact_provider.tmp_dir,
159
+ }
160
+
129
161
  # Create an empty context for the Run
130
162
  context = Context(
131
163
  run_id=run_id,
132
164
  node_id=0,
133
- node_config={},
165
+ # Dict is invariant in mypy
166
+ node_config=node_config, # type: ignore[arg-type]
134
167
  state=RecordDict(),
135
168
  run_config={},
136
169
  )
@@ -161,12 +194,9 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
161
194
  if not run:
162
195
  context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
163
196
 
164
- # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
165
- if self.auth_plugin:
166
- flwr_aid = shared_account_info.get().flwr_aid
167
- _check_flwr_aid_in_run(
168
- flwr_aid=flwr_aid, run=cast(Run, run), context=context
169
- )
197
+ # Check if `flwr_aid` matches the run's `flwr_aid`
198
+ flwr_aid = shared_account_info.get().flwr_aid
199
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=cast(Run, run), context=context)
170
200
 
171
201
  after_timestamp = request.after_timestamp + 1e-6
172
202
  while context.is_active():
@@ -202,20 +232,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
202
232
 
203
233
  # Build a set of run IDs for `flwr ls --runs`
204
234
  if not request.HasField("run_id"):
205
- if self.auth_plugin:
206
- # If no `run_id` is specified and user auth is enabled,
207
- # return run IDs for the authenticated user
208
- flwr_aid = shared_account_info.get().flwr_aid
209
- if flwr_aid is None:
210
- context.abort(
211
- grpc.StatusCode.PERMISSION_DENIED,
212
- "️⛔️ User authentication is enabled, but `flwr_aid` is None",
213
- )
214
- run_ids = state.get_run_ids(flwr_aid=flwr_aid)
215
- else:
216
- # If no `run_id` is specified and no user auth is enabled,
217
- # return all run IDs
218
- run_ids = state.get_run_ids(None)
235
+ # If no `run_id` is specified and account auth is enabled,
236
+ # return run IDs for the authenticated account
237
+ flwr_aid = shared_account_info.get().flwr_aid
238
+ _check_flwr_aid_exists(flwr_aid, context)
239
+ run_ids = state.get_run_ids(flwr_aid=flwr_aid)
219
240
  # Build a set of run IDs for `flwr ls --run-id <run_id>`
220
241
  else:
221
242
  # Retrieve run ID and run
@@ -225,13 +246,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
225
246
  # Exit if `run_id` not found
226
247
  if not run:
227
248
  context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
249
+ raise grpc.RpcError() # This line is unreachable
228
250
 
229
- # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
230
- if self.auth_plugin:
231
- flwr_aid = shared_account_info.get().flwr_aid
232
- _check_flwr_aid_in_run(
233
- flwr_aid=flwr_aid, run=cast(Run, run), context=context
234
- )
251
+ # Check if `flwr_aid` matches the run's `flwr_aid`
252
+ flwr_aid = shared_account_info.get().flwr_aid
253
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
235
254
 
236
255
  run_ids = {run_id}
237
256
 
@@ -253,13 +272,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
253
272
  # Exit if `run_id` not found
254
273
  if not run:
255
274
  context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
275
+ raise grpc.RpcError() # This line is unreachable
256
276
 
257
- # If user auth is enabled, check if `flwr_aid` matches the run's `flwr_aid`
258
- if self.auth_plugin:
259
- flwr_aid = shared_account_info.get().flwr_aid
260
- _check_flwr_aid_in_run(
261
- flwr_aid=flwr_aid, run=cast(Run, run), context=context
262
- )
277
+ # Check if `flwr_aid` matches the run's `flwr_aid`
278
+ flwr_aid = shared_account_info.get().flwr_aid
279
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
263
280
 
264
281
  run_status = state.get_run_status({run_id})[run_id]
265
282
  if run_status.status == Status.FINISHED:
@@ -289,22 +306,22 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
289
306
  ) -> GetLoginDetailsResponse:
290
307
  """Start login."""
291
308
  log(INFO, "ControlServicer.GetLoginDetails")
292
- if self.auth_plugin is None:
309
+ if self.authn_plugin is None:
293
310
  context.abort(
294
311
  grpc.StatusCode.UNIMPLEMENTED,
295
- NO_USER_AUTH_MESSAGE,
312
+ NO_ACCOUNT_AUTH_MESSAGE,
296
313
  )
297
314
  raise grpc.RpcError() # This line is unreachable
298
315
 
299
316
  # Get login details
300
- details = self.auth_plugin.get_login_details()
317
+ details = self.authn_plugin.get_login_details()
301
318
 
302
319
  # Return empty response if details is None
303
320
  if details is None:
304
321
  return GetLoginDetailsResponse()
305
322
 
306
323
  return GetLoginDetailsResponse(
307
- auth_type=details.auth_type,
324
+ authn_type=details.authn_type,
308
325
  device_code=details.device_code,
309
326
  verification_uri_complete=details.verification_uri_complete,
310
327
  expires_in=details.expires_in,
@@ -316,15 +333,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
316
333
  ) -> GetAuthTokensResponse:
317
334
  """Get auth token."""
318
335
  log(INFO, "ControlServicer.GetAuthTokens")
319
- if self.auth_plugin is None:
336
+ if self.authn_plugin is None:
320
337
  context.abort(
321
338
  grpc.StatusCode.UNIMPLEMENTED,
322
- NO_USER_AUTH_MESSAGE,
339
+ NO_ACCOUNT_AUTH_MESSAGE,
323
340
  )
324
341
  raise grpc.RpcError() # This line is unreachable
325
342
 
326
343
  # Get auth tokens
327
- credentials = self.auth_plugin.get_auth_tokens(request.device_code)
344
+ credentials = self.authn_plugin.get_auth_tokens(request.device_code)
328
345
 
329
346
  # Return empty response if credentials is None
330
347
  if credentials is None:
@@ -335,6 +352,132 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
335
352
  refresh_token=credentials.refresh_token,
336
353
  )
337
354
 
355
+ def PullArtifacts(
356
+ self, request: PullArtifactsRequest, context: grpc.ServicerContext
357
+ ) -> PullArtifactsResponse:
358
+ """Pull artifacts for a given run ID."""
359
+ log(INFO, "ControlServicer.PullArtifacts")
360
+
361
+ # Check if artifact provider is configured
362
+ if self.artifact_provider is None:
363
+ context.abort(
364
+ grpc.StatusCode.UNIMPLEMENTED,
365
+ NO_ARTIFACT_PROVIDER_MESSAGE,
366
+ )
367
+ raise grpc.RpcError() # This line is unreachable
368
+
369
+ # Init link state
370
+ state = self.linkstate_factory.state()
371
+
372
+ # Retrieve run ID and run
373
+ run_id = request.run_id
374
+ run = state.get_run(run_id)
375
+
376
+ # Exit if `run_id` not found
377
+ if not run:
378
+ context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
379
+ raise grpc.RpcError() # This line is unreachable
380
+
381
+ # Exit if the run is not finished yet
382
+ if run.status.status != Status.FINISHED:
383
+ context.abort(
384
+ grpc.StatusCode.FAILED_PRECONDITION, PULL_UNFINISHED_RUN_MESSAGE
385
+ )
386
+
387
+ # Check if `flwr_aid` matches the run's `flwr_aid`
388
+ flwr_aid = shared_account_info.get().flwr_aid
389
+ _check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
390
+
391
+ # Call artifact provider
392
+ download_url = self.artifact_provider.get_url(run_id)
393
+ return PullArtifactsResponse(url=download_url)
394
+
395
+ def RegisterNode(
396
+ self, request: RegisterNodeRequest, context: grpc.ServicerContext
397
+ ) -> RegisterNodeResponse:
398
+ """Add a SuperNode."""
399
+ log(INFO, "ControlServicer.RegisterNode")
400
+
401
+ # Verify public key
402
+ try:
403
+ # Attempt to deserialize public key
404
+ pub_key = bytes_to_public_key(request.public_key)
405
+ # Check if it's a NIST EC curve public key
406
+ if not uses_nist_ec_curve(pub_key):
407
+ err_msg = "The provided public key is not a NIST EC curve public key."
408
+ log(ERROR, "%s", err_msg)
409
+ raise ValueError(err_msg)
410
+ except (ValueError, AttributeError) as err:
411
+ log(ERROR, "%s", err)
412
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_NOT_VALID)
413
+
414
+ # Init link state
415
+ state = self.linkstate_factory.state()
416
+ node_id = 0
417
+
418
+ flwr_aid = shared_account_info.get().flwr_aid
419
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
420
+ try:
421
+ node_id = state.create_node(
422
+ owner_aid=flwr_aid,
423
+ public_key=request.public_key,
424
+ heartbeat_interval=HEARTBEAT_DEFAULT_INTERVAL,
425
+ )
426
+
427
+ except ValueError:
428
+ # Public key already in use
429
+ log(ERROR, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE)
430
+ context.abort(
431
+ grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
432
+ )
433
+ log(INFO, "[ControlServicer.RegisterNode] Created node_id=%s", node_id)
434
+
435
+ return RegisterNodeResponse(node_id=node_id)
436
+
437
+ def UnregisterNode(
438
+ self, request: UnregisterNodeRequest, context: grpc.ServicerContext
439
+ ) -> UnregisterNodeResponse:
440
+ """Remove a SuperNode."""
441
+ log(INFO, "ControlServicer.UnregisterNode")
442
+
443
+ # Init link state
444
+ state = self.linkstate_factory.state()
445
+
446
+ flwr_aid = shared_account_info.get().flwr_aid
447
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
448
+ try:
449
+ state.delete_node(owner_aid=flwr_aid, node_id=request.node_id)
450
+ except ValueError:
451
+ log(ERROR, NODE_NOT_FOUND_MESSAGE)
452
+ context.abort(grpc.StatusCode.NOT_FOUND, NODE_NOT_FOUND_MESSAGE)
453
+
454
+ return UnregisterNodeResponse()
455
+
456
+ def ListNodes(
457
+ self, request: ListNodesRequest, context: grpc.ServicerContext
458
+ ) -> ListNodesResponse:
459
+ """List all SuperNodes."""
460
+ log(INFO, "ControlServicer.ListNodes")
461
+
462
+ if self.is_simulation:
463
+ log(ERROR, "ListNodes is not available in simulation mode.")
464
+ context.abort(
465
+ grpc.StatusCode.UNIMPLEMENTED,
466
+ "ListNodes is not available in simulation mode.",
467
+ )
468
+ raise grpc.RpcError() # This line is unreachable
469
+
470
+ nodes_info: Sequence[NodeInfo] = []
471
+ # Init link state
472
+ state = self.linkstate_factory.state()
473
+
474
+ flwr_aid = shared_account_info.get().flwr_aid
475
+ flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
476
+ # Retrieve all nodes for the account
477
+ nodes_info = state.get_node_info(owner_aids=[flwr_aid])
478
+
479
+ return ListNodesResponse(nodes_info=nodes_info, now=now().isoformat())
480
+
338
481
 
339
482
  def _create_list_runs_response(
340
483
  run_ids: set[int], state: LinkState, store: ObjectStore
@@ -353,29 +496,35 @@ def _create_list_runs_response(
353
496
  )
354
497
 
355
498
 
356
- def _check_flwr_aid_in_run(
357
- flwr_aid: Optional[str], run: Run, context: grpc.ServicerContext
358
- ) -> None:
359
- """Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
360
- # `flwr_aid` must not be None. Abort if it is None.
499
+ def _check_flwr_aid_exists(
500
+ flwr_aid: Optional[str], context: grpc.ServicerContext
501
+ ) -> str:
502
+ """Guard clause to check if `flwr_aid` exists."""
361
503
  if flwr_aid is None:
362
504
  context.abort(
363
505
  grpc.StatusCode.PERMISSION_DENIED,
364
- "️⛔️ User authentication is enabled, but `flwr_aid` is None",
506
+ "️⛔️ Failed to fetch the account information.",
365
507
  )
508
+ raise RuntimeError # This line is unreachable
509
+ return flwr_aid
510
+
366
511
 
512
+ def _check_flwr_aid_in_run(
513
+ flwr_aid: Optional[str], run: Run, context: grpc.ServicerContext
514
+ ) -> None:
515
+ """Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
516
+ _check_flwr_aid_exists(flwr_aid, context)
367
517
  # `run.flwr_aid` must not be an empty string. Abort if it is empty.
368
518
  run_flwr_aid = run.flwr_aid
369
519
  if not run_flwr_aid:
370
520
  context.abort(
371
521
  grpc.StatusCode.PERMISSION_DENIED,
372
- "⛔️ User authentication is enabled, but the run is not associated "
373
- "with a `flwr_aid`.",
522
+ "⛔️ Run is not associated with a `flwr_aid`.",
374
523
  )
375
524
 
376
525
  # Exit if `flwr_aid` does not match the run's `flwr_aid`
377
526
  if run_flwr_aid != flwr_aid:
378
527
  context.abort(
379
528
  grpc.StatusCode.PERMISSION_DENIED,
380
- "⛔️ Run ID does not belong to the user",
529
+ "⛔️ Run ID does not belong to the account",
381
530
  )
@@ -22,10 +22,7 @@ from typing import Optional
22
22
 
23
23
  from cryptography.exceptions import UnsupportedAlgorithm
24
24
  from cryptography.hazmat.primitives.asymmetric import ec
25
- from cryptography.hazmat.primitives.serialization import (
26
- load_ssh_private_key,
27
- load_ssh_public_key,
28
- )
25
+ from cryptography.hazmat.primitives.serialization import load_ssh_private_key
29
26
 
30
27
  from flwr.common import EventType, event
31
28
  from flwr.common.args import try_obtain_root_certificates
@@ -64,6 +61,13 @@ def flower_supernode() -> None:
64
61
  root_certificates = try_obtain_root_certificates(args, args.superlink)
65
62
  authentication_keys = _try_setup_client_authentication(args)
66
63
 
64
+ # Warn if authentication keys are provided but transport is not grpc-rere
65
+ if authentication_keys is not None and args.transport != TRANSPORT_TYPE_GRPC_RERE:
66
+ log(
67
+ WARN,
68
+ "SuperNode Authentication is only supported with the grpc-rere transport.",
69
+ )
70
+
67
71
  log(DEBUG, "Isolation mode: %s", args.isolation)
68
72
 
69
73
  start_client_internal(
@@ -188,12 +192,12 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
188
192
  parser.add_argument(
189
193
  "--auth-supernode-private-key",
190
194
  type=str,
191
- help="The SuperNode's private key (as a path str) to enable authentication.",
195
+ help="Path to the SuperNode's private key to enable authentication.",
192
196
  )
193
197
  parser.add_argument(
194
198
  "--auth-supernode-public-key",
195
199
  type=str,
196
- help="The SuperNode's public key (as a path str) to enable authentication.",
200
+ help="This argument is deprecated and will be removed in a future release.",
197
201
  )
198
202
  parser.add_argument(
199
203
  "--node-config",
@@ -207,12 +211,9 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
207
211
  def _try_setup_client_authentication(
208
212
  args: argparse.Namespace,
209
213
  ) -> Optional[tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]]:
210
- if not args.auth_supernode_private_key and not args.auth_supernode_public_key:
214
+ if not args.auth_supernode_private_key:
211
215
  return None
212
216
 
213
- if not args.auth_supernode_private_key or not args.auth_supernode_public_key:
214
- flwr_exit(ExitCode.SUPERNODE_NODE_AUTH_KEYS_REQUIRED)
215
-
216
217
  try:
217
218
  ssh_private_key = load_ssh_private_key(
218
219
  Path(args.auth_supernode_private_key).read_bytes(),
@@ -222,23 +223,15 @@ def _try_setup_client_authentication(
222
223
  raise ValueError()
223
224
  except (ValueError, UnsupportedAlgorithm):
224
225
  flwr_exit(
225
- ExitCode.SUPERNODE_NODE_AUTH_KEYS_INVALID,
226
+ ExitCode.SUPERNODE_NODE_AUTH_KEY_INVALID,
226
227
  "Unable to parse the private key file.",
227
228
  )
228
229
 
229
- try:
230
- ssh_public_key = load_ssh_public_key(
231
- Path(args.auth_supernode_public_key).read_bytes()
232
- )
233
- if not isinstance(ssh_public_key, ec.EllipticCurvePublicKey):
234
- raise ValueError()
235
- except (ValueError, UnsupportedAlgorithm):
236
- flwr_exit(
237
- ExitCode.SUPERNODE_NODE_AUTH_KEYS_INVALID,
238
- "Unable to parse the public key file.",
230
+ if args.auth_supernode_public_key:
231
+ log(
232
+ WARN,
233
+ "The `--auth-supernode-public-key` flag is deprecated and will be "
234
+ "removed in a future release. The public key is now derived from the "
235
+ "private key provided by `--auth-supernode-private-key`.",
239
236
  )
240
-
241
- return (
242
- ssh_private_key,
243
- ssh_public_key,
244
- )
237
+ return ssh_private_key, ssh_private_key.public_key()
@@ -23,8 +23,8 @@ import grpc
23
23
 
24
24
  from flwr.app.error import Error
25
25
  from flwr.cli.install import install_from_fab
26
- from flwr.client.client_app import ClientApp, LoadClientAppError
27
- from flwr.client.clientapp.utils import get_load_client_app_fn
26
+ from flwr.clientapp.client_app import ClientApp, LoadClientAppError
27
+ from flwr.clientapp.utils import get_load_client_app_fn
28
28
  from flwr.common import Context, Message
29
29
  from flwr.common.config import get_flwr_dir
30
30
  from flwr.common.constant import ErrorCode
@@ -151,7 +151,7 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
151
151
  # Retrieve context, run and fab for this run
152
152
  context = cast(Context, state.get_context(run_id))
153
153
  run = cast(Run, state.get_run(run_id))
154
- fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0]) # type: ignore
154
+ fab = Fab(run.fab_hash, ffs.get(run.fab_hash)[0], ffs.get(run.fab_hash)[1]) # type: ignore
155
155
 
156
156
  return PullAppInputsResponse(
157
157
  context=context_to_proto(context),
@@ -54,6 +54,7 @@ from flwr.common.logger import log
54
54
  from flwr.common.retry_invoker import RetryInvoker, _make_simple_grpc_retry_invoker
55
55
  from flwr.common.telemetry import EventType
56
56
  from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
57
+ from flwr.common.version import package_version
57
58
  from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
58
59
  from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
59
60
  from flwr.supercore.ffs import Ffs, FfsFactory
@@ -141,6 +142,19 @@ def start_client_internal(
141
142
  if insecure is None:
142
143
  insecure = root_certificates is None
143
144
 
145
+ # Insecure HTTP is incompatible with authentication
146
+ if insecure and authentication_keys is not None:
147
+ url_v = f"https://flower.ai/docs/framework/v{package_version}/en/"
148
+ page = "how-to-authenticate-supernodes.html"
149
+ flwr_exit(
150
+ ExitCode.SUPERNODE_STARTED_WITHOUT_TLS_BUT_NODE_AUTH_ENABLED,
151
+ "Insecure connection is enabled, but the SuperNode's private key is "
152
+ "provided for authentication. SuperNode authentication requires a "
153
+ "secure TLS connection with the SuperLink. Please enable TLS by "
154
+ "providing the certificate via `--root-certificates`. Please refer "
155
+ f"to the Flower documentation for more information: {url_v}{page}",
156
+ )
157
+
144
158
  # Initialize factories
145
159
  state_factory = NodeStateFactory()
146
160
  ffs_factory = FfsFactory(get_flwr_dir(flwr_path) / "supernode" / "ffs") # type: ignore
@@ -193,21 +207,16 @@ def start_client_internal(
193
207
  max_wait_time=max_wait_time,
194
208
  ) as conn:
195
209
  (
210
+ node_id,
196
211
  receive,
197
212
  send,
198
- create_node,
199
- _,
200
213
  get_run,
201
214
  get_fab,
202
215
  pull_object,
203
216
  push_object,
204
217
  confirm_message_received,
205
218
  ) = 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")
219
+ # Store node_id in state
211
220
  state.set_node_id(node_id)
212
221
 
213
222
  # pylint: disable=too-many-nested-blocks
@@ -443,10 +452,9 @@ def _init_connection( # pylint: disable=too-many-positional-arguments
443
452
  max_wait_time: Optional[float] = None,
444
453
  ) -> Iterator[
445
454
  tuple[
455
+ int,
446
456
  Callable[[], Optional[tuple[Message, ObjectTree]]],
447
457
  Callable[[Message, ObjectTree], set[str]],
448
- Callable[[], Optional[int]],
449
- Callable[[], None],
450
458
  Callable[[int], Run],
451
459
  Callable[[str, int], Fab],
452
460
  Callable[[int, str], bytes],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr
3
- Version: 1.21.0
3
+ Version: 1.23.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
@@ -102,25 +102,15 @@ Meet the Flower community on [flower.ai](https://flower.ai)!
102
102
 
103
103
  Flower's goal is to make federated learning accessible to everyone. This series of tutorials introduces the fundamentals of federated learning and how to implement them in Flower.
104
104
 
105
- 0. **What is Federated Learning?**
105
+ 0. **[What is Federated Learning?](https://flower.ai/docs/framework/main/en/tutorial-series-what-is-federated-learning.html)**
106
106
 
107
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-what-is-federated-learning.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-what-is-federated-learning.ipynb))
107
+ 1. **[An Introduction to Federated Learning](https://flower.ai/docs/framework/main/en/tutorial-series-get-started-with-flower-pytorch.html)**
108
108
 
109
- 1. **An Introduction to Federated Learning**
109
+ 2. **[Using Strategies in Federated Learning](https://flower.ai/docs/framework/main/en/tutorial-series-use-a-federated-learning-strategy-pytorch.html)**
110
110
 
111
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-get-started-with-flower-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-get-started-with-flower-pytorch.ipynb))
111
+ 3. **[Customize a Flower Strategy](https://flower.ai/docs/framework/main/en/tutorial-series-build-a-strategy-from-scratch-pytorch.html)**
112
112
 
113
- 2. **Using Strategies in Federated Learning**
114
-
115
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb))
116
-
117
- 3. **Building Strategies for Federated Learning**
118
-
119
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb))
120
-
121
- 4. **Custom Clients for Federated Learning**
122
-
123
- [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb) (or open the [Jupyter Notebook](https://github.com/adap/flower/blob/main/framework/docs/source/tutorial-series-customize-the-client-pytorch.ipynb))
113
+ 4. **[Communicate Custom Messages](https://flower.ai/docs/framework/main/en/tutorial-series-customize-the-client-pytorch.html)**
124
114
 
125
115
  Stay tuned, more tutorials are coming soon. Topics include **Privacy and Security in Federated Learning**, and **Scaling Federated Learning**.
126
116