flwr 1.25.0__py3-none-any.whl → 1.26.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 (140) hide show
  1. flwr/__init__.py +1 -1
  2. flwr/app/__init__.py +4 -1
  3. flwr/app/message_type.py +29 -0
  4. flwr/app/metadata.py +5 -2
  5. flwr/app/user_config.py +19 -0
  6. flwr/cli/app.py +37 -19
  7. flwr/cli/app_cmd/publish.py +25 -75
  8. flwr/cli/app_cmd/review.py +18 -69
  9. flwr/cli/auth_plugin/auth_plugin.py +5 -10
  10. flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
  11. flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
  12. flwr/cli/build.py +15 -28
  13. flwr/cli/config/__init__.py +21 -0
  14. flwr/cli/config/ls.py +71 -0
  15. flwr/cli/config_migration.py +297 -0
  16. flwr/cli/config_utils.py +63 -156
  17. flwr/cli/constant.py +71 -0
  18. flwr/cli/federation/__init__.py +0 -2
  19. flwr/cli/federation/ls.py +256 -64
  20. flwr/cli/flower_config.py +429 -0
  21. flwr/cli/install.py +23 -62
  22. flwr/cli/log.py +23 -37
  23. flwr/cli/login/login.py +29 -63
  24. flwr/cli/ls.py +28 -58
  25. flwr/cli/new/new.py +9 -29
  26. flwr/cli/pull.py +19 -37
  27. flwr/cli/run/run.py +85 -93
  28. flwr/cli/run_utils.py +1 -1
  29. flwr/cli/stop.py +32 -73
  30. flwr/cli/supernode/ls.py +25 -57
  31. flwr/cli/supernode/register.py +31 -80
  32. flwr/cli/supernode/unregister.py +24 -70
  33. flwr/cli/typing.py +200 -0
  34. flwr/cli/utils.py +160 -275
  35. flwr/client/grpc_rere_client/connection.py +3 -3
  36. flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
  37. flwr/client/message_handler/message_handler.py +2 -1
  38. flwr/client/mod/centraldp_mods.py +1 -1
  39. flwr/client/mod/localdp_mod.py +1 -1
  40. flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
  41. flwr/client/run_info_store.py +2 -1
  42. flwr/clientapp/client_app.py +2 -1
  43. flwr/common/__init__.py +3 -2
  44. flwr/common/args.py +5 -5
  45. flwr/common/config.py +12 -17
  46. flwr/common/constant.py +3 -16
  47. flwr/common/context.py +2 -1
  48. flwr/common/exit/exit.py +4 -4
  49. flwr/common/exit/exit_code.py +6 -0
  50. flwr/common/grpc.py +2 -1
  51. flwr/common/logger.py +1 -1
  52. flwr/common/message.py +1 -1
  53. flwr/common/retry_invoker.py +13 -5
  54. flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
  55. flwr/common/serde.py +7 -5
  56. flwr/common/telemetry.py +1 -1
  57. flwr/common/typing.py +4 -3
  58. flwr/compat/client/app.py +6 -9
  59. flwr/compat/client/grpc_client/connection.py +2 -1
  60. flwr/compat/common/constant.py +29 -0
  61. flwr/compat/server/app.py +1 -1
  62. flwr/proto/clientappio_pb2.py +2 -2
  63. flwr/proto/clientappio_pb2_grpc.py +104 -88
  64. flwr/proto/clientappio_pb2_grpc.pyi +140 -80
  65. flwr/proto/federation_pb2.py +5 -3
  66. flwr/proto/federation_pb2.pyi +32 -2
  67. flwr/proto/run_pb2.py +5 -13
  68. flwr/proto/run_pb2.pyi +0 -57
  69. flwr/proto/serverappio_pb2.py +2 -2
  70. flwr/proto/serverappio_pb2_grpc.py +138 -207
  71. flwr/proto/serverappio_pb2_grpc.pyi +189 -155
  72. flwr/proto/simulationio_pb2.py +2 -2
  73. flwr/proto/simulationio_pb2_grpc.py +62 -90
  74. flwr/proto/simulationio_pb2_grpc.pyi +95 -55
  75. flwr/server/app.py +6 -13
  76. flwr/server/compat/grid_client_proxy.py +2 -1
  77. flwr/server/grid/grpc_grid.py +5 -5
  78. flwr/server/serverapp/app.py +11 -4
  79. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
  80. flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
  81. flwr/server/superlink/fleet/message_handler/message_handler.py +6 -5
  82. flwr/server/superlink/linkstate/__init__.py +2 -2
  83. flwr/server/superlink/linkstate/in_memory_linkstate.py +2 -10
  84. flwr/server/superlink/linkstate/linkstate.py +2 -21
  85. flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
  86. flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +432 -534
  87. flwr/server/superlink/linkstate/utils.py +49 -2
  88. flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
  89. flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
  90. flwr/server/utils/validator.py +1 -1
  91. flwr/server/workflow/default_workflows.py +2 -1
  92. flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
  93. flwr/serverapp/strategy/bulyan.py +7 -1
  94. flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
  95. flwr/serverapp/strategy/fedavg.py +1 -1
  96. flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
  97. flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
  98. flwr/simulation/run_simulation.py +3 -12
  99. flwr/simulation/simulationio_connection.py +3 -3
  100. flwr/{common → supercore}/address.py +7 -33
  101. flwr/supercore/app_utils.py +2 -1
  102. flwr/supercore/constant.py +24 -2
  103. flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
  104. flwr/supercore/credential_store/__init__.py +33 -0
  105. flwr/supercore/credential_store/credential_store.py +34 -0
  106. flwr/supercore/credential_store/file_credential_store.py +76 -0
  107. flwr/{common → supercore}/date.py +0 -11
  108. flwr/supercore/ffs/disk_ffs.py +1 -1
  109. flwr/supercore/object_store/object_store_factory.py +14 -6
  110. flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
  111. flwr/supercore/sql_mixin.py +315 -0
  112. flwr/supercore/state/__init__.py +15 -0
  113. flwr/supercore/state/alembic/__init__.py +15 -0
  114. flwr/supercore/state/alembic/env.py +103 -0
  115. flwr/supercore/state/alembic/script.py.mako +43 -0
  116. flwr/supercore/state/alembic/utils.py +239 -0
  117. flwr/supercore/state/alembic/versions/__init__.py +15 -0
  118. flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
  119. flwr/supercore/state/schema/README.md +121 -0
  120. flwr/supercore/state/schema/__init__.py +15 -0
  121. flwr/supercore/state/schema/corestate_tables.py +36 -0
  122. flwr/supercore/state/schema/linkstate_tables.py +152 -0
  123. flwr/supercore/state/schema/objectstore_tables.py +90 -0
  124. flwr/supercore/superexec/run_superexec.py +2 -2
  125. flwr/supercore/utils.py +36 -1
  126. flwr/superlink/federation/federation_manager.py +2 -2
  127. flwr/superlink/federation/noop_federation_manager.py +8 -6
  128. flwr/superlink/servicer/control/control_servicer.py +19 -17
  129. flwr/supernode/cli/flower_supernode.py +2 -1
  130. flwr/supernode/runtime/run_clientapp.py +14 -14
  131. flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -8
  132. flwr/supernode/start_client_internal.py +10 -6
  133. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/METADATA +7 -5
  134. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/RECORD +137 -116
  135. flwr/cli/federation/show.py +0 -318
  136. flwr/common/pyproject.py +0 -42
  137. flwr/supercore/sqlite_mixin.py +0 -159
  138. /flwr/{common → supercore}/version.py +0 -0
  139. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
  140. {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,152 @@
1
+ # Copyright 2026 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """SQLAlchemy Core Table definitions for LinkState."""
16
+
17
+
18
+ from sqlalchemy import (
19
+ TIMESTAMP,
20
+ Column,
21
+ Float,
22
+ ForeignKey,
23
+ Index,
24
+ Integer,
25
+ LargeBinary,
26
+ MetaData,
27
+ String,
28
+ Table,
29
+ UniqueConstraint,
30
+ )
31
+
32
+
33
+ def create_linkstate_metadata() -> MetaData:
34
+ """Create and return MetaData with LinkState table definitions."""
35
+ metadata = MetaData()
36
+
37
+ # --------------------------------------------------------------------------
38
+ # Table: node
39
+ # --------------------------------------------------------------------------
40
+ Table(
41
+ "node",
42
+ metadata,
43
+ Column("node_id", Integer, unique=True),
44
+ Column("owner_aid", String),
45
+ Column("owner_name", String),
46
+ Column("status", String),
47
+ Column("registered_at", String),
48
+ Column("last_activated_at", String, nullable=True),
49
+ Column("last_deactivated_at", String, nullable=True),
50
+ Column("unregistered_at", String, nullable=True),
51
+ Column("online_until", TIMESTAMP, nullable=True),
52
+ Column("heartbeat_interval", Float),
53
+ Column("public_key", LargeBinary, unique=True),
54
+ # Indexes
55
+ # Used in delete_node and get_node_info (security/filtering)
56
+ Index("idx_node_owner_aid", "owner_aid"),
57
+ # Used in get_nodes and activation checks (frequent filtering)
58
+ Index("idx_node_status", "status"),
59
+ # Used in heartbeat checks to efficiently find expired nodes
60
+ Index("idx_online_until", "online_until"),
61
+ )
62
+
63
+ # --------------------------------------------------------------------------
64
+ # Table: run
65
+ # --------------------------------------------------------------------------
66
+ Table(
67
+ "run",
68
+ metadata,
69
+ Column("run_id", Integer, unique=True),
70
+ Column("fab_id", String),
71
+ Column("fab_version", String),
72
+ Column("fab_hash", String),
73
+ Column("override_config", String),
74
+ Column("pending_at", String),
75
+ Column("starting_at", String),
76
+ Column("running_at", String),
77
+ Column("finished_at", String),
78
+ Column("sub_status", String),
79
+ Column("details", String),
80
+ Column("federation", String),
81
+ Column("federation_options", LargeBinary),
82
+ Column("flwr_aid", String),
83
+ Column("bytes_sent", Integer, server_default="0"),
84
+ Column("bytes_recv", Integer, server_default="0"),
85
+ Column("clientapp_runtime", Float, server_default="0.0"),
86
+ )
87
+
88
+ # --------------------------------------------------------------------------
89
+ # Table: logs
90
+ # --------------------------------------------------------------------------
91
+ Table(
92
+ "logs",
93
+ metadata,
94
+ Column("timestamp", Float),
95
+ Column("run_id", Integer, ForeignKey("run.run_id")),
96
+ Column("node_id", Integer),
97
+ Column("log", String),
98
+ # Composite PK
99
+ UniqueConstraint("timestamp", "run_id", "node_id"),
100
+ )
101
+
102
+ # --------------------------------------------------------------------------
103
+ # Table: context
104
+ # --------------------------------------------------------------------------
105
+ Table(
106
+ "context",
107
+ metadata,
108
+ Column("run_id", Integer, ForeignKey("run.run_id"), unique=True),
109
+ Column("context", LargeBinary),
110
+ )
111
+
112
+ # --------------------------------------------------------------------------
113
+ # Table: message_ins
114
+ # --------------------------------------------------------------------------
115
+ Table(
116
+ "message_ins",
117
+ metadata,
118
+ Column("message_id", String, unique=True),
119
+ Column("group_id", String),
120
+ Column("run_id", Integer, ForeignKey("run.run_id")),
121
+ Column("src_node_id", Integer),
122
+ Column("dst_node_id", Integer),
123
+ Column("reply_to_message_id", String),
124
+ Column("created_at", Float),
125
+ Column("delivered_at", String),
126
+ Column("ttl", Float),
127
+ Column("message_type", String),
128
+ Column("content", LargeBinary, nullable=True),
129
+ Column("error", LargeBinary, nullable=True),
130
+ )
131
+
132
+ # --------------------------------------------------------------------------
133
+ # Table: message_res
134
+ # --------------------------------------------------------------------------
135
+ Table(
136
+ "message_res",
137
+ metadata,
138
+ Column("message_id", String, unique=True),
139
+ Column("group_id", String),
140
+ Column("run_id", Integer, ForeignKey("run.run_id")),
141
+ Column("src_node_id", Integer),
142
+ Column("dst_node_id", Integer),
143
+ Column("reply_to_message_id", String),
144
+ Column("created_at", Float),
145
+ Column("delivered_at", String),
146
+ Column("ttl", Float),
147
+ Column("message_type", String),
148
+ Column("content", LargeBinary, nullable=True),
149
+ Column("error", LargeBinary, nullable=True),
150
+ )
151
+
152
+ return metadata
@@ -0,0 +1,90 @@
1
+ # Copyright 2026 Flower Labs GmbH. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """SQLAlchemy Core Table definitions for ObjectStore."""
16
+
17
+
18
+ from sqlalchemy import (
19
+ CheckConstraint,
20
+ Column,
21
+ ForeignKey,
22
+ Integer,
23
+ LargeBinary,
24
+ MetaData,
25
+ PrimaryKeyConstraint,
26
+ String,
27
+ Table,
28
+ )
29
+
30
+
31
+ def create_objectstore_metadata() -> MetaData:
32
+ """Create and return MetaData with ObjectStore table definitions."""
33
+ metadata = MetaData()
34
+
35
+ # --------------------------------------------------------------------------
36
+ # Table: objects
37
+ # --------------------------------------------------------------------------
38
+ Table(
39
+ "objects",
40
+ metadata,
41
+ Column("object_id", String, primary_key=True, nullable=True),
42
+ Column("content", LargeBinary),
43
+ Column(
44
+ "is_available",
45
+ Integer,
46
+ nullable=False,
47
+ server_default="0",
48
+ ),
49
+ Column("ref_count", Integer, nullable=False, server_default="0"),
50
+ CheckConstraint("is_available IN (0, 1)", name="ck_objects_is_available"),
51
+ )
52
+
53
+ # --------------------------------------------------------------------------
54
+ # Table: object_children
55
+ # --------------------------------------------------------------------------
56
+ Table(
57
+ "object_children",
58
+ metadata,
59
+ Column(
60
+ "parent_id",
61
+ String,
62
+ ForeignKey("objects.object_id", ondelete="CASCADE"),
63
+ nullable=False,
64
+ ),
65
+ Column(
66
+ "child_id",
67
+ String,
68
+ ForeignKey("objects.object_id", ondelete="CASCADE"),
69
+ nullable=False,
70
+ ),
71
+ PrimaryKeyConstraint("parent_id", "child_id"),
72
+ )
73
+
74
+ # --------------------------------------------------------------------------
75
+ # Table: run_objects
76
+ # --------------------------------------------------------------------------
77
+ Table(
78
+ "run_objects",
79
+ metadata,
80
+ Column("run_id", Integer, nullable=False),
81
+ Column(
82
+ "object_id",
83
+ String,
84
+ ForeignKey("objects.object_id", ondelete="CASCADE"),
85
+ nullable=False,
86
+ ),
87
+ PrimaryKeyConstraint("run_id", "object_id"),
88
+ )
89
+
90
+ return metadata
@@ -23,7 +23,7 @@ from flwr.common.config import get_flwr_dir
23
23
  from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
24
24
  from flwr.common.grpc import create_channel, on_channel_state_change
25
25
  from flwr.common.logger import log
26
- from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
26
+ from flwr.common.retry_invoker import make_simple_grpc_retry_invoker, wrap_stub
27
27
  from flwr.common.serde import run_from_proto
28
28
  from flwr.common.telemetry import EventType
29
29
  from flwr.common.typing import Run
@@ -101,7 +101,7 @@ def run_superexec( # pylint: disable=R0913,R0914,R0917
101
101
 
102
102
  # Create the gRPC stub for the AppIO API
103
103
  stub = stub_class(channel)
104
- _wrap_stub(stub, _make_simple_grpc_retry_invoker())
104
+ wrap_stub(stub, make_simple_grpc_retry_invoker())
105
105
 
106
106
  def get_run(run_id: int) -> Run:
107
107
  _req = GetRunRequest(run_id=run_id)
flwr/supercore/utils.py CHANGED
@@ -16,11 +16,14 @@
16
16
 
17
17
 
18
18
  import json
19
+ import os
19
20
  import re
21
+ from pathlib import Path
20
22
 
21
23
  import requests
22
24
 
23
- from flwr.common.version import package_version as flwr_version
25
+ from flwr.common.constant import FLWR_DIR, FLWR_HOME
26
+ from flwr.supercore.version import package_version as flwr_version
24
27
 
25
28
  from .constant import APP_ID_PATTERN, APP_VERSION_PATTERN
26
29
 
@@ -62,6 +65,17 @@ def int64_to_uint64(signed: int) -> int:
62
65
  return signed
63
66
 
64
67
 
68
+ def get_flwr_home() -> Path:
69
+ """Get the Flower home directory path.
70
+
71
+ Returns FLWR_HOME environment variable if set, otherwise returns a default
72
+ subdirectory in the user's home directory.
73
+ """
74
+ if flwr_home := os.getenv(FLWR_HOME):
75
+ return Path(flwr_home)
76
+ return Path.home() / FLWR_DIR
77
+
78
+
65
79
  def parse_app_spec(app_spec: str) -> tuple[str, str | None]:
66
80
  """Parse app specification string into app ID and version.
67
81
 
@@ -240,3 +254,24 @@ def humanize_bytes(num_bytes: int) -> str:
240
254
  value /= 1024
241
255
 
242
256
  raise RuntimeError("Unreachable code") # Make mypy happy
257
+
258
+
259
+ def check_federation_format(federation: str) -> None:
260
+ """Check if the federation string is valid.
261
+
262
+ Parameters
263
+ ----------
264
+ federation : str
265
+ The federation string to check.
266
+
267
+ Raises
268
+ ------
269
+ ValueError
270
+ If the federation string is not valid. The expected
271
+ format is '@<account-name>/<federation-name>'.
272
+ """
273
+ if not re.match(r"^@[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+$", federation):
274
+ raise ValueError(
275
+ f"Invalid federation format: {federation}. "
276
+ f"Expected format: '@<account-name>/<federation-name>'."
277
+ )
@@ -56,8 +56,8 @@ class FederationManager(ABC):
56
56
  """Given a node ID, check if it is in the federation."""
57
57
 
58
58
  @abstractmethod
59
- def get_federations(self, flwr_aid: str) -> list[str]:
60
- """Get federations of which the account is a member."""
59
+ def get_federations(self, flwr_aid: str) -> list[tuple[str, str]]:
60
+ """Get federations (name, description) of which the account is a member."""
61
61
 
62
62
  @abstractmethod
63
63
  def get_details(self, federation: str) -> Federation:
@@ -15,9 +15,10 @@
15
15
  """NoOp implementation of FederationManager."""
16
16
 
17
17
 
18
- from flwr.common.constant import NOOP_FLWR_AID
18
+ from flwr.common.constant import NOOP_ACCOUNT_NAME, NOOP_FLWR_AID
19
19
  from flwr.common.typing import Federation
20
- from flwr.supercore.constant import NOOP_FEDERATION
20
+ from flwr.proto.federation_pb2 import Account # pylint: disable=E0611
21
+ from flwr.supercore.constant import NOOP_FEDERATION, NOOP_FEDERATION_DESCRIPTION
21
22
 
22
23
  from .federation_manager import FederationManager
23
24
 
@@ -47,11 +48,11 @@ class NoOpFederationManager(FederationManager):
47
48
  raise ValueError(f"Federation '{federation}' does not exist.")
48
49
  return True
49
50
 
50
- def get_federations(self, flwr_aid: str) -> list[str]:
51
- """Get federations of which the account is a member."""
51
+ def get_federations(self, flwr_aid: str) -> list[tuple[str, str]]:
52
+ """Get federations (name, description) of which the account is a member."""
52
53
  if flwr_aid != NOOP_FLWR_AID:
53
54
  return []
54
- return [NOOP_FEDERATION]
55
+ return [(NOOP_FEDERATION, NOOP_FEDERATION_DESCRIPTION)]
55
56
 
56
57
  def get_details(self, federation: str) -> Federation:
57
58
  """Get details of the federation."""
@@ -65,7 +66,8 @@ class NoOpFederationManager(FederationManager):
65
66
  ]
66
67
  return Federation(
67
68
  name=NOOP_FEDERATION,
68
- member_aids=[NOOP_FLWR_AID],
69
+ description=NOOP_FEDERATION_DESCRIPTION,
70
+ accounts=[Account(id=NOOP_FLWR_AID, name=NOOP_ACCOUNT_NAME)],
69
71
  nodes=nodes,
70
72
  runs=runs,
71
73
  )
@@ -29,6 +29,8 @@ from flwr.cli.config_utils import get_fab_metadata
29
29
  from flwr.common import Context, RecordDict, now
30
30
  from flwr.common.constant import (
31
31
  FAB_MAX_SIZE,
32
+ FEDERATION_NOT_FOUND_MESSAGE,
33
+ FEDERATION_NOT_SPECIFIED_MESSAGE,
32
34
  HEARTBEAT_DEFAULT_INTERVAL,
33
35
  LOG_STREAM_INTERVAL,
34
36
  NO_ACCOUNT_AUTH_MESSAGE,
@@ -38,6 +40,7 @@ from flwr.common.constant import (
38
40
  PUBLIC_KEY_NOT_VALID,
39
41
  PULL_UNFINISHED_RUN_MESSAGE,
40
42
  RUN_ID_NOT_FOUND_MESSAGE,
43
+ SUPERLINK_NODE_ID,
41
44
  TRANSPORT_TYPE_GRPC_ADAPTER,
42
45
  Status,
43
46
  SubStatus,
@@ -79,7 +82,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
79
82
  from flwr.proto.federation_pb2 import Federation # pylint: disable=E0611
80
83
  from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
81
84
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
82
- from flwr.supercore.constant import PLATFORM_API_URL
85
+ from flwr.supercore.constant import NOOP_FEDERATION, PLATFORM_API_URL
83
86
  from flwr.supercore.ffs import FfsFactory
84
87
  from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
85
88
  from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
@@ -148,10 +151,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
148
151
  )
149
152
 
150
153
  # Check (1) federation exists and (2) the flwr_aid is a member
151
- federation = request.federation
152
-
154
+ federation = request.federation or NOOP_FEDERATION
153
155
  if not state.federation_manager.exists(federation):
154
- raise ValueError(f"Federation '{federation}' does not exist.")
156
+ if request.federation:
157
+ raise ValueError(FEDERATION_NOT_FOUND_MESSAGE % federation)
158
+ raise ValueError(FEDERATION_NOT_SPECIFIED_MESSAGE)
155
159
 
156
160
  if not state.federation_manager.has_member(flwr_aid, federation):
157
161
  raise ValueError(
@@ -169,7 +173,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
169
173
  fab_hash = ffs.put(fab.content, fab.verifications)
170
174
 
171
175
  if fab_hash != fab.hash_str:
172
- raise RuntimeError(
176
+ raise ValueError(
173
177
  f"FAB ({fab.hash_str}) hash from request doesn't match contents"
174
178
  )
175
179
  fab_id, fab_version = get_fab_metadata(fab.content)
@@ -179,7 +183,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
179
183
  fab_version,
180
184
  fab_hash,
181
185
  override_config,
182
- request.federation,
186
+ federation,
183
187
  federation_options,
184
188
  flwr_aid,
185
189
  )
@@ -195,7 +199,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
195
199
  # Create an empty context for the Run
196
200
  context = Context(
197
201
  run_id=run_id,
198
- node_id=0,
202
+ node_id=SUPERLINK_NODE_ID,
199
203
  # Dict is invariant in mypy
200
204
  node_config=node_config, # type: ignore[arg-type]
201
205
  state=RecordDict(),
@@ -205,13 +209,9 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
205
209
  # Register the context at the LinkState
206
210
  state.set_serverapp_context(run_id=run_id, context=context)
207
211
 
208
- # pylint: disable-next=broad-except
209
- except Exception as e:
212
+ except ValueError as e:
210
213
  log(ERROR, "Could not start run: %s", str(e))
211
- context.abort(
212
- grpc.StatusCode.FAILED_PRECONDITION,
213
- str(e),
214
- )
214
+ context.abort(grpc.StatusCode.FAILED_PRECONDITION, str(e))
215
215
 
216
216
  log(INFO, "Created run %s", str(run_id))
217
217
  return StartRunResponse(run_id=run_id)
@@ -536,9 +536,10 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
536
536
 
537
537
  # Get federations the account is a member of
538
538
  federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
539
-
540
539
  return ListFederationsResponse(
541
- federations=[Federation(name=fed) for fed in federations]
540
+ federations=[
541
+ Federation(name=fed[0], description=fed[1]) for fed in federations
542
+ ]
542
543
  )
543
544
 
544
545
  def ShowFederation(
@@ -558,7 +559,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
558
559
 
559
560
  # Ensure flwr_aid is a member of the requested federation
560
561
  federation = request.federation_name
561
- if federation not in federations:
562
+ if federation not in [fed[0] for fed in federations]:
562
563
  context.abort(
563
564
  grpc.StatusCode.FAILED_PRECONDITION,
564
565
  f"Federation '{federation}' does not exist or you are "
@@ -571,7 +572,8 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
571
572
  # Build Federation proto object
572
573
  federation_proto = Federation(
573
574
  name=federation,
574
- member_aids=details.member_aids,
575
+ member_aids=[acc.id for acc in details.accounts], # Deprecated in v1.26.0
576
+ accounts=details.accounts,
575
577
  nodes=details.nodes,
576
578
  runs=[run_to_proto(run) for run in details.runs],
577
579
  )
@@ -233,7 +233,7 @@ def _try_setup_client_authentication(
233
233
 
234
234
  try:
235
235
  ssh_private_key = load_ssh_private_key(
236
- Path(args.auth_supernode_private_key).read_bytes(),
236
+ Path(args.auth_supernode_private_key).expanduser().read_bytes(),
237
237
  None,
238
238
  )
239
239
  if not isinstance(ssh_private_key, ec.EllipticCurvePrivateKey):
@@ -260,6 +260,7 @@ def _try_obtain_trusted_entities(
260
260
  """Validate and return the trust entities."""
261
261
  if not trusted_entities_path:
262
262
  return None
263
+ trusted_entities_path = trusted_entities_path.expanduser()
263
264
  if not trusted_entities_path.is_file():
264
265
  flwr_exit(
265
266
  ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
@@ -41,7 +41,7 @@ from flwr.common.inflatable_protobuf_utils import (
41
41
  from flwr.common.inflatable_utils import pull_and_inflate_object_from_tree, push_objects
42
42
  from flwr.common.logger import log
43
43
  from flwr.common.message import remove_content_from_message
44
- from flwr.common.retry_invoker import _make_simple_grpc_retry_invoker, _wrap_stub
44
+ from flwr.common.retry_invoker import make_simple_grpc_retry_invoker, wrap_stub
45
45
  from flwr.common.serde import (
46
46
  context_from_proto,
47
47
  context_to_proto,
@@ -103,14 +103,14 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
103
103
  flwr_dir_ = get_flwr_dir(flwr_dir)
104
104
  try:
105
105
  stub = ClientAppIoStub(channel)
106
- _wrap_stub(stub, _make_simple_grpc_retry_invoker())
106
+ wrap_stub(stub, make_simple_grpc_retry_invoker())
107
107
 
108
108
  # Start app heartbeat
109
109
  heartbeat_sender = HeartbeatSender(make_app_heartbeat_fn_grpc(stub, token))
110
110
  heartbeat_sender.start()
111
111
 
112
112
  # Pull Message, Context, Run and (optional) FAB from SuperNode
113
- message, context, run, fab = pull_clientappinputs(stub=stub, token=token)
113
+ message, context, run, fab = pull_appinputs(stub=stub, token=token)
114
114
 
115
115
  try:
116
116
 
@@ -152,7 +152,7 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
152
152
  reply_message = Message(Error(code=e_code, reason=reason), reply_to=message)
153
153
 
154
154
  # Push Message and Context to SuperNode
155
- _ = push_clientappoutputs(
155
+ _ = push_appoutputs(
156
156
  stub=stub, token=token, message=reply_message, context=context
157
157
  )
158
158
 
@@ -165,15 +165,15 @@ def run_clientapp( # pylint: disable=R0913, R0914, R0917
165
165
  )
166
166
 
167
167
 
168
- def pull_clientappinputs(
168
+ def pull_appinputs(
169
169
  stub: ClientAppIoStub, token: str
170
170
  ) -> tuple[Message, Context, Run, Fab | None]:
171
- """Pull ClientAppInputs from SuperNode."""
171
+ """Pull AppInputs from SuperNode."""
172
172
  masked_token = mask_string(token)
173
- log(INFO, "[flwr-clientapp] Pull `ClientAppInputs` for token %s", masked_token)
173
+ log(INFO, "[flwr-clientapp] Pull `AppInputs` for token %s", masked_token)
174
174
  try:
175
175
  # Pull Context, Run and (optional) FAB
176
- res: PullAppInputsResponse = stub.PullClientAppInputs(
176
+ res: PullAppInputsResponse = stub.PullAppInputs(
177
177
  PullAppInputsRequest(token=token)
178
178
  )
179
179
  context = context_from_proto(res.context)
@@ -201,16 +201,16 @@ def pull_clientappinputs(
201
201
  message.metadata.__dict__["_message_id"] = object_tree.object_id
202
202
  return message, context, run, fab
203
203
  except grpc.RpcError as e:
204
- log(ERROR, "[PullClientAppInputs] gRPC error occurred: %s", str(e))
204
+ log(ERROR, "[PullAppInputs] gRPC error occurred: %s", str(e))
205
205
  raise e
206
206
 
207
207
 
208
- def push_clientappoutputs(
208
+ def push_appoutputs(
209
209
  stub: ClientAppIoStub, token: str, message: Message, context: Context
210
210
  ) -> PushAppOutputsResponse:
211
- """Push ClientAppOutputs to SuperNode."""
211
+ """Push AppOutputs to SuperNode."""
212
212
  masked_token = mask_string(token)
213
- log(INFO, "[flwr-clientapp] Push `ClientAppOutputs` for token %s", masked_token)
213
+ log(INFO, "[flwr-clientapp] Push `AppOutputs` for token %s", masked_token)
214
214
  # Set message ID
215
215
  message.metadata.__dict__["_message_id"] = message.object_id
216
216
  proto_message = message_to_proto(remove_content_from_message(message))
@@ -250,10 +250,10 @@ def push_clientappoutputs(
250
250
  )
251
251
 
252
252
  # Push Context
253
- res: PushAppOutputsResponse = stub.PushClientAppOutputs(
253
+ res: PushAppOutputsResponse = stub.PushAppOutputs(
254
254
  PushAppOutputsRequest(token=token, context=proto_context)
255
255
  )
256
256
  return res
257
257
  except grpc.RpcError as e:
258
- log(ERROR, "[PushClientAppOutputs] gRPC error occurred: %s", str(e))
258
+ log(ERROR, "[PushAppOutputs] gRPC error occurred: %s", str(e))
259
259
  raise e
@@ -129,11 +129,11 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
129
129
 
130
130
  return GetRunResponse(run=run_to_proto(run))
131
131
 
132
- def PullClientAppInputs(
132
+ def PullAppInputs(
133
133
  self, request: PullAppInputsRequest, context: grpc.ServicerContext
134
134
  ) -> PullAppInputsResponse:
135
135
  """Pull Message, Context, and Run."""
136
- log(DEBUG, "ClientAppIo.PullClientAppInputs")
136
+ log(DEBUG, "ClientAppIo.PullAppInputs")
137
137
 
138
138
  # Initialize state and ffs connection
139
139
  state = self.state_factory.state()
@@ -176,11 +176,11 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
176
176
  fab=fab_to_proto(fab),
177
177
  )
178
178
 
179
- def PushClientAppOutputs(
179
+ def PushAppOutputs(
180
180
  self, request: PushAppOutputsRequest, context: grpc.ServicerContext
181
181
  ) -> PushAppOutputsResponse:
182
182
  """Push Message and Context."""
183
- log(DEBUG, "ClientAppIo.PushClientAppOutputs")
183
+ log(DEBUG, "ClientAppIo.PushAppOutputs")
184
184
 
185
185
  # Initialize state connection
186
186
  state = self.state_factory.state()
@@ -251,16 +251,18 @@ class ClientAppIoServicer(clientappio_pb2_grpc.ClientAppIoServicer):
251
251
  )
252
252
  raise RuntimeError("This line should never be reached.")
253
253
 
254
+ # Record message processing end time
255
+ state.record_message_processing_end(
256
+ message_id=request.messages_list[0].metadata.reply_to_message_id
257
+ )
258
+
254
259
  # Store Message object to descendants mapping and preregister objects
255
260
  objects_to_push: set[str] = set()
256
261
  for object_tree in request.message_object_trees:
257
262
  objects_to_push |= set(store.preregister(run_id, object_tree))
263
+
258
264
  # Save the message to the state
259
265
  state.store_message(message_from_proto(request.messages_list[0]))
260
- # Record message processing end time
261
- state.record_message_processing_end(
262
- message_id=request.messages_list[0].metadata.reply_to_message_id
263
- )
264
266
  return PushAppMessagesResponse(objects_to_push=objects_to_push)
265
267
 
266
268
  def SendAppHeartbeat(