flwr 1.22.0__py3-none-any.whl → 1.24.0__py3-none-any.whl

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