flwr-nightly 1.15.0.dev20250114__py3-none-any.whl → 1.15.0.dev20250123__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 (82) hide show
  1. flwr/cli/config_utils.py +23 -146
  2. flwr/cli/constant.py +27 -0
  3. flwr/cli/install.py +1 -1
  4. flwr/cli/log.py +17 -2
  5. flwr/cli/login/login.py +9 -1
  6. flwr/cli/ls.py +10 -2
  7. flwr/cli/run/run.py +20 -10
  8. flwr/cli/stop.py +9 -1
  9. flwr/client/app.py +23 -43
  10. flwr/client/clientapp/app.py +4 -6
  11. flwr/client/clientapp/utils.py +1 -1
  12. flwr/client/grpc_client/connection.py +0 -6
  13. flwr/client/grpc_rere_client/client_interceptor.py +19 -125
  14. flwr/client/grpc_rere_client/connection.py +10 -0
  15. flwr/client/rest_client/connection.py +12 -3
  16. flwr/client/supernode/app.py +14 -20
  17. flwr/common/auth_plugin/auth_plugin.py +1 -0
  18. flwr/common/config.py +152 -15
  19. flwr/common/constant.py +9 -8
  20. flwr/common/exit/__init__.py +24 -0
  21. flwr/common/exit/exit.py +99 -0
  22. flwr/common/exit/exit_code.py +93 -0
  23. flwr/common/exit_handlers.py +24 -10
  24. flwr/common/grpc.py +7 -0
  25. flwr/common/logger.py +1 -1
  26. flwr/common/serde.py +6 -4
  27. flwr/proto/clientappio_pb2.py +13 -3
  28. flwr/proto/clientappio_pb2_grpc.py +63 -12
  29. flwr/proto/error_pb2.py +13 -3
  30. flwr/proto/error_pb2_grpc.py +20 -0
  31. flwr/proto/exec_pb2.py +15 -5
  32. flwr/proto/exec_pb2_grpc.py +105 -24
  33. flwr/proto/fab_pb2.py +13 -3
  34. flwr/proto/fab_pb2_grpc.py +20 -0
  35. flwr/proto/fleet_pb2.py +15 -5
  36. flwr/proto/fleet_pb2_grpc.py +147 -36
  37. flwr/proto/grpcadapter_pb2.py +14 -4
  38. flwr/proto/grpcadapter_pb2_grpc.py +35 -4
  39. flwr/proto/log_pb2.py +13 -3
  40. flwr/proto/log_pb2_grpc.py +20 -0
  41. flwr/proto/message_pb2.py +15 -5
  42. flwr/proto/message_pb2_grpc.py +20 -0
  43. flwr/proto/node_pb2.py +15 -5
  44. flwr/proto/node_pb2.pyi +1 -4
  45. flwr/proto/node_pb2_grpc.py +20 -0
  46. flwr/proto/recordset_pb2.py +18 -8
  47. flwr/proto/recordset_pb2_grpc.py +20 -0
  48. flwr/proto/run_pb2.py +16 -6
  49. flwr/proto/run_pb2_grpc.py +20 -0
  50. flwr/proto/serverappio_pb2.py +32 -14
  51. flwr/proto/serverappio_pb2.pyi +56 -0
  52. flwr/proto/serverappio_pb2_grpc.py +261 -44
  53. flwr/proto/serverappio_pb2_grpc.pyi +20 -0
  54. flwr/proto/simulationio_pb2.py +13 -3
  55. flwr/proto/simulationio_pb2_grpc.py +105 -24
  56. flwr/proto/task_pb2.py +13 -3
  57. flwr/proto/task_pb2_grpc.py +20 -0
  58. flwr/proto/transport_pb2.py +20 -10
  59. flwr/proto/transport_pb2_grpc.py +35 -4
  60. flwr/server/app.py +40 -11
  61. flwr/server/compat/app_utils.py +0 -1
  62. flwr/server/compat/driver_client_proxy.py +1 -2
  63. flwr/server/driver/grpc_driver.py +5 -2
  64. flwr/server/driver/inmemory_driver.py +2 -1
  65. flwr/server/serverapp/app.py +5 -6
  66. flwr/server/superlink/driver/serverappio_servicer.py +110 -6
  67. flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +20 -88
  68. flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +95 -169
  69. flwr/server/superlink/fleet/message_handler/message_handler.py +4 -5
  70. flwr/server/superlink/fleet/rest_rere/rest_api.py +2 -3
  71. flwr/server/superlink/linkstate/in_memory_linkstate.py +14 -26
  72. flwr/server/superlink/linkstate/linkstate.py +5 -18
  73. flwr/server/superlink/linkstate/sqlite_linkstate.py +30 -70
  74. flwr/server/superlink/linkstate/utils.py +18 -8
  75. flwr/server/utils/validator.py +9 -34
  76. flwr/simulation/app.py +4 -6
  77. flwr/simulation/legacy_app.py +4 -2
  78. {flwr_nightly-1.15.0.dev20250114.dist-info → flwr_nightly-1.15.0.dev20250123.dist-info}/METADATA +4 -4
  79. {flwr_nightly-1.15.0.dev20250114.dist-info → flwr_nightly-1.15.0.dev20250123.dist-info}/RECORD +82 -78
  80. {flwr_nightly-1.15.0.dev20250114.dist-info → flwr_nightly-1.15.0.dev20250123.dist-info}/LICENSE +0 -0
  81. {flwr_nightly-1.15.0.dev20250114.dist-info → flwr_nightly-1.15.0.dev20250123.dist-info}/WHEEL +0 -0
  82. {flwr_nightly-1.15.0.dev20250114.dist-info → flwr_nightly-1.15.0.dev20250123.dist-info}/entry_points.txt +0 -0
@@ -17,13 +17,12 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- import sys
21
20
  from collections.abc import Awaitable
22
21
  from typing import Callable, TypeVar, cast
23
22
 
24
23
  from google.protobuf.message import Message as GrpcMessage
25
24
 
26
- from flwr.common.constant import MISSING_EXTRA_REST
25
+ from flwr.common.exit import ExitCode, flwr_exit
27
26
  from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
28
27
  from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611
29
28
  CreateNodeRequest,
@@ -55,7 +54,7 @@ try:
55
54
  from starlette.responses import Response
56
55
  from starlette.routing import Route
57
56
  except ModuleNotFoundError:
58
- sys.exit(MISSING_EXTRA_REST)
57
+ flwr_exit(ExitCode.COMMON_MISSING_EXTRA_REST)
59
58
 
60
59
 
61
60
  GrpcRequest = TypeVar("GrpcRequest", bound=GrpcMessage)
@@ -28,6 +28,7 @@ from flwr.common.constant import (
28
28
  MESSAGE_TTL_TOLERANCE,
29
29
  NODE_ID_NUM_BYTES,
30
30
  RUN_ID_NUM_BYTES,
31
+ SUPERLINK_NODE_ID,
31
32
  Status,
32
33
  )
33
34
  from flwr.common.record import ConfigsRecord
@@ -90,7 +91,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
90
91
  log(ERROR, "Invalid run ID for TaskIns: %s", task_ins.run_id)
91
92
  return None
92
93
  # Validate source node ID
93
- if task_ins.task.producer.node_id != 0:
94
+ if task_ins.task.producer.node_id != SUPERLINK_NODE_ID:
94
95
  log(
95
96
  ERROR,
96
97
  "Invalid source node ID for TaskIns: %s",
@@ -98,14 +99,13 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
98
99
  )
99
100
  return None
100
101
  # Validate destination node ID
101
- if not task_ins.task.consumer.anonymous:
102
- if task_ins.task.consumer.node_id not in self.node_ids:
103
- log(
104
- ERROR,
105
- "Invalid destination node ID for TaskIns: %s",
106
- task_ins.task.consumer.node_id,
107
- )
108
- return None
102
+ if task_ins.task.consumer.node_id not in self.node_ids:
103
+ log(
104
+ ERROR,
105
+ "Invalid destination node ID for TaskIns: %s",
106
+ task_ins.task.consumer.node_id,
107
+ )
108
+ return None
109
109
 
110
110
  # Create task_id
111
111
  task_id = uuid4()
@@ -118,9 +118,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
118
118
  # Return the new task_id
119
119
  return task_id
120
120
 
121
- def get_task_ins(
122
- self, node_id: Optional[int], limit: Optional[int]
123
- ) -> list[TaskIns]:
121
+ def get_task_ins(self, node_id: int, limit: Optional[int]) -> list[TaskIns]:
124
122
  """Get all TaskIns that have not been delivered yet."""
125
123
  if limit is not None and limit < 1:
126
124
  raise AssertionError("`limit` must be >= 1")
@@ -130,17 +128,8 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
130
128
  current_time = time.time()
131
129
  with self.lock:
132
130
  for _, task_ins in self.task_ins_store.items():
133
- # pylint: disable=too-many-boolean-expressions
134
131
  if (
135
- node_id is not None # Not anonymous
136
- and task_ins.task.consumer.anonymous is False
137
- and task_ins.task.consumer.node_id == node_id
138
- and task_ins.task.delivered_at == ""
139
- and task_ins.task.created_at + task_ins.task.ttl > current_time
140
- ) or (
141
- node_id is None # Anonymous
142
- and task_ins.task.consumer.anonymous is True
143
- and task_ins.task.consumer.node_id == 0
132
+ task_ins.task.consumer.node_id == node_id
144
133
  and task_ins.task.delivered_at == ""
145
134
  and task_ins.task.created_at + task_ins.task.ttl > current_time
146
135
  ):
@@ -174,9 +163,6 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
174
163
  if (
175
164
  task_ins
176
165
  and task_res
177
- and not (
178
- task_ins.task.consumer.anonymous or task_res.task.producer.anonymous
179
- )
180
166
  and task_ins.task.consumer.node_id != task_res.task.producer.node_id
181
167
  ):
182
168
  return None
@@ -310,7 +296,9 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
310
296
  def create_node(self, ping_interval: float) -> int:
311
297
  """Create, store in the link state, and return `node_id`."""
312
298
  # Sample a random int64 as node_id
313
- node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
299
+ node_id = generate_rand_int_from_bytes(
300
+ NODE_ID_NUM_BYTES, exclude=[SUPERLINK_NODE_ID, 0]
301
+ )
314
302
 
315
303
  with self.lock:
316
304
  if node_id in self.node_ids:
@@ -40,20 +40,14 @@ class LinkState(abc.ABC): # pylint: disable=R0904
40
40
 
41
41
  Constraints
42
42
  -----------
43
- If `task_ins.task.consumer.anonymous` is `True`, then
44
- `task_ins.task.consumer.node_id` MUST NOT be set (equal 0).
45
-
46
- If `task_ins.task.consumer.anonymous` is `False`, then
47
- `task_ins.task.consumer.node_id` MUST be set (not 0)
43
+ `task_ins.task.consumer.node_id` MUST be set (not constant.DRIVER_NODE_ID)
48
44
 
49
45
  If `task_ins.run_id` is invalid, then
50
46
  storing the `task_ins` MUST fail.
51
47
  """
52
48
 
53
49
  @abc.abstractmethod
54
- def get_task_ins(
55
- self, node_id: Optional[int], limit: Optional[int]
56
- ) -> list[TaskIns]:
50
+ def get_task_ins(self, node_id: int, limit: Optional[int]) -> list[TaskIns]:
57
51
  """Get TaskIns optionally filtered by node_id.
58
52
 
59
53
  Usually, the Fleet API calls this for Nodes planning to work on one or more
@@ -61,15 +55,11 @@ class LinkState(abc.ABC): # pylint: disable=R0904
61
55
 
62
56
  Constraints
63
57
  -----------
64
- If `node_id` is not `None`, retrieve all TaskIns where
58
+ Retrieve all TaskIns where
65
59
 
66
60
  1. the `task_ins.task.consumer.node_id` equals `node_id` AND
67
- 2. the `task_ins.task.consumer.anonymous` equals `False` AND
68
- 3. the `task_ins.task.delivered_at` equals `""`.
61
+ 2. the `task_ins.task.delivered_at` equals `""`.
69
62
 
70
- If `node_id` is `None`, retrieve all TaskIns where the
71
- `task_ins.task.consumer.node_id` equals `0` and
72
- `task_ins.task.consumer.anonymous` is set to `True`.
73
63
 
74
64
  If `delivered_at` MUST BE set (not `""`) otherwise the TaskIns MUST not be in
75
65
  the result.
@@ -89,11 +79,8 @@ class LinkState(abc.ABC): # pylint: disable=R0904
89
79
 
90
80
  Constraints
91
81
  -----------
92
- If `task_res.task.consumer.anonymous` is `True`, then
93
- `task_res.task.consumer.node_id` MUST NOT be set (equal 0).
94
82
 
95
- If `task_res.task.consumer.anonymous` is `False`, then
96
- `task_res.task.consumer.node_id` MUST be set (not 0)
83
+ `task_res.task.consumer.node_id` MUST be set (not constant.DRIVER_NODE_ID)
97
84
 
98
85
  If `task_res.run_id` is invalid, then
99
86
  storing the `task_res` MUST fail.
@@ -31,6 +31,7 @@ from flwr.common.constant import (
31
31
  MESSAGE_TTL_TOLERANCE,
32
32
  NODE_ID_NUM_BYTES,
33
33
  RUN_ID_NUM_BYTES,
34
+ SUPERLINK_NODE_ID,
34
35
  Status,
35
36
  )
36
37
  from flwr.common.record import ConfigsRecord
@@ -128,9 +129,7 @@ CREATE TABLE IF NOT EXISTS task_ins(
128
129
  task_id TEXT UNIQUE,
129
130
  group_id TEXT,
130
131
  run_id INTEGER,
131
- producer_anonymous BOOLEAN,
132
132
  producer_node_id INTEGER,
133
- consumer_anonymous BOOLEAN,
134
133
  consumer_node_id INTEGER,
135
134
  created_at REAL,
136
135
  delivered_at TEXT,
@@ -148,9 +147,7 @@ CREATE TABLE IF NOT EXISTS task_res(
148
147
  task_id TEXT UNIQUE,
149
148
  group_id TEXT,
150
149
  run_id INTEGER,
151
- producer_anonymous BOOLEAN,
152
150
  producer_node_id INTEGER,
153
- consumer_anonymous BOOLEAN,
154
151
  consumer_node_id INTEGER,
155
152
  created_at REAL,
156
153
  delivered_at TEXT,
@@ -263,11 +260,8 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
263
260
 
264
261
  Constraints
265
262
  -----------
266
- If `task_ins.task.consumer.anonymous` is `True`, then
267
- `task_ins.task.consumer.node_id` MUST NOT be set (equal 0).
268
263
 
269
- If `task_ins.task.consumer.anonymous` is `False`, then
270
- `task_ins.task.consumer.node_id` MUST be set (not 0)
264
+ `task_ins.task.consumer.node_id` MUST be set (not constant.DRIVER_NODE_ID)
271
265
  """
272
266
  # Validate task
273
267
  errors = validate_task_ins_or_res(task_ins)
@@ -292,7 +286,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
292
286
  log(ERROR, "Invalid run ID for TaskIns: %s", task_ins.run_id)
293
287
  return None
294
288
  # Validate source node ID
295
- if task_ins.task.producer.node_id != 0:
289
+ if task_ins.task.producer.node_id != SUPERLINK_NODE_ID:
296
290
  log(
297
291
  ERROR,
298
292
  "Invalid source node ID for TaskIns: %s",
@@ -301,14 +295,13 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
301
295
  return None
302
296
  # Validate destination node ID
303
297
  query = "SELECT node_id FROM node WHERE node_id = ?;"
304
- if not task_ins.task.consumer.anonymous:
305
- if not self.query(query, (data[0]["consumer_node_id"],)):
306
- log(
307
- ERROR,
308
- "Invalid destination node ID for TaskIns: %s",
309
- task_ins.task.consumer.node_id,
310
- )
311
- return None
298
+ if not self.query(query, (data[0]["consumer_node_id"],)):
299
+ log(
300
+ ERROR,
301
+ "Invalid destination node ID for TaskIns: %s",
302
+ task_ins.task.consumer.node_id,
303
+ )
304
+ return None
312
305
 
313
306
  columns = ", ".join([f":{key}" for key in data[0]])
314
307
  query = f"INSERT INTO task_ins VALUES({columns});"
@@ -319,25 +312,18 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
319
312
 
320
313
  return task_id
321
314
 
322
- def get_task_ins(
323
- self, node_id: Optional[int], limit: Optional[int]
324
- ) -> list[TaskIns]:
325
- """Get undelivered TaskIns for one node (either anonymous or with ID).
315
+ def get_task_ins(self, node_id: int, limit: Optional[int]) -> list[TaskIns]:
316
+ """Get undelivered TaskIns for one node.
326
317
 
327
318
  Usually, the Fleet API calls this for Nodes planning to work on one or more
328
319
  TaskIns.
329
320
 
330
321
  Constraints
331
322
  -----------
332
- If `node_id` is not `None`, retrieve all TaskIns where
323
+ Retrieve all TaskIns where
333
324
 
334
325
  1. the `task_ins.task.consumer.node_id` equals `node_id` AND
335
- 2. the `task_ins.task.consumer.anonymous` equals `False` AND
336
- 3. the `task_ins.task.delivered_at` equals `""`.
337
-
338
- If `node_id` is `None`, retrieve all TaskIns where the
339
- `task_ins.task.consumer.node_id` equals `0` and
340
- `task_ins.task.consumer.anonymous` is set to `True`.
326
+ 2. the `task_ins.task.delivered_at` equals `""`.
341
327
 
342
328
  `delivered_at` MUST BE set (i.e., not `""`) otherwise the TaskIns MUST not be in
343
329
  the result.
@@ -348,38 +334,23 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
348
334
  if limit is not None and limit < 1:
349
335
  raise AssertionError("`limit` must be >= 1")
350
336
 
351
- if node_id == 0:
352
- msg = (
353
- "`node_id` must be >= 1"
354
- "\n\n For requesting anonymous tasks use `node_id` equal `None`"
355
- )
337
+ if node_id == SUPERLINK_NODE_ID:
338
+ msg = f"`node_id` must be != {SUPERLINK_NODE_ID}"
356
339
  raise AssertionError(msg)
357
340
 
358
341
  data: dict[str, Union[str, int]] = {}
359
342
 
360
- if node_id is None:
361
- # Retrieve all anonymous Tasks
362
- query = """
363
- SELECT task_id
364
- FROM task_ins
365
- WHERE consumer_anonymous == 1
366
- AND consumer_node_id == 0
367
- AND delivered_at = ""
368
- AND (created_at + ttl) > CAST(strftime('%s', 'now') AS REAL)
369
- """
370
- else:
371
- # Convert the uint64 value to sint64 for SQLite
372
- data["node_id"] = convert_uint64_to_sint64(node_id)
343
+ # Convert the uint64 value to sint64 for SQLite
344
+ data["node_id"] = convert_uint64_to_sint64(node_id)
373
345
 
374
- # Retrieve all TaskIns for node_id
375
- query = """
376
- SELECT task_id
377
- FROM task_ins
378
- WHERE consumer_anonymous == 0
379
- AND consumer_node_id == :node_id
380
- AND delivered_at = ""
381
- AND (created_at + ttl) > CAST(strftime('%s', 'now') AS REAL)
382
- """
346
+ # Retrieve all TaskIns for node_id
347
+ query = """
348
+ SELECT task_id
349
+ FROM task_ins
350
+ WHERE consumer_node_id == :node_id
351
+ AND delivered_at = ""
352
+ AND (created_at + ttl) > CAST(strftime('%s', 'now') AS REAL)
353
+ """
383
354
 
384
355
  if limit is not None:
385
356
  query += " LIMIT :limit"
@@ -429,11 +400,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
429
400
 
430
401
  Constraints
431
402
  -----------
432
- If `task_res.task.consumer.anonymous` is `True`, then
433
- `task_res.task.consumer.node_id` MUST NOT be set (equal 0).
434
-
435
- If `task_res.task.consumer.anonymous` is `False`, then
436
- `task_res.task.consumer.node_id` MUST be set (not 0)
403
+ `task_res.task.consumer.node_id` MUST be set (not constant.DRIVER_NODE_ID)
437
404
  """
438
405
  # Validate task
439
406
  errors = validate_task_ins_or_res(task_res)
@@ -459,7 +426,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
459
426
  if (
460
427
  task_ins
461
428
  and task_res
462
- and not (task_ins["consumer_anonymous"] or task_res.task.producer.anonymous)
463
429
  and convert_sint64_to_uint64(task_ins["consumer_node_id"])
464
430
  != task_res.task.producer.node_id
465
431
  ):
@@ -638,7 +604,9 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
638
604
  def create_node(self, ping_interval: float) -> int:
639
605
  """Create, store in the link state, and return `node_id`."""
640
606
  # Sample a random uint64 as node_id
641
- uint64_node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
607
+ uint64_node_id = generate_rand_int_from_bytes(
608
+ NODE_ID_NUM_BYTES, exclude=[SUPERLINK_NODE_ID, 0]
609
+ )
642
610
 
643
611
  # Convert the uint64 value to sint64 for SQLite
644
612
  sint64_node_id = convert_uint64_to_sint64(uint64_node_id)
@@ -1126,9 +1094,7 @@ def task_ins_to_dict(task_msg: TaskIns) -> dict[str, Any]:
1126
1094
  "task_id": task_msg.task_id,
1127
1095
  "group_id": task_msg.group_id,
1128
1096
  "run_id": task_msg.run_id,
1129
- "producer_anonymous": task_msg.task.producer.anonymous,
1130
1097
  "producer_node_id": task_msg.task.producer.node_id,
1131
- "consumer_anonymous": task_msg.task.consumer.anonymous,
1132
1098
  "consumer_node_id": task_msg.task.consumer.node_id,
1133
1099
  "created_at": task_msg.task.created_at,
1134
1100
  "delivered_at": task_msg.task.delivered_at,
@@ -1147,9 +1113,7 @@ def task_res_to_dict(task_msg: TaskRes) -> dict[str, Any]:
1147
1113
  "task_id": task_msg.task_id,
1148
1114
  "group_id": task_msg.group_id,
1149
1115
  "run_id": task_msg.run_id,
1150
- "producer_anonymous": task_msg.task.producer.anonymous,
1151
1116
  "producer_node_id": task_msg.task.producer.node_id,
1152
- "consumer_anonymous": task_msg.task.consumer.anonymous,
1153
1117
  "consumer_node_id": task_msg.task.consumer.node_id,
1154
1118
  "created_at": task_msg.task.created_at,
1155
1119
  "delivered_at": task_msg.task.delivered_at,
@@ -1174,11 +1138,9 @@ def dict_to_task_ins(task_dict: dict[str, Any]) -> TaskIns:
1174
1138
  task=Task(
1175
1139
  producer=Node(
1176
1140
  node_id=task_dict["producer_node_id"],
1177
- anonymous=task_dict["producer_anonymous"],
1178
1141
  ),
1179
1142
  consumer=Node(
1180
1143
  node_id=task_dict["consumer_node_id"],
1181
- anonymous=task_dict["consumer_anonymous"],
1182
1144
  ),
1183
1145
  created_at=task_dict["created_at"],
1184
1146
  delivered_at=task_dict["delivered_at"],
@@ -1204,11 +1166,9 @@ def dict_to_task_res(task_dict: dict[str, Any]) -> TaskRes:
1204
1166
  task=Task(
1205
1167
  producer=Node(
1206
1168
  node_id=task_dict["producer_node_id"],
1207
- anonymous=task_dict["producer_anonymous"],
1208
1169
  ),
1209
1170
  consumer=Node(
1210
1171
  node_id=task_dict["consumer_node_id"],
1211
- anonymous=task_dict["consumer_anonymous"],
1212
1172
  ),
1213
1173
  created_at=task_dict["created_at"],
1214
1174
  delivered_at=task_dict["delivered_at"],
@@ -21,7 +21,7 @@ from typing import Optional, Union
21
21
  from uuid import UUID, uuid4
22
22
 
23
23
  from flwr.common import ConfigsRecord, Context, log, now, serde
24
- from flwr.common.constant import ErrorCode, Status, SubStatus
24
+ from flwr.common.constant import SUPERLINK_NODE_ID, ErrorCode, Status, SubStatus
25
25
  from flwr.common.typing import RunStatus
26
26
 
27
27
  # pylint: disable=E0611
@@ -60,9 +60,19 @@ REPLY_MESSAGE_UNAVAILABLE_ERROR_REASON = (
60
60
  )
61
61
 
62
62
 
63
- def generate_rand_int_from_bytes(num_bytes: int) -> int:
64
- """Generate a random unsigned integer from `num_bytes` bytes."""
65
- return int.from_bytes(urandom(num_bytes), "little", signed=False)
63
+ def generate_rand_int_from_bytes(
64
+ num_bytes: int, exclude: Optional[list[int]] = None
65
+ ) -> int:
66
+ """Generate a random unsigned integer from `num_bytes` bytes.
67
+
68
+ If `exclude` is set, this function guarantees such number is not returned.
69
+ """
70
+ num = int.from_bytes(urandom(num_bytes), "little", signed=False)
71
+
72
+ if exclude:
73
+ while num in exclude:
74
+ num = int.from_bytes(urandom(num_bytes), "little", signed=False)
75
+ return num
66
76
 
67
77
 
68
78
  def convert_uint64_to_sint64(u: int) -> int:
@@ -246,8 +256,8 @@ def create_taskres_for_unavailable_taskins(taskins_id: Union[str, UUID]) -> Task
246
256
  run_id=0, # Unknown run ID
247
257
  task=Task(
248
258
  # This function is only called by SuperLink, and thus it's the producer.
249
- producer=Node(node_id=0, anonymous=False),
250
- consumer=Node(node_id=0, anonymous=False),
259
+ producer=Node(node_id=SUPERLINK_NODE_ID),
260
+ consumer=Node(node_id=SUPERLINK_NODE_ID),
251
261
  created_at=current_time,
252
262
  ttl=0,
253
263
  ancestry=[str(taskins_id)],
@@ -285,8 +295,8 @@ def create_taskres_for_unavailable_taskres(ref_taskins: TaskIns) -> TaskRes:
285
295
  run_id=ref_taskins.run_id,
286
296
  task=Task(
287
297
  # This function is only called by SuperLink, and thus it's the producer.
288
- producer=Node(node_id=0, anonymous=False),
289
- consumer=Node(node_id=0, anonymous=False),
298
+ producer=Node(node_id=SUPERLINK_NODE_ID),
299
+ consumer=Node(node_id=SUPERLINK_NODE_ID),
290
300
  created_at=current_time,
291
301
  ttl=ttl,
292
302
  ancestry=[ref_taskins.task_id],
@@ -18,6 +18,7 @@
18
18
  import time
19
19
  from typing import Union
20
20
 
21
+ from flwr.common.constant import SUPERLINK_NODE_ID
21
22
  from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611
22
23
 
23
24
 
@@ -58,24 +59,14 @@ def validate_task_ins_or_res(tasks_ins_res: Union[TaskIns, TaskRes]) -> list[str
58
59
  # Task producer
59
60
  if not tasks_ins_res.task.HasField("producer"):
60
61
  validation_errors.append("`producer` does not set field `producer`")
61
- if tasks_ins_res.task.producer.node_id != 0:
62
- validation_errors.append("`producer.node_id` is not 0")
63
- if not tasks_ins_res.task.producer.anonymous:
64
- validation_errors.append("`producer` is not anonymous")
62
+ if tasks_ins_res.task.producer.node_id != SUPERLINK_NODE_ID:
63
+ validation_errors.append(f"`producer.node_id` is not {SUPERLINK_NODE_ID}")
65
64
 
66
65
  # Task consumer
67
66
  if not tasks_ins_res.task.HasField("consumer"):
68
67
  validation_errors.append("`consumer` does not set field `consumer`")
69
- if (
70
- tasks_ins_res.task.consumer.anonymous
71
- and tasks_ins_res.task.consumer.node_id != 0
72
- ):
73
- validation_errors.append("anonymous consumers MUST NOT set a `node_id`")
74
- if (
75
- not tasks_ins_res.task.consumer.anonymous
76
- and tasks_ins_res.task.consumer.node_id == 0
77
- ):
78
- validation_errors.append("non-anonymous consumer MUST provide a `node_id`")
68
+ if tasks_ins_res.task.consumer.node_id == SUPERLINK_NODE_ID:
69
+ validation_errors.append("consumer MUST provide a valid `node_id`")
79
70
 
80
71
  # Content check
81
72
  if tasks_ins_res.task.task_type == "":
@@ -95,30 +86,14 @@ def validate_task_ins_or_res(tasks_ins_res: Union[TaskIns, TaskRes]) -> list[str
95
86
  # Task producer
96
87
  if not tasks_ins_res.task.HasField("producer"):
97
88
  validation_errors.append("`producer` does not set field `producer`")
98
- if (
99
- tasks_ins_res.task.producer.anonymous
100
- and tasks_ins_res.task.producer.node_id != 0
101
- ):
102
- validation_errors.append("anonymous producers MUST NOT set a `node_id`")
103
- if (
104
- not tasks_ins_res.task.producer.anonymous
105
- and tasks_ins_res.task.producer.node_id == 0
106
- ):
107
- validation_errors.append("non-anonymous producer MUST provide a `node_id`")
89
+ if tasks_ins_res.task.producer.node_id == SUPERLINK_NODE_ID:
90
+ validation_errors.append("producer MUST provide a valid `node_id`")
108
91
 
109
92
  # Task consumer
110
93
  if not tasks_ins_res.task.HasField("consumer"):
111
94
  validation_errors.append("`consumer` does not set field `consumer`")
112
- if (
113
- tasks_ins_res.task.consumer.anonymous
114
- and tasks_ins_res.task.consumer.node_id != 0
115
- ):
116
- validation_errors.append("anonymous consumers MUST NOT set a `node_id`")
117
- if (
118
- not tasks_ins_res.task.consumer.anonymous
119
- and tasks_ins_res.task.consumer.node_id == 0
120
- ):
121
- validation_errors.append("non-anonymous consumer MUST provide a `node_id`")
95
+ if tasks_ins_res.task.consumer.node_id != SUPERLINK_NODE_ID:
96
+ validation_errors.append(f"consumer is not {SUPERLINK_NODE_ID}")
122
97
 
123
98
  # Content check
124
99
  if tasks_ins_res.task.task_type == "":
flwr/simulation/app.py CHANGED
@@ -16,7 +16,6 @@
16
16
 
17
17
 
18
18
  import argparse
19
- import sys
20
19
  from logging import DEBUG, ERROR, INFO
21
20
  from queue import Queue
22
21
  from time import sleep
@@ -39,6 +38,7 @@ from flwr.common.constant import (
39
38
  Status,
40
39
  SubStatus,
41
40
  )
41
+ from flwr.common.exit import ExitCode, flwr_exit
42
42
  from flwr.common.logger import (
43
43
  log,
44
44
  mirror_output_to_queue,
@@ -81,12 +81,10 @@ def flwr_simulation() -> None:
81
81
  log(INFO, "Starting Flower Simulation")
82
82
 
83
83
  if not args.insecure:
84
- log(
85
- ERROR,
86
- "`flwr-simulation` does not support TLS yet. "
87
- "Please use the '--insecure' flag.",
84
+ flwr_exit(
85
+ ExitCode.COMMON_TLS_NOT_SUPPORTED,
86
+ "`flwr-simulation` does not support TLS yet. ",
88
87
  )
89
- sys.exit(1)
90
88
 
91
89
  log(
92
90
  DEBUG,
@@ -29,7 +29,7 @@ from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy
29
29
 
30
30
  from flwr.client import ClientFnExt
31
31
  from flwr.common import EventType, event
32
- from flwr.common.constant import NODE_ID_NUM_BYTES
32
+ from flwr.common.constant import NODE_ID_NUM_BYTES, SUPERLINK_NODE_ID
33
33
  from flwr.common.logger import (
34
34
  log,
35
35
  set_logger_propagation,
@@ -87,7 +87,9 @@ def _create_node_id_to_partition_mapping(
87
87
  nodes_mapping: NodeToPartitionMapping = {} # {node-id; partition-id}
88
88
  for i in range(num_clients):
89
89
  while True:
90
- node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES)
90
+ node_id = generate_rand_int_from_bytes(
91
+ NODE_ID_NUM_BYTES, exclude=[SUPERLINK_NODE_ID, 0]
92
+ )
91
93
  if node_id not in nodes_mapping:
92
94
  break
93
95
  nodes_mapping[node_id] = i
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flwr-nightly
3
- Version: 1.15.0.dev20250114
3
+ Version: 1.15.0.dev20250123
4
4
  Summary: Flower: A Friendly Federated AI Framework
5
5
  Home-page: https://flower.ai
6
6
  License: Apache-2.0
@@ -32,12 +32,12 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
32
32
  Classifier: Typing :: Typed
33
33
  Provides-Extra: rest
34
34
  Provides-Extra: simulation
35
- Requires-Dist: cryptography (>=42.0.4,<43.0.0)
36
- Requires-Dist: grpcio (>=1.60.0,<2.0.0,!=1.64.2,<=1.64.3)
35
+ Requires-Dist: cryptography (>=43.0.1,<44.0.0)
36
+ Requires-Dist: grpcio (>=1.69.0,<2.0.0)
37
37
  Requires-Dist: iterators (>=0.0.2,<0.0.3)
38
38
  Requires-Dist: numpy (>=1.26.0,<3.0.0)
39
39
  Requires-Dist: pathspec (>=0.12.1,<0.13.0)
40
- Requires-Dist: protobuf (>=4.25.2,<5.0.0)
40
+ Requires-Dist: protobuf (>=5.29.3,<6.0.0)
41
41
  Requires-Dist: pycryptodome (>=3.18.0,<4.0.0)
42
42
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
43
43
  Requires-Dist: ray (==2.10.0) ; (python_version >= "3.9" and python_version < "3.12") and (extra == "simulation")