flwr-nightly 1.23.0.dev20251005__py3-none-any.whl → 1.23.0.dev20251007__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flwr/cli/supernode/ls.py CHANGED
@@ -15,10 +15,16 @@
15
15
  """Flower command line interface `supernode list` command."""
16
16
 
17
17
 
18
+ import io
19
+ import json
20
+ from datetime import datetime, timedelta
18
21
  from pathlib import Path
19
- from typing import Annotated, Optional
22
+ from typing import Annotated, Optional, cast
20
23
 
21
24
  import typer
25
+ from rich.console import Console
26
+ from rich.table import Table
27
+ from rich.text import Text
22
28
 
23
29
  from flwr.cli.config_utils import (
24
30
  exit_if_no_address,
@@ -26,10 +32,23 @@ from flwr.cli.config_utils import (
26
32
  process_loaded_project_config,
27
33
  validate_federation_in_project_config,
28
34
  )
29
- from flwr.common.constant import FAB_CONFIG_FILE
35
+ from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
36
+ from flwr.common.date import format_timedelta, isoformat8601_utc
37
+ from flwr.common.logger import print_json_error, redirect_output, restore_output
38
+ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
39
+ ListNodesCliRequest,
40
+ ListNodesCliResponse,
41
+ )
42
+ from flwr.proto.control_pb2_grpc import ControlStub
43
+ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
44
+
45
+ from ..utils import flwr_cli_grpc_exc_handler, init_channel, try_obtain_cli_auth_plugin
46
+
47
+ _NodeListType = tuple[int, str, str, str, str, str, str, str]
30
48
 
31
49
 
32
50
  def ls( # pylint: disable=R0914
51
+ ctx: typer.Context,
33
52
  app: Annotated[
34
53
  Path,
35
54
  typer.Argument(help="Path of the Flower project"),
@@ -38,14 +57,202 @@ def ls( # pylint: disable=R0914
38
57
  Optional[str],
39
58
  typer.Argument(help="Name of the federation"),
40
59
  ] = None,
60
+ output_format: Annotated[
61
+ str,
62
+ typer.Option(
63
+ "--format",
64
+ case_sensitive=False,
65
+ help="Format output using 'default' view or 'json'",
66
+ ),
67
+ ] = CliOutputFormat.DEFAULT,
68
+ verbose: Annotated[
69
+ bool,
70
+ typer.Option(
71
+ "--verbose",
72
+ "-v",
73
+ help="Enable verbose output",
74
+ ),
75
+ ] = False,
41
76
  ) -> None:
42
77
  """List SuperNodes in the federation."""
43
- typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
78
+ # Resolve command used (list or ls)
79
+ command_name = cast(str, ctx.command.name) if ctx.command else "ls"
80
+
81
+ suppress_output = output_format == CliOutputFormat.JSON
82
+ captured_output = io.StringIO()
83
+ try:
84
+ if suppress_output:
85
+ redirect_output(captured_output)
86
+ typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
87
+
88
+ pyproject_path = app / FAB_CONFIG_FILE if app else None
89
+ config, errors, warnings = load_and_validate(path=pyproject_path)
90
+ config = process_loaded_project_config(config, errors, warnings)
91
+ federation, federation_config = validate_federation_in_project_config(
92
+ federation, config
93
+ )
94
+ exit_if_no_address(federation_config, f"supernode {command_name}")
95
+ channel = None
96
+ try:
97
+ auth_plugin = try_obtain_cli_auth_plugin(app, federation, federation_config)
98
+ channel = init_channel(app, federation_config, auth_plugin)
99
+ stub = ControlStub(channel)
100
+ typer.echo("📄 Listing all nodes...")
101
+ formatted_nodes = _list_nodes(stub)
102
+ restore_output()
103
+ if output_format == CliOutputFormat.JSON:
104
+ Console().print_json(_to_json(formatted_nodes, verbose=verbose))
105
+ else:
106
+ Console().print(_to_table(formatted_nodes, verbose=verbose))
107
+
108
+ finally:
109
+ if channel:
110
+ channel.close()
111
+ except (typer.Exit, Exception) as err: # pylint: disable=broad-except
112
+ if suppress_output:
113
+ restore_output()
114
+ e_message = captured_output.getvalue()
115
+ print_json_error(e_message, err)
116
+ else:
117
+ typer.secho(
118
+ f"{err}",
119
+ fg=typer.colors.RED,
120
+ bold=True,
121
+ )
122
+ finally:
123
+ if suppress_output:
124
+ restore_output()
125
+ captured_output.close()
126
+
127
+
128
+ def _list_nodes(stub: ControlStub) -> list[_NodeListType]:
129
+ """List all nodes."""
130
+ with flwr_cli_grpc_exc_handler():
131
+ res: ListNodesCliResponse = stub.ListNodesCli(ListNodesCliRequest())
132
+
133
+ return _format_nodes(list(res.nodes_info), res.now)
134
+
135
+
136
+ def _format_nodes(
137
+ nodes_info: list[NodeInfo], now_isoformat: str
138
+ ) -> list[_NodeListType]:
139
+ """Format node information for display."""
140
+
141
+ def _format_datetime(dt_str: Optional[str]) -> str:
142
+ dt = datetime.fromisoformat(dt_str) if dt_str else None
143
+ return isoformat8601_utc(dt).replace("T", " ") if dt else "N/A"
144
+
145
+ formatted_nodes: list[_NodeListType] = []
146
+ # Add rows
147
+ for node in sorted(nodes_info, key=lambda x: datetime.fromisoformat(x.created_at)):
148
+
149
+ # Calculate elapsed times
150
+ elapsed_time_activated = timedelta()
151
+ if node.last_activated_at:
152
+ end_time = datetime.fromisoformat(now_isoformat)
153
+ elapsed_time_activated = end_time - datetime.fromisoformat(
154
+ node.last_activated_at
155
+ )
44
156
 
45
- pyproject_path = app / FAB_CONFIG_FILE if app else None
46
- config, errors, warnings = load_and_validate(path=pyproject_path)
47
- config = process_loaded_project_config(config, errors, warnings)
48
- federation, federation_config = validate_federation_in_project_config(
49
- federation, config
157
+ formatted_nodes.append(
158
+ (
159
+ node.node_id,
160
+ node.owner_aid,
161
+ node.status,
162
+ _format_datetime(node.created_at),
163
+ _format_datetime(node.last_activated_at),
164
+ _format_datetime(node.last_deactivated_at),
165
+ _format_datetime(node.deleted_at),
166
+ format_timedelta(elapsed_time_activated),
167
+ )
168
+ )
169
+
170
+ return formatted_nodes
171
+
172
+
173
+ def _to_table(nodes_info: list[_NodeListType], verbose: bool) -> Table:
174
+ """Format the provided node list to a rich Table."""
175
+ table = Table(header_style="bold cyan", show_lines=True)
176
+
177
+ # Add columns
178
+ table.add_column(
179
+ Text("Node ID", justify="center"), style="bright_white", no_wrap=True
50
180
  )
51
- exit_if_no_address(federation_config, "supernode ls")
181
+ table.add_column(Text("Owner", justify="center"), style="dim white")
182
+ table.add_column(Text("Status", justify="center"))
183
+ table.add_column(Text("Elapsed", justify="center"))
184
+ table.add_column(Text("Status Changed @", justify="center"), style="dim white")
185
+
186
+ for row in nodes_info:
187
+ (
188
+ node_id,
189
+ owner_aid,
190
+ status,
191
+ _,
192
+ last_activated_at,
193
+ last_deactivated_at,
194
+ deleted_at,
195
+ elapse_activated,
196
+ ) = row
197
+
198
+ if status == "online":
199
+ status_style = "green"
200
+ time_at = last_activated_at
201
+ elif status == "offline":
202
+ status_style = "bright_yellow"
203
+ time_at = last_deactivated_at
204
+ elif status == "deleted":
205
+ if not verbose:
206
+ continue
207
+ status_style = "red"
208
+ time_at = deleted_at
209
+ elif status == "created":
210
+ status_style = "blue"
211
+ time_at = "N/A"
212
+ else:
213
+ raise ValueError(f"Unexpected node status '{status}'")
214
+
215
+ formatted_row = (
216
+ f"[bold]{node_id}[/bold]",
217
+ f"{owner_aid}",
218
+ f"[{status_style}]{status}",
219
+ f"[cyan]{elapse_activated}[/cyan]" if status == "online" else "",
220
+ time_at,
221
+ )
222
+ table.add_row(*formatted_row)
223
+
224
+ return table
225
+
226
+
227
+ def _to_json(nodes_info: list[_NodeListType], verbose: bool) -> str:
228
+ """Format node list to a JSON formatted string."""
229
+ nodes_list = []
230
+ for row in nodes_info:
231
+ (
232
+ node_id,
233
+ owner_aid,
234
+ status,
235
+ created_at,
236
+ activated_at,
237
+ deactivated_at,
238
+ deleted_at,
239
+ elapse_activated,
240
+ ) = row
241
+
242
+ if status == "deleted" and not verbose:
243
+ continue
244
+
245
+ nodes_list.append(
246
+ {
247
+ "node-id": node_id,
248
+ "owner-aid": owner_aid,
249
+ "status": status,
250
+ "created-at": created_at,
251
+ "online-at": activated_at,
252
+ "online-elapsed": elapse_activated,
253
+ "offline-at": deactivated_at,
254
+ "deleted-at": deleted_at,
255
+ }
256
+ )
257
+
258
+ return json.dumps({"success": True, "nodes": nodes_list})
@@ -23,10 +23,7 @@ from google.protobuf.message import Message as GrpcMessage
23
23
 
24
24
  from flwr.common import now
25
25
  from flwr.common.constant import PUBLIC_KEY_HEADER, SIGNATURE_HEADER, TIMESTAMP_HEADER
26
- from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
27
- public_key_to_bytes,
28
- sign_message,
29
- )
26
+ from flwr.supercore.primitives.asymmetric import public_key_to_bytes, sign_message
30
27
 
31
28
 
32
29
  class AuthenticateClientInterceptor(grpc.UnaryUnaryClientInterceptor): # type: ignore
@@ -36,9 +36,6 @@ from flwr.common.inflatable_protobuf_utils import (
36
36
  from flwr.common.logger import log
37
37
  from flwr.common.message import Message, remove_content_from_message
38
38
  from flwr.common.retry_invoker import RetryInvoker, _wrap_stub
39
- from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
40
- generate_key_pairs,
41
- )
42
39
  from flwr.common.serde import message_from_proto, message_to_proto, run_from_proto
43
40
  from flwr.common.typing import Fab, Run
44
41
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
@@ -58,6 +55,7 @@ from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
58
55
  from flwr.proto.message_pb2 import ObjectTree # pylint: disable=E0611
59
56
  from flwr.proto.node_pb2 import Node # pylint: disable=E0611
60
57
  from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
58
+ from flwr.supercore.primitives.asymmetric import generate_key_pairs
61
59
 
62
60
  from .client_interceptor import AuthenticateClientInterceptor
63
61
  from .grpc_adapter import GrpcAdapter
@@ -35,14 +35,9 @@ from flwr.common.constant import MessageType
35
35
  from flwr.common.logger import log
36
36
  from flwr.common.secure_aggregation.crypto.shamir import create_shares
37
37
  from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
38
- bytes_to_private_key,
39
- bytes_to_public_key,
40
38
  decrypt,
41
39
  encrypt,
42
- generate_key_pairs,
43
40
  generate_shared_key,
44
- private_key_to_bytes,
45
- public_key_to_bytes,
46
41
  )
47
42
  from flwr.common.secure_aggregation.ndarrays_arithmetic import (
48
43
  factor_combine,
@@ -64,6 +59,13 @@ from flwr.common.secure_aggregation.secaggplus_utils import (
64
59
  share_keys_plaintext_separate,
65
60
  )
66
61
  from flwr.common.typing import ConfigRecordValues
62
+ from flwr.supercore.primitives.asymmetric import (
63
+ bytes_to_private_key,
64
+ bytes_to_public_key,
65
+ generate_key_pairs,
66
+ private_key_to_bytes,
67
+ public_key_to_bytes,
68
+ )
67
69
 
68
70
 
69
71
  @dataclass
@@ -16,57 +16,14 @@
16
16
 
17
17
 
18
18
  import base64
19
- from typing import cast
20
19
 
21
20
  from cryptography.exceptions import InvalidSignature
22
21
  from cryptography.fernet import Fernet
23
- from cryptography.hazmat.primitives import hashes, hmac, serialization
22
+ from cryptography.hazmat.primitives import hashes, hmac
24
23
  from cryptography.hazmat.primitives.asymmetric import ec
25
24
  from cryptography.hazmat.primitives.kdf.hkdf import HKDF
26
25
 
27
26
 
28
- def generate_key_pairs() -> (
29
- tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
30
- ):
31
- """Generate private and public key pairs with Cryptography."""
32
- private_key = ec.generate_private_key(ec.SECP384R1())
33
- public_key = private_key.public_key()
34
- return private_key, public_key
35
-
36
-
37
- def private_key_to_bytes(private_key: ec.EllipticCurvePrivateKey) -> bytes:
38
- """Serialize private key to bytes."""
39
- return private_key.private_bytes(
40
- encoding=serialization.Encoding.PEM,
41
- format=serialization.PrivateFormat.PKCS8,
42
- encryption_algorithm=serialization.NoEncryption(),
43
- )
44
-
45
-
46
- def bytes_to_private_key(private_key_bytes: bytes) -> ec.EllipticCurvePrivateKey:
47
- """Deserialize private key from bytes."""
48
- return cast(
49
- ec.EllipticCurvePrivateKey,
50
- serialization.load_pem_private_key(data=private_key_bytes, password=None),
51
- )
52
-
53
-
54
- def public_key_to_bytes(public_key: ec.EllipticCurvePublicKey) -> bytes:
55
- """Serialize public key to bytes."""
56
- return public_key.public_bytes(
57
- encoding=serialization.Encoding.PEM,
58
- format=serialization.PublicFormat.SubjectPublicKeyInfo,
59
- )
60
-
61
-
62
- def bytes_to_public_key(public_key_bytes: bytes) -> ec.EllipticCurvePublicKey:
63
- """Deserialize public key from bytes."""
64
- return cast(
65
- ec.EllipticCurvePublicKey,
66
- serialization.load_pem_public_key(data=public_key_bytes),
67
- )
68
-
69
-
70
27
  def generate_shared_key(
71
28
  private_key: ec.EllipticCurvePrivateKey, public_key: ec.EllipticCurvePublicKey
72
29
  ) -> bytes:
@@ -117,48 +74,3 @@ def verify_hmac(key: bytes, message: bytes, hmac_value: bytes) -> bool:
117
74
  return True
118
75
  except InvalidSignature:
119
76
  return False
120
-
121
-
122
- def sign_message(private_key: ec.EllipticCurvePrivateKey, message: bytes) -> bytes:
123
- """Sign a message using the provided EC private key.
124
-
125
- Parameters
126
- ----------
127
- private_key : ec.EllipticCurvePrivateKey
128
- The EC private key to sign the message with.
129
- message : bytes
130
- The message to be signed.
131
-
132
- Returns
133
- -------
134
- bytes
135
- The signature of the message.
136
- """
137
- signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
138
- return signature
139
-
140
-
141
- def verify_signature(
142
- public_key: ec.EllipticCurvePublicKey, message: bytes, signature: bytes
143
- ) -> bool:
144
- """Verify a signature against a message using the provided EC public key.
145
-
146
- Parameters
147
- ----------
148
- public_key : ec.EllipticCurvePublicKey
149
- The EC public key to verify the signature.
150
- message : bytes
151
- The original message.
152
- signature : bytes
153
- The signature to verify.
154
-
155
- Returns
156
- -------
157
- bool
158
- True if the signature is valid, False otherwise.
159
- """
160
- try:
161
- public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
162
- return True
163
- except InvalidSignature:
164
- return False
flwr/proto/node_pb2.py CHANGED
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
14
14
 
15
15
 
16
16
 
17
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/node.proto\x12\nflwr.proto\"\x17\n\x04Node\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\xb6\x01\n\x08NodeInfo\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\x12\x11\n\towner_aid\x18\x02 \x01(\t\x12\x12\n\ncreated_at\x18\x03 \x01(\t\x12\x14\n\x0c\x61\x63tivated_at\x18\x04 \x01(\t\x12\x16\n\x0e\x64\x65\x61\x63tivated_at\x18\x05 \x01(\t\x12\x12\n\ndeleted_at\x18\x06 \x01(\t\x12\x14\n\x0conline_until\x18\x07 \x01(\x02\x12\x1a\n\x12heartbeat_interval\x18\x08 \x01(\x02\x62\x06proto3')
17
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/node.proto\x12\nflwr.proto\"\x17\n\x04Node\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\xd0\x01\n\x08NodeInfo\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\x12\x11\n\towner_aid\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x12\n\ncreated_at\x18\x04 \x01(\t\x12\x19\n\x11last_activated_at\x18\x05 \x01(\t\x12\x1b\n\x13last_deactivated_at\x18\x06 \x01(\t\x12\x12\n\ndeleted_at\x18\x07 \x01(\t\x12\x14\n\x0conline_until\x18\x08 \x01(\x02\x12\x1a\n\x12heartbeat_interval\x18\t \x01(\x02\x62\x06proto3')
18
18
 
19
19
  _globals = globals()
20
20
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -24,5 +24,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
24
24
  _globals['_NODE']._serialized_start=37
25
25
  _globals['_NODE']._serialized_end=60
26
26
  _globals['_NODEINFO']._serialized_start=63
27
- _globals['_NODEINFO']._serialized_end=245
27
+ _globals['_NODEINFO']._serialized_end=271
28
28
  # @@protoc_insertion_point(module_scope)
flwr/proto/node_pb2.pyi CHANGED
@@ -25,17 +25,19 @@ class NodeInfo(google.protobuf.message.Message):
25
25
  DESCRIPTOR: google.protobuf.descriptor.Descriptor
26
26
  NODE_ID_FIELD_NUMBER: builtins.int
27
27
  OWNER_AID_FIELD_NUMBER: builtins.int
28
+ STATUS_FIELD_NUMBER: builtins.int
28
29
  CREATED_AT_FIELD_NUMBER: builtins.int
29
- ACTIVATED_AT_FIELD_NUMBER: builtins.int
30
- DEACTIVATED_AT_FIELD_NUMBER: builtins.int
30
+ LAST_ACTIVATED_AT_FIELD_NUMBER: builtins.int
31
+ LAST_DEACTIVATED_AT_FIELD_NUMBER: builtins.int
31
32
  DELETED_AT_FIELD_NUMBER: builtins.int
32
33
  ONLINE_UNTIL_FIELD_NUMBER: builtins.int
33
34
  HEARTBEAT_INTERVAL_FIELD_NUMBER: builtins.int
34
35
  node_id: builtins.int
35
36
  owner_aid: typing.Text
37
+ status: typing.Text
36
38
  created_at: typing.Text
37
- activated_at: typing.Text
38
- deactivated_at: typing.Text
39
+ last_activated_at: typing.Text
40
+ last_deactivated_at: typing.Text
39
41
  deleted_at: typing.Text
40
42
  online_until: builtins.float
41
43
  heartbeat_interval: builtins.float
@@ -43,12 +45,13 @@ class NodeInfo(google.protobuf.message.Message):
43
45
  *,
44
46
  node_id: builtins.int = ...,
45
47
  owner_aid: typing.Text = ...,
48
+ status: typing.Text = ...,
46
49
  created_at: typing.Text = ...,
47
- activated_at: typing.Text = ...,
48
- deactivated_at: typing.Text = ...,
50
+ last_activated_at: typing.Text = ...,
51
+ last_deactivated_at: typing.Text = ...,
49
52
  deleted_at: typing.Text = ...,
50
53
  online_until: builtins.float = ...,
51
54
  heartbeat_interval: builtins.float = ...,
52
55
  ) -> None: ...
53
- def ClearField(self, field_name: typing_extensions.Literal["activated_at",b"activated_at","created_at",b"created_at","deactivated_at",b"deactivated_at","deleted_at",b"deleted_at","heartbeat_interval",b"heartbeat_interval","node_id",b"node_id","online_until",b"online_until","owner_aid",b"owner_aid"]) -> None: ...
56
+ def ClearField(self, field_name: typing_extensions.Literal["created_at",b"created_at","deleted_at",b"deleted_at","heartbeat_interval",b"heartbeat_interval","last_activated_at",b"last_activated_at","last_deactivated_at",b"last_deactivated_at","node_id",b"node_id","online_until",b"online_until","owner_aid",b"owner_aid","status",b"status"]) -> None: ...
54
57
  global___NodeInfo = NodeInfo
flwr/server/app.py CHANGED
@@ -59,9 +59,6 @@ from flwr.common.event_log_plugin import EventLogWriterPlugin
59
59
  from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
60
60
  from flwr.common.grpc import generic_create_grpc_server
61
61
  from flwr.common.logger import log
62
- from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
63
- public_key_to_bytes,
64
- )
65
62
  from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611
66
63
  add_FleetServicer_to_server,
67
64
  )
@@ -70,6 +67,7 @@ from flwr.server.fleet_event_log_interceptor import FleetEventLogInterceptor
70
67
  from flwr.supercore.ffs import FfsFactory
71
68
  from flwr.supercore.grpc_health import add_args_health, run_health_server_grpc_no_tls
72
69
  from flwr.supercore.object_store import ObjectStoreFactory
70
+ from flwr.supercore.primitives.asymmetric import public_key_to_bytes
73
71
  from flwr.superlink.artifact_provider import ArtifactProvider
74
72
  from flwr.superlink.auth_plugin import ControlAuthnPlugin, ControlAuthzPlugin
75
73
  from flwr.superlink.servicer.control import run_control_api_grpc
@@ -29,15 +29,12 @@ from flwr.common.constant import (
29
29
  TIMESTAMP_HEADER,
30
30
  TIMESTAMP_TOLERANCE,
31
31
  )
32
- from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
33
- bytes_to_public_key,
34
- verify_signature,
35
- )
36
32
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
37
33
  CreateNodeRequest,
38
34
  CreateNodeResponse,
39
35
  )
40
36
  from flwr.server.superlink.linkstate import LinkStateFactory
37
+ from flwr.supercore.primitives.asymmetric import bytes_to_public_key, verify_signature
41
38
 
42
39
  MIN_TIMESTAMP_DIFF = -SYSTEM_TIME_TOLERANCE
43
40
  MAX_TIMESTAMP_DIFF = TIMESTAMP_TOLERANCE + SYSTEM_TIME_TOLERANCE
@@ -35,8 +35,6 @@ from flwr.common import (
35
35
  )
36
36
  from flwr.common.secure_aggregation.crypto.shamir import combine_shares
37
37
  from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
38
- bytes_to_private_key,
39
- bytes_to_public_key,
40
38
  generate_shared_key,
41
39
  )
42
40
  from flwr.common.secure_aggregation.ndarrays_arithmetic import (
@@ -56,6 +54,10 @@ from flwr.common.secure_aggregation.secaggplus_utils import pseudo_rand_gen
56
54
  from flwr.server.client_proxy import ClientProxy
57
55
  from flwr.server.compat.legacy_context import LegacyContext
58
56
  from flwr.server.grid import Grid
57
+ from flwr.supercore.primitives.asymmetric import (
58
+ bytes_to_private_key,
59
+ bytes_to_public_key,
60
+ )
59
61
 
60
62
  from ..constant import MAIN_CONFIGS_RECORD, MAIN_PARAMS_RECORD
61
63
  from ..constant import Key as WorkflowKey
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 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
+ """Cryptographic primitives for the Flower infrastructure."""
@@ -0,0 +1,109 @@
1
+ # Copyright 2025 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
+ """Asymmetric cryptography utilities."""
16
+
17
+
18
+ from typing import cast
19
+
20
+ from cryptography.exceptions import InvalidSignature
21
+ from cryptography.hazmat.primitives import hashes, serialization
22
+ from cryptography.hazmat.primitives.asymmetric import ec
23
+
24
+
25
+ def generate_key_pairs() -> (
26
+ tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]
27
+ ):
28
+ """Generate private and public key pairs with Cryptography."""
29
+ private_key = ec.generate_private_key(ec.SECP384R1())
30
+ public_key = private_key.public_key()
31
+ return private_key, public_key
32
+
33
+
34
+ def private_key_to_bytes(private_key: ec.EllipticCurvePrivateKey) -> bytes:
35
+ """Serialize private key to bytes."""
36
+ return private_key.private_bytes(
37
+ encoding=serialization.Encoding.PEM,
38
+ format=serialization.PrivateFormat.PKCS8,
39
+ encryption_algorithm=serialization.NoEncryption(),
40
+ )
41
+
42
+
43
+ def bytes_to_private_key(private_key_bytes: bytes) -> ec.EllipticCurvePrivateKey:
44
+ """Deserialize private key from bytes."""
45
+ return cast(
46
+ ec.EllipticCurvePrivateKey,
47
+ serialization.load_pem_private_key(data=private_key_bytes, password=None),
48
+ )
49
+
50
+
51
+ def public_key_to_bytes(public_key: ec.EllipticCurvePublicKey) -> bytes:
52
+ """Serialize public key to bytes."""
53
+ return public_key.public_bytes(
54
+ encoding=serialization.Encoding.PEM,
55
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
56
+ )
57
+
58
+
59
+ def bytes_to_public_key(public_key_bytes: bytes) -> ec.EllipticCurvePublicKey:
60
+ """Deserialize public key from bytes."""
61
+ return cast(
62
+ ec.EllipticCurvePublicKey,
63
+ serialization.load_pem_public_key(data=public_key_bytes),
64
+ )
65
+
66
+
67
+ def sign_message(private_key: ec.EllipticCurvePrivateKey, message: bytes) -> bytes:
68
+ """Sign a message using the provided EC private key.
69
+
70
+ Parameters
71
+ ----------
72
+ private_key : ec.EllipticCurvePrivateKey
73
+ The EC private key to sign the message with.
74
+ message : bytes
75
+ The message to be signed.
76
+
77
+ Returns
78
+ -------
79
+ bytes
80
+ The signature of the message.
81
+ """
82
+ signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
83
+ return signature
84
+
85
+
86
+ def verify_signature(
87
+ public_key: ec.EllipticCurvePublicKey, message: bytes, signature: bytes
88
+ ) -> bool:
89
+ """Verify a signature against a message using the provided EC public key.
90
+
91
+ Parameters
92
+ ----------
93
+ public_key : ec.EllipticCurvePublicKey
94
+ The EC public key to verify the signature.
95
+ message : bytes
96
+ The original message.
97
+ signature : bytes
98
+ The signature to verify.
99
+
100
+ Returns
101
+ -------
102
+ bool
103
+ True if the signature is valid, False otherwise.
104
+ """
105
+ try:
106
+ public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
107
+ return True
108
+ except InvalidSignature:
109
+ return False
@@ -18,6 +18,7 @@
18
18
  import hashlib
19
19
  import time
20
20
  from collections.abc import Generator
21
+ from datetime import timedelta
21
22
  from logging import ERROR, INFO
22
23
  from typing import Any, Optional, cast
23
24
 
@@ -65,6 +66,7 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
65
66
  StreamLogsRequest,
66
67
  StreamLogsResponse,
67
68
  )
69
+ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
68
70
  from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
69
71
  from flwr.supercore.ffs import FfsFactory
70
72
  from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
@@ -418,7 +420,61 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
418
420
  ) -> ListNodesCliResponse:
419
421
  """List all SuperNodes."""
420
422
  log(INFO, "ControlServicer.ListNodesCli")
421
- return ListNodesCliResponse()
423
+
424
+ nodes_info = []
425
+ # A node created (but not connected)
426
+ nodes_info.append(
427
+ NodeInfo(
428
+ node_id=15390646978706312628,
429
+ owner_aid="owner_aid_1",
430
+ status="created",
431
+ created_at=(now()).isoformat(),
432
+ last_activated_at="",
433
+ last_deactivated_at="",
434
+ deleted_at="",
435
+ )
436
+ )
437
+
438
+ # A node created and connected
439
+ nodes_info.append(
440
+ NodeInfo(
441
+ node_id=2941141058168602545,
442
+ owner_aid="owner_aid_2",
443
+ status="online",
444
+ created_at=(now()).isoformat(),
445
+ last_activated_at=(now() + timedelta(hours=0.5)).isoformat(),
446
+ last_deactivated_at="",
447
+ deleted_at="",
448
+ )
449
+ )
450
+
451
+ # A node created and deleted (never connected)
452
+ nodes_info.append(
453
+ NodeInfo(
454
+ node_id=906971720890549292,
455
+ owner_aid="owner_aid_3",
456
+ status="deleted",
457
+ created_at=(now()).isoformat(),
458
+ last_activated_at="",
459
+ last_deactivated_at="",
460
+ deleted_at=(now() + timedelta(hours=1)).isoformat(),
461
+ )
462
+ )
463
+
464
+ # A node created, deactivate and then deleted
465
+ nodes_info.append(
466
+ NodeInfo(
467
+ node_id=1781174086018058152,
468
+ owner_aid="owner_aid_4",
469
+ status="offline",
470
+ created_at=(now()).isoformat(),
471
+ last_activated_at=(now() + timedelta(hours=0.5)).isoformat(),
472
+ last_deactivated_at=(now() + timedelta(hours=1)).isoformat(),
473
+ deleted_at=(now() + timedelta(hours=1.5)).isoformat(),
474
+ )
475
+ )
476
+
477
+ return ListNodesCliResponse(nodes_info=nodes_info, now=now().isoformat())
422
478
 
423
479
 
424
480
  def _create_list_runs_response(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: flwr-nightly
3
- Version: 1.23.0.dev20251005
3
+ Version: 1.23.0.dev20251007
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
@@ -89,7 +89,7 @@ flwr/cli/stop.py,sha256=TR9F61suTxNUzGIktUdoBhXwdRtCdvzGhy3qCuvcfBg,5000
89
89
  flwr/cli/supernode/__init__.py,sha256=DVrTcyCg9NFll6glPLAAA6WPi7boxu6pFY_PRqIyHMk,893
90
90
  flwr/cli/supernode/create.py,sha256=MloB7Rr-CE_37jH5lFoM8OsaSPUzWt0TSSB_GMwND5E,1959
91
91
  flwr/cli/supernode/delete.py,sha256=_oCfBnoeUFPLEq_1EAqQp_CHEdzIEP2N_vHZsFTgWn0,1958
92
- flwr/cli/supernode/ls.py,sha256=j850KQEMpS0rdpL8m9icx7DlRMDRtns6I_iTwUudC_0,1801
92
+ flwr/cli/supernode/ls.py,sha256=KZZHQczmXi4vmgMJlBntD893fYQ9a7ZJum-8K_BPWAw,8550
93
93
  flwr/cli/utils.py,sha256=3bCu_MndtJD2S5MPG9VKi7SxG0No8S651pcfBVb29Y8,14397
94
94
  flwr/client/__init__.py,sha256=Q0MIF442vLIGSkcwHKq_sIfECQynLARJrumAscq2Q6E,1241
95
95
  flwr/client/client.py,sha256=3HAchxvknKG9jYbB7swNyDj-e5vUWDuMKoLvbT7jCVM,7895
@@ -97,8 +97,8 @@ flwr/client/dpfedavg_numpy_client.py,sha256=3hul067cT2E9jBhzp7bFnFAZ_D2nWcIUEdHY
97
97
  flwr/client/grpc_adapter_client/__init__.py,sha256=RQWP5mFPROLHKgombiRvPXVWSoVrQ81wvZm0-lOuuBA,742
98
98
  flwr/client/grpc_adapter_client/connection.py,sha256=JGv02EjSOAG1E5BRUD4lwXc1LLiYJ0OhInvp31qx1cU,4404
99
99
  flwr/client/grpc_rere_client/__init__.py,sha256=i7iS0Lt8B7q0E2L72e4F_YrKm6ClRKnd71PNA6PW2O0,752
100
- flwr/client/grpc_rere_client/client_interceptor.py,sha256=zFaVHw6AxeNO-7eCKKb-RxrPa7zbM5Z-2-1Efc4adQY,2451
101
- flwr/client/grpc_rere_client/connection.py,sha256=uLcXAJoHHLVm4SVR3oFCpdqHrZJcT_kGqfC1g8reXAM,13008
100
+ flwr/client/grpc_rere_client/client_interceptor.py,sha256=65MAcKpA7Sv2FRYGgyCQosnZlh7cSIlPn2lMqh-cqZw,2416
101
+ flwr/client/grpc_rere_client/connection.py,sha256=OSRsZxeDoGE2avTStQMTV4z2o5ezO-nBrwy6qvCs430,12977
102
102
  flwr/client/grpc_rere_client/grpc_adapter.py,sha256=dLGB5GriszAmtgvuFGuz_F7rIwpzLfDxhJ7T3Un-Ce0,6694
103
103
  flwr/client/message_handler/__init__.py,sha256=0lyljDVqre3WljiZbPcwCCf8GiIaSVI_yo_ylEyPwSE,719
104
104
  flwr/client/message_handler/message_handler.py,sha256=X9SXX6et97Lw9_DGD93HKsEBGNjXClcFgc_5aLK0oiU,6541
@@ -108,7 +108,7 @@ flwr/client/mod/comms_mods.py,sha256=ehp0AF-nGXxQOtm4G5Ts_7RTP04x5Y8PmA1Myfrjuro
108
108
  flwr/client/mod/localdp_mod.py,sha256=GzVQrydejUfFDgnIIEIwrUU2DgvhN990LUnoydm9Tpo,5008
109
109
  flwr/client/mod/secure_aggregation/__init__.py,sha256=k8HYXvqu3pd_V3eZ0_5wwH52o-C6XJCPbT4n4anYyWY,849
110
110
  flwr/client/mod/secure_aggregation/secagg_mod.py,sha256=y54DvpM2-DWUiEYqgwZ0DssC1VVRCJEfGgST7O3OcwM,1095
111
- flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=aKqjZCrikF73y3E-7h40u-s0H6-hmyd4Ah1LHnrLrIg,19661
111
+ flwr/client/mod/secure_aggregation/secaggplus_mod.py,sha256=Ib-HlhZOXB6-hAWGZ0IpbK7mIr1A5j2qpc8IRu-MxLU,19714
112
112
  flwr/client/mod/utils.py,sha256=FUgD2TfcWqSeF6jUKZ4i6Ke56U4Nrv85AeVb93s6R9g,1201
113
113
  flwr/client/numpy_client.py,sha256=Qq6ghsIAop2slKqAfgiI5NiHJ4LIxGmrik3Ror4_XVc,9581
114
114
  flwr/client/rest_client/__init__.py,sha256=MBiuK62hj439m9rtwSwI184Hth6Tt5GbmpNMyl3zkZY,735
@@ -163,7 +163,7 @@ flwr/common/retry_invoker.py,sha256=uQeDcgoTgmFwhJ0mkDE2eNz2acF9eShaqMOO5boGrPQ,
163
163
  flwr/common/secure_aggregation/__init__.py,sha256=MgW6uHGhyFLBAYQqa1Vzs5n2Gc0d4yEw1_NmerFir70,731
164
164
  flwr/common/secure_aggregation/crypto/__init__.py,sha256=5E4q4-Fw0CNz4tLah_QHj7m_rDeM4ucHcFlPWB_Na3Q,738
165
165
  flwr/common/secure_aggregation/crypto/shamir.py,sha256=N8pPa5cEksowNoAqfFm5SP3IuxuVi9GGMa3JOtPniQY,3954
166
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=H6Rlpr4i-VAwOMNdyfH33uDpXNsquiK1gKrOixtKqek,5333
166
+ flwr/common/secure_aggregation/crypto/symmetric_encryption.py,sha256=fc8h7ST3mSq4d0u89vAdcxSsfspdsmOpQRba0VBn3zg,2718
167
167
  flwr/common/secure_aggregation/ndarrays_arithmetic.py,sha256=TrggOlizlny3V2KS7-3mpDr33En8UmW9oJcxcS_6oh0,3013
168
168
  flwr/common/secure_aggregation/quantization.py,sha256=ssFZpiRyj9ltIh0Ai3vGkDqWFO4SoqgoD1mDU9XqMEM,2400
169
169
  flwr/common/secure_aggregation/secaggplus_constants.py,sha256=dGYhWOBMMDJcQH4_tQNC8-Efqm-ecEUNN9ANz59UnCk,2182
@@ -223,8 +223,8 @@ flwr/proto/message_pb2.py,sha256=giymevXYEUdpIO-3A0XKsmRabXW1xSz0sIo5oOlbQ8Y,519
223
223
  flwr/proto/message_pb2.pyi,sha256=EzXZHy2mtabofrd_ZgKSI6M4QH-soIaRZIZBPwBGPv0,11260
224
224
  flwr/proto/message_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
225
225
  flwr/proto/message_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
226
- flwr/proto/node_pb2.py,sha256=gvFzmkEBB4Prp9y27RZKDoy1O5r56GqN-wpCdjoW5BA,1508
227
- flwr/proto/node_pb2.pyi,sha256=THEURTmlD-23XOULcu2AH6Mvy7pl2N9eWUSntCutO8o,2060
226
+ flwr/proto/node_pb2.py,sha256=_5UD-yH9Y-qtAP-gO3LZeDH_WoSFfjXmNvngsb9G-YM,1534
227
+ flwr/proto/node_pb2.pyi,sha256=kmLGmDIqudRtveUop_uVU6w696ahPB_b-O94k3HQIpU,2226
228
228
  flwr/proto/node_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
229
229
  flwr/proto/node_pb2_grpc.pyi,sha256=ff2TSiLVnG6IVQcTGzb2DIH3XRSoAvAo_RMcvbMFyc0,76
230
230
  flwr/proto/recorddict_pb2.py,sha256=eVkcnxMTFa3rvknRNiFuJ8z8xxPqgw7bV04aFiTe1j4,5290
@@ -249,7 +249,7 @@ flwr/proto/transport_pb2_grpc.py,sha256=vLN3EHtx2aEEMCO4f1Upu-l27BPzd3-5pV-u8wPc
249
249
  flwr/proto/transport_pb2_grpc.pyi,sha256=AGXf8RiIiW2J5IKMlm_3qT3AzcDa4F3P5IqUjve_esA,766
250
250
  flwr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
251
251
  flwr/server/__init__.py,sha256=LQQHiuL2jy7TpNaKastRdGsexlxSt5ZWAQNVqitDnrY,1598
252
- flwr/server/app.py,sha256=ESJ55Gl77zptQC10YwZg7WyK8Zr6zisI0rPtQJrx_-w,30698
252
+ flwr/server/app.py,sha256=Axmw-BnqK3Tv2ObDDuMrdQTAFOfEWcxehKhzeAXtBNY,30667
253
253
  flwr/server/client_manager.py,sha256=5jCGavVli7XdupvWWo7ru3PdFTlRU8IGvHFSSoUVLRs,6227
254
254
  flwr/server/client_proxy.py,sha256=sv0E9AldBYOvc3pusqFh-GnyreeMfsXQ1cuTtxTq_wY,2399
255
255
  flwr/server/compat/__init__.py,sha256=0IsttWvY15qO98_1GyzVC-vR1e_ZPXOdu2qUlOkYMPE,886
@@ -306,7 +306,7 @@ flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=iSf0mbBAlig7G6
306
306
  flwr/server/superlink/fleet/grpc_bidi/grpc_server.py,sha256=OsS-6GgCIzMMZDVu5Y-OKjynHVUrpdc_5OrtuB-IbU0,5174
307
307
  flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=ahDJJ1e-lDxBpeBMgPk7YZt2wB38_QltcpOC0gLbpFs,758
308
308
  flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=X7-z4oReIH5ghMfmMXML3SSpa2bhRsuIvt2OZs82BUk,8675
309
- flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=9_RaYWMqFdpFi8QcE7Nv8-pRjWJ2dLHxezrwhd1tAYk,6845
309
+ flwr/server/superlink/fleet/grpc_rere/server_interceptor.py,sha256=Kr0CY2CObzvbUU1HB1aZa2CDwrD-oZYCt-O4eoZtvxE,6810
310
310
  flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
311
311
  flwr/server/superlink/fleet/message_handler/message_handler.py,sha256=3Wg20bFo1tZfkzTQUerPVSHXyOuUqNuitEib3W_Dy-U,8691
312
312
  flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=Lzc93nA7tDqoy-zRUaPG316oqFiZX1HUCL5ELaXY_xw,735
@@ -338,7 +338,7 @@ flwr/server/workflow/constant.py,sha256=4t2MTvOJUlByJfuD9yZjl8Xn5SwBJQDBi15Dphzt
338
338
  flwr/server/workflow/default_workflows.py,sha256=RlD26dXbSksY-23f3ZspnN1YU1DOhDYOchMTEQvqFrM,13900
339
339
  flwr/server/workflow/secure_aggregation/__init__.py,sha256=vGkycLb65CxdaMkKsANxQE6AS4urfZKvwcS3r1Vln_c,880
340
340
  flwr/server/workflow/secure_aggregation/secagg_workflow.py,sha256=b_pKk7gmbahwyj0ftOOLXvu-AMtRHEc82N9PJTEO8dc,5839
341
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=DkayCsnlAya6Y2PZsueLgoUCMRtV-GbnW08RfWx_SXM,29460
341
+ flwr/server/workflow/secure_aggregation/secaggplus_workflow.py,sha256=SqAIIOsETxay-SCwGnhOUO3dkxanQaqdZD2Ex7M-wvE,29513
342
342
  flwr/serverapp/__init__.py,sha256=ZujKNXULwhWYQhFnxOOT5Wi9MRq2JCWFhAAj7ouiQ78,884
343
343
  flwr/serverapp/exception.py,sha256=5cuH-2AafvihzosWDdDjuMmHdDqZ1XxHvCqZXNBVklw,1334
344
344
  flwr/serverapp/strategy/__init__.py,sha256=dezK2TKSffjjBVXW18ATRxJLTuQ7I2M1dPuNi5y-_6c,1968
@@ -392,6 +392,8 @@ flwr/supercore/object_store/in_memory_object_store.py,sha256=CGY43syxDGrUPcdOzRH
392
392
  flwr/supercore/object_store/object_store.py,sha256=J-rI3X7ET-F6dqOyM-UfHKCCQtPJ_EnYW62H_1txts0,5252
393
393
  flwr/supercore/object_store/object_store_factory.py,sha256=QVwE2ywi7vsj2iKfvWWnNw3N_I7Rz91NUt2RpcbJ7iM,1527
394
394
  flwr/supercore/object_store/utils.py,sha256=DcPbrb9PenloAPoQRiKiXX9DrDfpXcSyY0cZpgn4PeQ,1680
395
+ flwr/supercore/primitives/__init__.py,sha256=Tx8GOjnmMo8Y74RsDGrMpfr-E0Nl8dcUDF784_ge6F8,745
396
+ flwr/supercore/primitives/asymmetric.py,sha256=g5NnE_4nnHjqOFCtSi3hVy2CdgQ_G9gcfbOtx-Kmkm0,3502
395
397
  flwr/supercore/superexec/__init__.py,sha256=XKX208hZ6a9gZ4KT9kMqfpCtp_8VGxekzKFfHSu2esQ,707
396
398
  flwr/supercore/superexec/plugin/__init__.py,sha256=GNwq8uNdE8RB7ywEFRAvKjLFzgS3YXgz39-HBGsemWw,1035
397
399
  flwr/supercore/superexec/plugin/base_exec_plugin.py,sha256=fL-Ufc9Dp56OhWOzNSJUc7HumbkuSDYqZKwde2opG4g,2074
@@ -413,7 +415,7 @@ flwr/superlink/servicer/control/control_account_auth_interceptor.py,sha256=Tbi4W
413
415
  flwr/superlink/servicer/control/control_event_log_interceptor.py,sha256=5uBl6VcJlUOgCF0d4kmsmJc1Rs1qxyouaZv0-uH2axs,5969
414
416
  flwr/superlink/servicer/control/control_grpc.py,sha256=xajQdX9WuKJmyXZJOaMZKeiSwgSCrile1NOErOiXR5w,4270
415
417
  flwr/superlink/servicer/control/control_license_interceptor.py,sha256=T3AzmRt-PPwyTq3hrdpmZHQd5_CpPOk7TtnFZrB-JRY,3349
416
- flwr/superlink/servicer/control/control_servicer.py,sha256=KUj45Fu5d6jugLWBkWzPre0l3TqD13NYQCWr-KeiSZ0,17134
418
+ flwr/superlink/servicer/control/control_servicer.py,sha256=DJaHBHNRS_9UVuqAk9ELu-1IfmFNVbt3SilYG1awaXQ,19092
417
419
  flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
418
420
  flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
419
421
  flwr/supernode/cli/flower_supernode.py,sha256=7aBm0z03OU-npVd1onLCvUotyhSvlZLxAnFkGVMhZcw,8670
@@ -428,7 +430,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
428
430
  flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
429
431
  flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=nIHRu38EWK-rpNOkcgBRAAKwYQQWFeCwu0lkO7OPZGQ,10239
430
432
  flwr/supernode/start_client_internal.py,sha256=Y9S1-QlO2WP6eo4JvWzIpfaCoh2aoE7bjEYyxNNnlyg,20777
431
- flwr_nightly-1.23.0.dev20251005.dist-info/METADATA,sha256=0zIDexdYWqIJTLvW_ebh5iN-ypv6TkI6y3HSRUmVjMA,14559
432
- flwr_nightly-1.23.0.dev20251005.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
433
- flwr_nightly-1.23.0.dev20251005.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
434
- flwr_nightly-1.23.0.dev20251005.dist-info/RECORD,,
433
+ flwr_nightly-1.23.0.dev20251007.dist-info/METADATA,sha256=BTnTS1rjPnKyvChtLD-tLZYUSZbQdqpXxZOVL-ghaE4,14559
434
+ flwr_nightly-1.23.0.dev20251007.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
435
+ flwr_nightly-1.23.0.dev20251007.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
436
+ flwr_nightly-1.23.0.dev20251007.dist-info/RECORD,,