flwr-nightly 1.23.0.dev20251013__py3-none-any.whl → 1.23.0.dev20251014__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.
Potentially problematic release.
This version of flwr-nightly might be problematic. Click here for more details.
- flwr/common/constant.py +1 -0
- flwr/server/app.py +34 -71
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +39 -5
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +1 -12
- flwr/server/superlink/linkstate/in_memory_linkstate.py +10 -25
- flwr/server/superlink/linkstate/linkstate.py +15 -16
- flwr/server/superlink/linkstate/sqlite_linkstate.py +13 -22
- {flwr_nightly-1.23.0.dev20251013.dist-info → flwr_nightly-1.23.0.dev20251014.dist-info}/METADATA +1 -1
- {flwr_nightly-1.23.0.dev20251013.dist-info → flwr_nightly-1.23.0.dev20251014.dist-info}/RECORD +11 -11
- {flwr_nightly-1.23.0.dev20251013.dist-info → flwr_nightly-1.23.0.dev20251014.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20251013.dist-info → flwr_nightly-1.23.0.dev20251014.dist-info}/entry_points.txt +0 -0
flwr/common/constant.py
CHANGED
|
@@ -157,6 +157,7 @@ RUN_ID_NOT_FOUND_MESSAGE = "Run ID not found"
|
|
|
157
157
|
NO_ACCOUNT_AUTH_MESSAGE = "ControlServicer initialized without account authentication"
|
|
158
158
|
NO_ARTIFACT_PROVIDER_MESSAGE = "ControlServicer initialized without artifact provider"
|
|
159
159
|
PULL_UNFINISHED_RUN_MESSAGE = "Cannot pull artifacts for an unfinished run"
|
|
160
|
+
SUPERNODE_NOT_CREATED_FROM_CLI_MESSAGE = "Invalid SuperNode credentials"
|
|
160
161
|
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE = "Public key already in use"
|
|
161
162
|
PUBLIC_KEY_NOT_VALID = "The provided public key is not valid"
|
|
162
163
|
NODE_NOT_FOUND_MESSAGE = "Node ID not found for account"
|
flwr/server/app.py
CHANGED
|
@@ -16,22 +16,19 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
|
-
import csv
|
|
20
19
|
import importlib.util
|
|
21
20
|
import os
|
|
22
21
|
import subprocess
|
|
23
22
|
import sys
|
|
24
23
|
import threading
|
|
25
24
|
from collections.abc import Sequence
|
|
26
|
-
from logging import
|
|
25
|
+
from logging import INFO, WARN
|
|
27
26
|
from pathlib import Path
|
|
28
27
|
from time import sleep
|
|
29
28
|
from typing import Callable, Optional, TypeVar, cast
|
|
30
29
|
|
|
31
30
|
import grpc
|
|
32
31
|
import yaml
|
|
33
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
34
|
-
from cryptography.hazmat.primitives.serialization import load_ssh_public_key
|
|
35
32
|
|
|
36
33
|
from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, event
|
|
37
34
|
from flwr.common.address import parse_address
|
|
@@ -61,6 +58,7 @@ from flwr.common.event_log_plugin import EventLogWriterPlugin
|
|
|
61
58
|
from flwr.common.exit import ExitCode, flwr_exit, register_signal_handlers
|
|
62
59
|
from flwr.common.grpc import generic_create_grpc_server
|
|
63
60
|
from flwr.common.logger import log
|
|
61
|
+
from flwr.common.version import package_version
|
|
64
62
|
from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611
|
|
65
63
|
add_FleetServicer_to_server,
|
|
66
64
|
)
|
|
@@ -69,7 +67,6 @@ from flwr.server.fleet_event_log_interceptor import FleetEventLogInterceptor
|
|
|
69
67
|
from flwr.supercore.ffs import FfsFactory
|
|
70
68
|
from flwr.supercore.grpc_health import add_args_health, run_health_server_grpc_no_tls
|
|
71
69
|
from flwr.supercore.object_store import ObjectStoreFactory
|
|
72
|
-
from flwr.supercore.primitives.asymmetric import public_key_to_bytes
|
|
73
70
|
from flwr.superlink.artifact_provider import ArtifactProvider
|
|
74
71
|
from flwr.superlink.auth_plugin import (
|
|
75
72
|
ControlAuthnPlugin,
|
|
@@ -214,6 +211,27 @@ def run_superlink() -> None:
|
|
|
214
211
|
log(WARN, "The `--artifact-provider-config` flag is highly experimental.")
|
|
215
212
|
artifact_provider = get_ee_artifact_provider(cfg_path)
|
|
216
213
|
|
|
214
|
+
# If supernode authentication is disabled, warn users
|
|
215
|
+
enable_supernode_auth: bool = args.enable_supernode_auth
|
|
216
|
+
if not enable_supernode_auth:
|
|
217
|
+
log(
|
|
218
|
+
WARN,
|
|
219
|
+
"SuperNode authentication is disabled. The SuperLink will accept "
|
|
220
|
+
"connections from any SuperNode.",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if args.auth_list_public_keys:
|
|
224
|
+
url_v = f"https://flower.ai/docs/framework/v{package_version}/en/"
|
|
225
|
+
page = "how-to-authenticate-supernodes.html"
|
|
226
|
+
flwr_exit(
|
|
227
|
+
ExitCode.SUPERLINK_INVALID_ARGS,
|
|
228
|
+
"The `--auth-list-public-keys` "
|
|
229
|
+
"argument is no longer supported. To enable SuperNode authentication, "
|
|
230
|
+
"use the `--enable-supernode-auth` flag and use the Flower CLI to register "
|
|
231
|
+
"SuperNodes by supplying their public keys. Please refer"
|
|
232
|
+
f" to the Flower documentation for more information: {url_v}{page}",
|
|
233
|
+
)
|
|
234
|
+
|
|
217
235
|
# Initialize StateFactory
|
|
218
236
|
state_factory = LinkStateFactory(args.database)
|
|
219
237
|
|
|
@@ -309,22 +327,8 @@ def run_superlink() -> None:
|
|
|
309
327
|
fleet_thread.start()
|
|
310
328
|
bckg_threads.append(fleet_thread)
|
|
311
329
|
elif args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE:
|
|
312
|
-
node_public_keys = _try_load_public_keys_node_authentication(args)
|
|
313
|
-
auto_auth = True
|
|
314
|
-
if node_public_keys is not None:
|
|
315
|
-
auto_auth = False
|
|
316
|
-
state = state_factory.state()
|
|
317
|
-
state.clear_supernode_auth_keys()
|
|
318
|
-
state.store_node_public_keys(node_public_keys)
|
|
319
|
-
log(
|
|
320
|
-
INFO,
|
|
321
|
-
"Node authentication enabled with %d known public keys",
|
|
322
|
-
len(node_public_keys),
|
|
323
|
-
)
|
|
324
|
-
else:
|
|
325
|
-
log(DEBUG, "Automatic node authentication enabled")
|
|
326
330
|
|
|
327
|
-
interceptors = [NodeAuthServerInterceptor(state_factory
|
|
331
|
+
interceptors = [NodeAuthServerInterceptor(state_factory)]
|
|
328
332
|
if getattr(args, "enable_event_log", None):
|
|
329
333
|
fleet_log_plugin = _try_obtain_fleet_event_log_writer_plugin()
|
|
330
334
|
if fleet_log_plugin is not None:
|
|
@@ -336,6 +340,7 @@ def run_superlink() -> None:
|
|
|
336
340
|
state_factory=state_factory,
|
|
337
341
|
ffs_factory=ffs_factory,
|
|
338
342
|
objectstore_factory=objectstore_factory,
|
|
343
|
+
enable_supernode_auth=enable_supernode_auth,
|
|
339
344
|
certificates=certificates,
|
|
340
345
|
interceptors=interceptors,
|
|
341
346
|
)
|
|
@@ -346,6 +351,7 @@ def run_superlink() -> None:
|
|
|
346
351
|
state_factory=state_factory,
|
|
347
352
|
ffs_factory=ffs_factory,
|
|
348
353
|
objectstore_factory=objectstore_factory,
|
|
354
|
+
enable_supernode_auth=enable_supernode_auth,
|
|
349
355
|
certificates=certificates,
|
|
350
356
|
)
|
|
351
357
|
grpc_servers.append(fleet_server)
|
|
@@ -403,48 +409,6 @@ def _format_address(address: str) -> tuple[str, str, int]:
|
|
|
403
409
|
return (f"[{host}]:{port}" if is_v6 else f"{host}:{port}", host, port)
|
|
404
410
|
|
|
405
411
|
|
|
406
|
-
def _try_load_public_keys_node_authentication(
|
|
407
|
-
args: argparse.Namespace,
|
|
408
|
-
) -> Optional[set[bytes]]:
|
|
409
|
-
"""Return a set of node public keys."""
|
|
410
|
-
if args.auth_superlink_private_key or args.auth_superlink_public_key:
|
|
411
|
-
log(
|
|
412
|
-
WARN,
|
|
413
|
-
"The `--auth-superlink-private-key` and `--auth-superlink-public-key` "
|
|
414
|
-
"arguments are deprecated and will be removed in a future release. Node "
|
|
415
|
-
"authentication no longer requires these arguments.",
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
if not args.auth_list_public_keys:
|
|
419
|
-
return None
|
|
420
|
-
|
|
421
|
-
node_keys_file_path = Path(args.auth_list_public_keys)
|
|
422
|
-
if not node_keys_file_path.exists():
|
|
423
|
-
sys.exit(
|
|
424
|
-
"The provided path to the known public keys CSV file does not exist: "
|
|
425
|
-
f"{node_keys_file_path}. "
|
|
426
|
-
"Please provide the CSV file path containing known public keys "
|
|
427
|
-
"to '--auth-list-public-keys'."
|
|
428
|
-
)
|
|
429
|
-
|
|
430
|
-
node_public_keys: set[bytes] = set()
|
|
431
|
-
|
|
432
|
-
with open(node_keys_file_path, newline="", encoding="utf-8") as csvfile:
|
|
433
|
-
reader = csv.reader(csvfile)
|
|
434
|
-
for row in reader:
|
|
435
|
-
for element in row:
|
|
436
|
-
public_key = load_ssh_public_key(element.encode())
|
|
437
|
-
if isinstance(public_key, ec.EllipticCurvePublicKey):
|
|
438
|
-
node_public_keys.add(public_key_to_bytes(public_key))
|
|
439
|
-
else:
|
|
440
|
-
sys.exit(
|
|
441
|
-
"Error: Unable to parse the public keys in the CSV "
|
|
442
|
-
"file. Please ensure that the CSV file path points to a valid "
|
|
443
|
-
"known SSH public keys files and try again."
|
|
444
|
-
)
|
|
445
|
-
return node_public_keys
|
|
446
|
-
|
|
447
|
-
|
|
448
412
|
def _load_control_auth_plugins(
|
|
449
413
|
config_path: Optional[str], verify_tls_cert: bool
|
|
450
414
|
) -> tuple[ControlAuthnPlugin, ControlAuthzPlugin]:
|
|
@@ -538,6 +502,7 @@ def _run_fleet_api_grpc_rere( # pylint: disable=R0913, R0917
|
|
|
538
502
|
state_factory: LinkStateFactory,
|
|
539
503
|
ffs_factory: FfsFactory,
|
|
540
504
|
objectstore_factory: ObjectStoreFactory,
|
|
505
|
+
enable_supernode_auth: bool,
|
|
541
506
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
542
507
|
interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None,
|
|
543
508
|
) -> grpc.Server:
|
|
@@ -547,6 +512,7 @@ def _run_fleet_api_grpc_rere( # pylint: disable=R0913, R0917
|
|
|
547
512
|
state_factory=state_factory,
|
|
548
513
|
ffs_factory=ffs_factory,
|
|
549
514
|
objectstore_factory=objectstore_factory,
|
|
515
|
+
enable_supernode_auth=enable_supernode_auth,
|
|
550
516
|
)
|
|
551
517
|
fleet_add_servicer_to_server_fn = add_FleetServicer_to_server
|
|
552
518
|
fleet_grpc_server = generic_create_grpc_server(
|
|
@@ -565,11 +531,13 @@ def _run_fleet_api_grpc_rere( # pylint: disable=R0913, R0917
|
|
|
565
531
|
return fleet_grpc_server
|
|
566
532
|
|
|
567
533
|
|
|
534
|
+
# pylint: disable=R0913, R0917
|
|
568
535
|
def _run_fleet_api_grpc_adapter(
|
|
569
536
|
address: str,
|
|
570
537
|
state_factory: LinkStateFactory,
|
|
571
538
|
ffs_factory: FfsFactory,
|
|
572
539
|
objectstore_factory: ObjectStoreFactory,
|
|
540
|
+
enable_supernode_auth: bool,
|
|
573
541
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
|
574
542
|
) -> grpc.Server:
|
|
575
543
|
"""Run Fleet API (GrpcAdapter)."""
|
|
@@ -578,6 +546,7 @@ def _run_fleet_api_grpc_adapter(
|
|
|
578
546
|
state_factory=state_factory,
|
|
579
547
|
ffs_factory=ffs_factory,
|
|
580
548
|
objectstore_factory=objectstore_factory,
|
|
549
|
+
enable_supernode_auth=enable_supernode_auth,
|
|
581
550
|
)
|
|
582
551
|
fleet_add_servicer_to_server_fn = add_GrpcAdapterServicer_to_server
|
|
583
552
|
fleet_grpc_server = generic_create_grpc_server(
|
|
@@ -722,18 +691,12 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
722
691
|
parser.add_argument(
|
|
723
692
|
"--auth-list-public-keys",
|
|
724
693
|
type=str,
|
|
725
|
-
help="A CSV file (as a path str) containing a list of known public "
|
|
726
|
-
"keys to enable authentication.",
|
|
727
|
-
)
|
|
728
|
-
parser.add_argument(
|
|
729
|
-
"--auth-superlink-private-key",
|
|
730
|
-
type=str,
|
|
731
694
|
help="This argument is deprecated and will be removed in a future release.",
|
|
732
695
|
)
|
|
733
696
|
parser.add_argument(
|
|
734
|
-
"--
|
|
735
|
-
|
|
736
|
-
help="
|
|
697
|
+
"--enable-supernode-auth",
|
|
698
|
+
action="store_true",
|
|
699
|
+
help="Enable supernode authentication.",
|
|
737
700
|
)
|
|
738
701
|
|
|
739
702
|
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
"""Fleet API gRPC request-response servicer."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
from logging import DEBUG, INFO
|
|
18
|
+
from logging import DEBUG, ERROR, INFO
|
|
19
19
|
|
|
20
20
|
import grpc
|
|
21
21
|
from google.protobuf.json_format import MessageToDict
|
|
22
22
|
|
|
23
|
+
from flwr.common.constant import SUPERNODE_NOT_CREATED_FROM_CLI_MESSAGE
|
|
23
24
|
from flwr.common.inflatable import UnexpectedObjectContentError
|
|
24
25
|
from flwr.common.logger import log
|
|
25
26
|
from flwr.common.typing import InvalidRunStatusException
|
|
@@ -47,6 +48,7 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
|
|
47
48
|
PushObjectRequest,
|
|
48
49
|
PushObjectResponse,
|
|
49
50
|
)
|
|
51
|
+
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
50
52
|
from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611
|
|
51
53
|
from flwr.server.superlink.fleet.message_handler import message_handler
|
|
52
54
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
|
@@ -63,10 +65,12 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
|
|
|
63
65
|
state_factory: LinkStateFactory,
|
|
64
66
|
ffs_factory: FfsFactory,
|
|
65
67
|
objectstore_factory: ObjectStoreFactory,
|
|
68
|
+
enable_supernode_auth: bool,
|
|
66
69
|
) -> None:
|
|
67
70
|
self.state_factory = state_factory
|
|
68
71
|
self.ffs_factory = ffs_factory
|
|
69
72
|
self.objectstore_factory = objectstore_factory
|
|
73
|
+
self.enable_supernode_auth = enable_supernode_auth
|
|
70
74
|
|
|
71
75
|
def CreateNode(
|
|
72
76
|
self, request: CreateNodeRequest, context: grpc.ServicerContext
|
|
@@ -79,10 +83,28 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
|
|
|
79
83
|
)
|
|
80
84
|
log(DEBUG, "[Fleet.CreateNode] Request: %s", MessageToDict(request))
|
|
81
85
|
try:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
|
|
87
|
+
state = self.state_factory.state()
|
|
88
|
+
|
|
89
|
+
# Check if public key is already in use
|
|
90
|
+
if node_id := state.get_node_id_by_public_key(request.public_key):
|
|
91
|
+
# Prepare response with existing node_id
|
|
92
|
+
response = CreateNodeResponse(node=Node(node_id=node_id))
|
|
93
|
+
else:
|
|
94
|
+
if self.enable_supernode_auth:
|
|
95
|
+
# When SuperNode authentication is enabled,
|
|
96
|
+
# only SuperNodes created from the CLI are allowed to
|
|
97
|
+
# stablish a connection with the Fleet API
|
|
98
|
+
log(ERROR, SUPERNODE_NOT_CREATED_FROM_CLI_MESSAGE)
|
|
99
|
+
raise ValueError(SUPERNODE_NOT_CREATED_FROM_CLI_MESSAGE)
|
|
100
|
+
|
|
101
|
+
# When SuperNode authentication is disabled, auto-auth
|
|
102
|
+
# allows creating a new node
|
|
103
|
+
response = message_handler.create_node(
|
|
104
|
+
request=request,
|
|
105
|
+
state=state,
|
|
106
|
+
)
|
|
107
|
+
|
|
86
108
|
except ValueError as e:
|
|
87
109
|
# Public key already in use
|
|
88
110
|
context.abort(grpc.StatusCode.FAILED_PRECONDITION, str(e))
|
|
@@ -96,6 +118,18 @@ class FleetServicer(fleet_pb2_grpc.FleetServicer):
|
|
|
96
118
|
"""."""
|
|
97
119
|
log(INFO, "[Fleet.DeleteNode] Delete node_id=%s", request.node.node_id)
|
|
98
120
|
log(DEBUG, "[Fleet.DeleteNode] Request: %s", MessageToDict(request))
|
|
121
|
+
# This shall be refactored when renaming `Fleet.Create/DeleteNode`
|
|
122
|
+
# to `Fleet.Activate/DeactivateNode`
|
|
123
|
+
if self.enable_supernode_auth:
|
|
124
|
+
# SuperNodes can only be deleted from the CLI
|
|
125
|
+
# We simply acknowledge the heartbeat with interval 0
|
|
126
|
+
# to mark the node as offline
|
|
127
|
+
state = self.state_factory.state()
|
|
128
|
+
state.acknowledge_node_heartbeat(
|
|
129
|
+
node_id=request.node.node_id, heartbeat_interval=0
|
|
130
|
+
)
|
|
131
|
+
return DeleteNodeResponse()
|
|
132
|
+
|
|
99
133
|
return message_handler.delete_node(
|
|
100
134
|
request=request,
|
|
101
135
|
state=self.state_factory.state(),
|
|
@@ -54,15 +54,10 @@ class NodeAuthServerInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
54
54
|
----------
|
|
55
55
|
state_factory : LinkStateFactory
|
|
56
56
|
A factory for creating new instances of LinkState.
|
|
57
|
-
auto_auth : bool (default: False)
|
|
58
|
-
If True, nodes are authenticated without requiring their public keys to be
|
|
59
|
-
pre-stored in the LinkState. If False, only nodes with pre-stored public keys
|
|
60
|
-
can be authenticated.
|
|
61
57
|
"""
|
|
62
58
|
|
|
63
|
-
def __init__(self, state_factory: LinkStateFactory
|
|
59
|
+
def __init__(self, state_factory: LinkStateFactory):
|
|
64
60
|
self.state_factory = state_factory
|
|
65
|
-
self.auto_auth = auto_auth
|
|
66
61
|
|
|
67
62
|
def intercept_service( # pylint: disable=too-many-return-statements
|
|
68
63
|
self,
|
|
@@ -79,7 +74,6 @@ class NodeAuthServerInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
79
74
|
if not handler_call_details.method.startswith("/flwr.proto.Fleet/"):
|
|
80
75
|
return continuation(handler_call_details)
|
|
81
76
|
|
|
82
|
-
state = self.state_factory.state()
|
|
83
77
|
metadata_dict = dict(handler_call_details.invocation_metadata)
|
|
84
78
|
|
|
85
79
|
# Retrieve info from the metadata
|
|
@@ -90,11 +84,6 @@ class NodeAuthServerInterceptor(grpc.ServerInterceptor): # type: ignore
|
|
|
90
84
|
except KeyError:
|
|
91
85
|
return _unary_unary_rpc_terminator("Missing authentication metadata")
|
|
92
86
|
|
|
93
|
-
if not self.auto_auth:
|
|
94
|
-
# Abort the RPC call if the node public key is not found
|
|
95
|
-
if node_pk_bytes not in state.get_node_public_keys():
|
|
96
|
-
return _unary_unary_rpc_terminator("Public key not recognized")
|
|
97
|
-
|
|
98
87
|
# Verify the signature
|
|
99
88
|
node_pk = bytes_to_public_key(node_pk_bytes)
|
|
100
89
|
if not verify_signature(node_pk, timestamp_iso.encode("ascii"), signature):
|
|
@@ -21,7 +21,7 @@ from bisect import bisect_right
|
|
|
21
21
|
from collections import defaultdict
|
|
22
22
|
from collections.abc import Sequence
|
|
23
23
|
from dataclasses import dataclass, field
|
|
24
|
-
from datetime import datetime
|
|
24
|
+
from datetime import datetime, timezone
|
|
25
25
|
from logging import ERROR, WARNING
|
|
26
26
|
from typing import Optional
|
|
27
27
|
|
|
@@ -74,7 +74,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
74
74
|
|
|
75
75
|
# Map node_id to NodeInfo
|
|
76
76
|
self.nodes: dict[int, NodeInfo] = {}
|
|
77
|
-
self.
|
|
77
|
+
self.node_public_key_to_node_id: dict[bytes, int] = {}
|
|
78
78
|
self.owner_to_node_ids: dict[str, set[int]] = {} # Quick lookup
|
|
79
79
|
|
|
80
80
|
# Map run_id to RunRecord
|
|
@@ -346,7 +346,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
346
346
|
if node_id in self.nodes:
|
|
347
347
|
log(ERROR, "Unexpected node registration failure.")
|
|
348
348
|
return 0
|
|
349
|
-
if public_key in self.
|
|
349
|
+
if public_key in self.node_public_key_to_node_id:
|
|
350
350
|
raise ValueError("Public key already in use")
|
|
351
351
|
|
|
352
352
|
# The node is not activated upon creation
|
|
@@ -362,7 +362,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
362
362
|
heartbeat_interval=heartbeat_interval,
|
|
363
363
|
public_key=public_key,
|
|
364
364
|
)
|
|
365
|
-
self.
|
|
365
|
+
self.node_public_key_to_node_id[public_key] = node_id
|
|
366
366
|
self.owner_to_node_ids.setdefault(owner_aid, set()).add(node_id)
|
|
367
367
|
return node_id
|
|
368
368
|
|
|
@@ -428,7 +428,7 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
428
428
|
if node.online_until <= current_ts:
|
|
429
429
|
node.status = NodeStatus.OFFLINE
|
|
430
430
|
node.last_deactivated_at = datetime.fromtimestamp(
|
|
431
|
-
node.online_until
|
|
431
|
+
node.online_until, tz=timezone.utc
|
|
432
432
|
).isoformat()
|
|
433
433
|
|
|
434
434
|
def get_node_public_key(self, node_id: int) -> bytes:
|
|
@@ -440,6 +440,11 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
440
440
|
raise ValueError(f"Node ID {node_id} not found")
|
|
441
441
|
return node.public_key
|
|
442
442
|
|
|
443
|
+
def get_node_id_by_public_key(self, public_key: bytes) -> Optional[int]:
|
|
444
|
+
"""Get `node_id` for the specified `public_key`."""
|
|
445
|
+
with self.lock:
|
|
446
|
+
return self.node_public_key_to_node_id.get(public_key)
|
|
447
|
+
|
|
443
448
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
444
449
|
def create_run(
|
|
445
450
|
self,
|
|
@@ -486,26 +491,6 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
|
486
491
|
log(ERROR, "Unexpected run creation failure.")
|
|
487
492
|
return 0
|
|
488
493
|
|
|
489
|
-
def clear_supernode_auth_keys(self) -> None:
|
|
490
|
-
"""Clear stored `node_public_keys` in the link state if any."""
|
|
491
|
-
with self.lock:
|
|
492
|
-
self.node_public_keys.clear()
|
|
493
|
-
|
|
494
|
-
def store_node_public_keys(self, public_keys: set[bytes]) -> None:
|
|
495
|
-
"""Store a set of `node_public_keys` in the link state."""
|
|
496
|
-
with self.lock:
|
|
497
|
-
self.node_public_keys.update(public_keys)
|
|
498
|
-
|
|
499
|
-
def store_node_public_key(self, public_key: bytes) -> None:
|
|
500
|
-
"""Store a `node_public_key` in the link state."""
|
|
501
|
-
with self.lock:
|
|
502
|
-
self.node_public_keys.add(public_key)
|
|
503
|
-
|
|
504
|
-
def get_node_public_keys(self) -> set[bytes]:
|
|
505
|
-
"""Retrieve all currently stored `node_public_keys` as a set."""
|
|
506
|
-
with self.lock:
|
|
507
|
-
return self.node_public_keys.copy()
|
|
508
|
-
|
|
509
494
|
def get_run_ids(self, flwr_aid: Optional[str]) -> set[int]:
|
|
510
495
|
"""Retrieve all run IDs if `flwr_aid` is not specified.
|
|
511
496
|
|
|
@@ -149,6 +149,21 @@ class LinkState(CoreState): # pylint: disable=R0904
|
|
|
149
149
|
an empty `Set` MUST be returned.
|
|
150
150
|
"""
|
|
151
151
|
|
|
152
|
+
@abc.abstractmethod
|
|
153
|
+
def get_node_id_by_public_key(self, public_key: bytes) -> Optional[int]:
|
|
154
|
+
"""Get `node_id` for the specified `public_key`.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
public_key : bytes
|
|
159
|
+
The public key of the node whose information is to be retrieved.
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
Optional[int]
|
|
164
|
+
The `node_id` associated with the specified `public_key`.
|
|
165
|
+
"""
|
|
166
|
+
|
|
152
167
|
@abc.abstractmethod
|
|
153
168
|
def get_node_info(
|
|
154
169
|
self,
|
|
@@ -298,22 +313,6 @@ class LinkState(CoreState): # pylint: disable=R0904
|
|
|
298
313
|
The federation options for the run if it exists; None otherwise.
|
|
299
314
|
"""
|
|
300
315
|
|
|
301
|
-
@abc.abstractmethod
|
|
302
|
-
def clear_supernode_auth_keys(self) -> None:
|
|
303
|
-
"""Clear stored `node_public_keys` in the link state if any."""
|
|
304
|
-
|
|
305
|
-
@abc.abstractmethod
|
|
306
|
-
def store_node_public_keys(self, public_keys: set[bytes]) -> None:
|
|
307
|
-
"""Store a set of `node_public_keys` in the link state."""
|
|
308
|
-
|
|
309
|
-
@abc.abstractmethod
|
|
310
|
-
def store_node_public_key(self, public_key: bytes) -> None:
|
|
311
|
-
"""Store a `node_public_key` in the link state."""
|
|
312
|
-
|
|
313
|
-
@abc.abstractmethod
|
|
314
|
-
def get_node_public_keys(self) -> set[bytes]:
|
|
315
|
-
"""Retrieve all currently stored `node_public_keys` as a set."""
|
|
316
|
-
|
|
317
316
|
@abc.abstractmethod
|
|
318
317
|
def acknowledge_node_heartbeat(
|
|
319
318
|
self, node_id: int, heartbeat_interval: float
|
|
@@ -784,6 +784,19 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
784
784
|
# Return the public key
|
|
785
785
|
return cast(bytes, rows[0]["public_key"])
|
|
786
786
|
|
|
787
|
+
def get_node_id_by_public_key(self, public_key: bytes) -> Optional[int]:
|
|
788
|
+
"""Get `node_id` for the specified `public_key`."""
|
|
789
|
+
query = "SELECT node_id FROM node WHERE public_key = ? AND status != ?;"
|
|
790
|
+
rows = self.query(query, (public_key, NodeStatus.DELETED))
|
|
791
|
+
|
|
792
|
+
# If no result is found, return None
|
|
793
|
+
if not rows:
|
|
794
|
+
return None
|
|
795
|
+
|
|
796
|
+
# Convert sint64 node_id to uint64
|
|
797
|
+
node_id = convert_sint64_to_uint64(rows[0]["node_id"])
|
|
798
|
+
return node_id
|
|
799
|
+
|
|
787
800
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
788
801
|
def create_run(
|
|
789
802
|
self,
|
|
@@ -835,28 +848,6 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
|
835
848
|
log(ERROR, "Unexpected run creation failure.")
|
|
836
849
|
return 0
|
|
837
850
|
|
|
838
|
-
def clear_supernode_auth_keys(self) -> None:
|
|
839
|
-
"""Clear stored `node_public_keys` in the link state if any."""
|
|
840
|
-
self.query("DELETE FROM public_key;")
|
|
841
|
-
|
|
842
|
-
def store_node_public_keys(self, public_keys: set[bytes]) -> None:
|
|
843
|
-
"""Store a set of `node_public_keys` in the link state."""
|
|
844
|
-
query = "INSERT INTO public_key (public_key) VALUES (?)"
|
|
845
|
-
data = [(key,) for key in public_keys]
|
|
846
|
-
self.query(query, data)
|
|
847
|
-
|
|
848
|
-
def store_node_public_key(self, public_key: bytes) -> None:
|
|
849
|
-
"""Store a `node_public_key` in the link state."""
|
|
850
|
-
query = "INSERT INTO public_key (public_key) VALUES (:public_key)"
|
|
851
|
-
self.query(query, {"public_key": public_key})
|
|
852
|
-
|
|
853
|
-
def get_node_public_keys(self) -> set[bytes]:
|
|
854
|
-
"""Retrieve all currently stored `node_public_keys` as a set."""
|
|
855
|
-
query = "SELECT public_key FROM public_key"
|
|
856
|
-
rows = self.query(query)
|
|
857
|
-
result: set[bytes] = {row["public_key"] for row in rows}
|
|
858
|
-
return result
|
|
859
|
-
|
|
860
851
|
def get_run_ids(self, flwr_aid: Optional[str]) -> set[int]:
|
|
861
852
|
"""Retrieve all run IDs if `flwr_aid` is not specified.
|
|
862
853
|
|
{flwr_nightly-1.23.0.dev20251013.dist-info → flwr_nightly-1.23.0.dev20251014.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flwr-nightly
|
|
3
|
-
Version: 1.23.0.
|
|
3
|
+
Version: 1.23.0.dev20251014
|
|
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
|
{flwr_nightly-1.23.0.dev20251013.dist-info → flwr_nightly-1.23.0.dev20251014.dist-info}/RECORD
RENAMED
|
@@ -126,7 +126,7 @@ flwr/common/__init__.py,sha256=5GCLVk399Az_rTJHNticRlL0Sl_oPw_j5_LuFKfX7-M,4171
|
|
|
126
126
|
flwr/common/address.py,sha256=9JucdTwlc-jpeJkRKeUboZoacUtErwSVtnDR9kAtLqE,4119
|
|
127
127
|
flwr/common/args.py,sha256=Nq2u4yePbkSY0CWFamn0hZY6Rms8G1xYDeDGIcLIITE,5849
|
|
128
128
|
flwr/common/config.py,sha256=glcZDjco-amw1YfQcYTFJ4S1pt9APoexT-mf1QscuHs,13960
|
|
129
|
-
flwr/common/constant.py,sha256=
|
|
129
|
+
flwr/common/constant.py,sha256=vkHawDmnH0Czt3y8L2FBgj4NHaqal76M6lSEAwE1Z1s,9656
|
|
130
130
|
flwr/common/context.py,sha256=Be8obQR_OvEDy1OmshuUKxGRQ7Qx89mf5F4xlhkR10s,2407
|
|
131
131
|
flwr/common/date.py,sha256=1ZT2cRSpC2DJqprOVTLXYCR_O2_OZR0zXO_brJ3LqWc,1554
|
|
132
132
|
flwr/common/differential_privacy.py,sha256=FdlpdpPl_H_2HJa8CQM1iCUGBBQ5Dc8CzxmHERM-EoE,6148
|
|
@@ -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=
|
|
252
|
+
flwr/server/app.py,sha256=VvetbRhQagGDTM3egm1lqJ3G9nL6LXye6QLtemPRiOU,28848
|
|
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
|
|
@@ -305,8 +305,8 @@ flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py,sha256=KouR9PUcrPmMtoLooF4O
|
|
|
305
305
|
flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py,sha256=iSf0mbBAlig7G6subQwBSVjcUCgSihONKdZ1RmQPTOk,4887
|
|
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
|
-
flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=
|
|
309
|
-
flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py,sha256=
|
|
308
|
+
flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=1dJAFTKEh2YbCIuRu89xYQ4lYxJDuqxICEr3pEM3MA4,10462
|
|
309
|
+
flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py,sha256=UWeFQNBW2pGBRVN36HodHcv7bKTgMmdToh96Lhqip1M,5411
|
|
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=ijK-zji1AKkOvJLLIzC2mYQlEh50lI1qPW8QpysNsKk,8751
|
|
312
312
|
flwr/server/superlink/fleet/rest_rere/__init__.py,sha256=Lzc93nA7tDqoy-zRUaPG316oqFiZX1HUCL5ELaXY_xw,735
|
|
@@ -317,10 +317,10 @@ flwr/server/superlink/fleet/vce/backend/backend.py,sha256=cSrHZ5SjCCvy4vI0pgsyjt
|
|
|
317
317
|
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=cBZYTmfiAsb1HmVUmOQXYLU-UJmJTFWkj1wW4RYRDuc,7218
|
|
318
318
|
flwr/server/superlink/fleet/vce/vce_api.py,sha256=EU0DLt4njtKelOpOWfQ7zWW45bSVC6K7pPYfHSyOJwM,13332
|
|
319
319
|
flwr/server/superlink/linkstate/__init__.py,sha256=OtsgvDTnZLU3k0sUbkHbqoVwW6ql2FDmb6uT6DbNkZo,1064
|
|
320
|
-
flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=
|
|
321
|
-
flwr/server/superlink/linkstate/linkstate.py,sha256=
|
|
320
|
+
flwr/server/superlink/linkstate/in_memory_linkstate.py,sha256=NLJ4lNdySnAKiraZ3z2xHcc2-4s8DaMCYyOa2bk_pM4,29388
|
|
321
|
+
flwr/server/superlink/linkstate/linkstate.py,sha256=hfe8vhRreMb0OqqSHT6ccTGngi62iW5D-WH-sD99eFY,14606
|
|
322
322
|
flwr/server/superlink/linkstate/linkstate_factory.py,sha256=8RlosqSpKOoD_vhUUQPY0jtE3A84GeF96Z7sWNkRRcA,2069
|
|
323
|
-
flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=
|
|
323
|
+
flwr/server/superlink/linkstate/sqlite_linkstate.py,sha256=Lj5MWxjux01w04M8MBhK-kmD-9bvEG9EfFTvn6Zo-FU,47820
|
|
324
324
|
flwr/server/superlink/linkstate/utils.py,sha256=IeLh7iGRCHU5MEWOl7iriaSE4L__8GWOa2OleXadK5M,15444
|
|
325
325
|
flwr/server/superlink/serverappio/__init__.py,sha256=Fy4zJuoccZe5mZSEIpOmQvU6YeXFBa1M4eZuXXmJcn8,717
|
|
326
326
|
flwr/server/superlink/serverappio/serverappio_grpc.py,sha256=-I7kBbr4w4ZVYwBZoAIle-xHKthFnZrsVfxa6WR8uxA,2310
|
|
@@ -430,7 +430,7 @@ flwr/supernode/servicer/__init__.py,sha256=lucTzre5WPK7G1YLCfaqg3rbFWdNSb7ZTt-ca
|
|
|
430
430
|
flwr/supernode/servicer/clientappio/__init__.py,sha256=7Oy62Y_oijqF7Dxi6tpcUQyOpLc_QpIRZ83NvwmB0Yg,813
|
|
431
431
|
flwr/supernode/servicer/clientappio/clientappio_servicer.py,sha256=nIHRu38EWK-rpNOkcgBRAAKwYQQWFeCwu0lkO7OPZGQ,10239
|
|
432
432
|
flwr/supernode/start_client_internal.py,sha256=Y9S1-QlO2WP6eo4JvWzIpfaCoh2aoE7bjEYyxNNnlyg,20777
|
|
433
|
-
flwr_nightly-1.23.0.
|
|
434
|
-
flwr_nightly-1.23.0.
|
|
435
|
-
flwr_nightly-1.23.0.
|
|
436
|
-
flwr_nightly-1.23.0.
|
|
433
|
+
flwr_nightly-1.23.0.dev20251014.dist-info/METADATA,sha256=2lxDwkO_kHIC4xnpLHpsZFU2yXLr-cNZLCtQlathFkg,14559
|
|
434
|
+
flwr_nightly-1.23.0.dev20251014.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
435
|
+
flwr_nightly-1.23.0.dev20251014.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
|
|
436
|
+
flwr_nightly-1.23.0.dev20251014.dist-info/RECORD,,
|
{flwr_nightly-1.23.0.dev20251013.dist-info → flwr_nightly-1.23.0.dev20251014.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|