flwr-nightly 1.23.0.dev20251008__py3-none-any.whl → 1.23.0.dev20251010__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/create.py +137 -11
- flwr/cli/supernode/delete.py +88 -10
- flwr/cli/utils.py +37 -11
- flwr/common/constant.py +3 -0
- flwr/common/exit/exit_code.py +10 -0
- flwr/proto/control_pb2.py +1 -1
- flwr/proto/control_pb2.pyi +2 -2
- flwr/proto/node_pb2.py +2 -2
- flwr/proto/node_pb2.pyi +4 -1
- flwr/server/superlink/fleet/message_handler/message_handler.py +5 -3
- flwr/server/superlink/fleet/vce/vce_api.py +4 -1
- flwr/server/superlink/linkstate/in_memory_linkstate.py +22 -36
- flwr/server/superlink/linkstate/linkstate.py +20 -10
- flwr/server/superlink/linkstate/sqlite_linkstate.py +19 -42
- flwr/supercore/primitives/asymmetric.py +8 -0
- flwr/superlink/servicer/control/control_servicer.py +55 -2
- {flwr_nightly-1.23.0.dev20251008.dist-info → flwr_nightly-1.23.0.dev20251010.dist-info}/METADATA +1 -1
- {flwr_nightly-1.23.0.dev20251008.dist-info → flwr_nightly-1.23.0.dev20251010.dist-info}/RECORD +20 -20
- {flwr_nightly-1.23.0.dev20251008.dist-info → flwr_nightly-1.23.0.dev20251010.dist-info}/WHEEL +0 -0
- {flwr_nightly-1.23.0.dev20251008.dist-info → flwr_nightly-1.23.0.dev20251010.dist-info}/entry_points.txt +0 -0
flwr/cli/supernode/create.py
CHANGED
@@ -14,10 +14,17 @@
|
|
14
14
|
# ==============================================================================
|
15
15
|
"""Flower command line interface `supernode create` command."""
|
16
16
|
|
17
|
+
|
18
|
+
import io
|
19
|
+
import json
|
17
20
|
from pathlib import Path
|
18
21
|
from typing import Annotated, Optional
|
19
22
|
|
20
23
|
import typer
|
24
|
+
from cryptography.exceptions import UnsupportedAlgorithm
|
25
|
+
from cryptography.hazmat.primitives import serialization
|
26
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
27
|
+
from rich.console import Console
|
21
28
|
|
22
29
|
from flwr.cli.config_utils import (
|
23
30
|
exit_if_no_address,
|
@@ -25,14 +32,24 @@ from flwr.cli.config_utils import (
|
|
25
32
|
process_loaded_project_config,
|
26
33
|
validate_federation_in_project_config,
|
27
34
|
)
|
28
|
-
from flwr.common.constant import FAB_CONFIG_FILE
|
35
|
+
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
36
|
+
from flwr.common.exit import ExitCode, flwr_exit
|
37
|
+
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
38
|
+
from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
39
|
+
CreateNodeCliRequest,
|
40
|
+
CreateNodeCliResponse,
|
41
|
+
)
|
42
|
+
from flwr.proto.control_pb2_grpc import ControlStub
|
43
|
+
from flwr.supercore.primitives.asymmetric import public_key_to_bytes, uses_nist_ec_curve
|
44
|
+
|
45
|
+
from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
29
46
|
|
30
47
|
|
31
48
|
def create( # pylint: disable=R0914
|
32
49
|
public_key: Annotated[
|
33
50
|
Path,
|
34
51
|
typer.Argument(
|
35
|
-
help="Path to
|
52
|
+
help="Path to a P-384 (or any other NIST EC curve) public key file.",
|
36
53
|
),
|
37
54
|
],
|
38
55
|
app: Annotated[
|
@@ -43,16 +60,125 @@ def create( # pylint: disable=R0914
|
|
43
60
|
Optional[str],
|
44
61
|
typer.Argument(help="Name of the federation"),
|
45
62
|
] = None,
|
63
|
+
output_format: Annotated[
|
64
|
+
str,
|
65
|
+
typer.Option(
|
66
|
+
"--format",
|
67
|
+
case_sensitive=False,
|
68
|
+
help="Format output using 'default' view or 'json'",
|
69
|
+
),
|
70
|
+
] = CliOutputFormat.DEFAULT,
|
46
71
|
) -> None:
|
47
72
|
"""Add a SuperNode to the federation."""
|
48
|
-
|
73
|
+
suppress_output = output_format == CliOutputFormat.JSON
|
74
|
+
captured_output = io.StringIO()
|
75
|
+
|
76
|
+
# Load public key
|
77
|
+
public_key_path = Path(public_key)
|
78
|
+
public_key_bytes = try_load_public_key(public_key_path)
|
79
|
+
|
80
|
+
try:
|
81
|
+
if suppress_output:
|
82
|
+
redirect_output(captured_output)
|
83
|
+
|
84
|
+
# Load and validate federation config
|
85
|
+
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
86
|
+
|
87
|
+
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
88
|
+
config, errors, warnings = load_and_validate(path=pyproject_path)
|
89
|
+
config = process_loaded_project_config(config, errors, warnings)
|
90
|
+
federation, federation_config = validate_federation_in_project_config(
|
91
|
+
federation, config
|
92
|
+
)
|
93
|
+
exit_if_no_address(federation_config, "supernode create")
|
94
|
+
|
95
|
+
channel = None
|
96
|
+
try:
|
97
|
+
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
98
|
+
channel = init_channel(app, federation_config, auth_plugin)
|
99
|
+
stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
|
100
|
+
|
101
|
+
_create_node(
|
102
|
+
stub=stub, public_key=public_key_bytes, output_format=output_format
|
103
|
+
)
|
104
|
+
|
105
|
+
except ValueError as err:
|
106
|
+
typer.secho(
|
107
|
+
f"❌ {err}",
|
108
|
+
fg=typer.colors.RED,
|
109
|
+
bold=True,
|
110
|
+
)
|
111
|
+
raise typer.Exit(code=1) from err
|
112
|
+
finally:
|
113
|
+
if channel:
|
114
|
+
channel.close()
|
115
|
+
|
116
|
+
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
117
|
+
if suppress_output:
|
118
|
+
restore_output()
|
119
|
+
e_message = captured_output.getvalue()
|
120
|
+
print_json_error(e_message, err)
|
121
|
+
else:
|
122
|
+
typer.secho(
|
123
|
+
f"{err}",
|
124
|
+
fg=typer.colors.RED,
|
125
|
+
bold=True,
|
126
|
+
)
|
127
|
+
finally:
|
128
|
+
if suppress_output:
|
129
|
+
restore_output()
|
130
|
+
captured_output.close()
|
131
|
+
|
132
|
+
|
133
|
+
def _create_node(stub: ControlStub, public_key: bytes, output_format: str) -> None:
|
134
|
+
"""Create a node."""
|
135
|
+
with flwr_cli_grpc_exc_handler():
|
136
|
+
response: CreateNodeCliResponse = stub.CreateNodeCli(
|
137
|
+
request=CreateNodeCliRequest(public_key=public_key)
|
138
|
+
)
|
139
|
+
if response.node_id:
|
140
|
+
typer.secho(
|
141
|
+
f"✅ Node {response.node_id} created successfully.", fg=typer.colors.GREEN
|
142
|
+
)
|
143
|
+
if output_format == CliOutputFormat.JSON:
|
144
|
+
run_output = json.dumps(
|
145
|
+
{
|
146
|
+
"success": True,
|
147
|
+
"node-id": response.node_id,
|
148
|
+
}
|
149
|
+
)
|
150
|
+
restore_output()
|
151
|
+
Console().print_json(run_output)
|
152
|
+
else:
|
153
|
+
typer.secho("❌ Node couldn't be created.", fg=typer.colors.RED)
|
154
|
+
|
155
|
+
|
156
|
+
def try_load_public_key(public_key_path: Path) -> bytes:
|
157
|
+
"""Try to load a public key from a file."""
|
158
|
+
if not public_key_path.exists():
|
159
|
+
typer.secho(
|
160
|
+
f"❌ Public key file '{public_key_path}' does not exist.",
|
161
|
+
fg=typer.colors.RED,
|
162
|
+
bold=True,
|
163
|
+
)
|
164
|
+
raise typer.Exit(code=1)
|
165
|
+
|
166
|
+
with open(public_key_path, "rb") as key_file:
|
167
|
+
try:
|
168
|
+
public_key = serialization.load_ssh_public_key(key_file.read())
|
169
|
+
|
170
|
+
if not isinstance(public_key, ec.EllipticCurvePublicKey):
|
171
|
+
raise ValueError(f"Not an EC public key, got {type(public_key)}")
|
49
172
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
)
|
56
|
-
exit_if_no_address(federation_config, "supernode add")
|
173
|
+
# Verify it's one of the approved NIST curves
|
174
|
+
if not uses_nist_ec_curve(public_key):
|
175
|
+
raise ValueError(
|
176
|
+
f"EC curve {public_key.curve.name} is not an approved NIST curve"
|
177
|
+
)
|
57
178
|
|
58
|
-
|
179
|
+
except (ValueError, UnsupportedAlgorithm) as err:
|
180
|
+
flwr_exit(
|
181
|
+
ExitCode.FLWRCLI_NODE_AUTH_PUBLIC_KEY_INVALID,
|
182
|
+
str(err),
|
183
|
+
)
|
184
|
+
return public_key_to_bytes(public_key)
|
flwr/cli/supernode/delete.py
CHANGED
@@ -14,10 +14,14 @@
|
|
14
14
|
# ==============================================================================
|
15
15
|
"""Flower command line interface `supernode delete` command."""
|
16
16
|
|
17
|
+
|
18
|
+
import io
|
19
|
+
import json
|
17
20
|
from pathlib import Path
|
18
21
|
from typing import Annotated, Optional
|
19
22
|
|
20
23
|
import typer
|
24
|
+
from rich.console import Console
|
21
25
|
|
22
26
|
from flwr.cli.config_utils import (
|
23
27
|
exit_if_no_address,
|
@@ -25,7 +29,12 @@ from flwr.cli.config_utils import (
|
|
25
29
|
process_loaded_project_config,
|
26
30
|
validate_federation_in_project_config,
|
27
31
|
)
|
28
|
-
from flwr.common.constant import FAB_CONFIG_FILE
|
32
|
+
from flwr.common.constant import FAB_CONFIG_FILE, CliOutputFormat
|
33
|
+
from flwr.common.logger import print_json_error, redirect_output, restore_output
|
34
|
+
from flwr.proto.control_pb2 import DeleteNodeCliRequest # pylint: disable=E0611
|
35
|
+
from flwr.proto.control_pb2_grpc import ControlStub
|
36
|
+
|
37
|
+
from ..utils import flwr_cli_grpc_exc_handler, init_channel, load_cli_auth_plugin
|
29
38
|
|
30
39
|
|
31
40
|
def delete( # pylint: disable=R0914
|
@@ -43,16 +52,85 @@ def delete( # pylint: disable=R0914
|
|
43
52
|
Optional[str],
|
44
53
|
typer.Argument(help="Name of the federation"),
|
45
54
|
] = None,
|
55
|
+
output_format: Annotated[
|
56
|
+
str,
|
57
|
+
typer.Option(
|
58
|
+
"--format",
|
59
|
+
case_sensitive=False,
|
60
|
+
help="Format output using 'default' view or 'json'",
|
61
|
+
),
|
62
|
+
] = CliOutputFormat.DEFAULT,
|
46
63
|
) -> None:
|
47
64
|
"""Remove a SuperNode from the federation."""
|
48
|
-
|
65
|
+
suppress_output = output_format == CliOutputFormat.JSON
|
66
|
+
captured_output = io.StringIO()
|
67
|
+
|
68
|
+
try:
|
69
|
+
if suppress_output:
|
70
|
+
redirect_output(captured_output)
|
71
|
+
|
72
|
+
# Load and validate federation config
|
73
|
+
typer.secho("Loading project configuration... ", fg=typer.colors.BLUE)
|
74
|
+
|
75
|
+
pyproject_path = app / FAB_CONFIG_FILE if app else None
|
76
|
+
config, errors, warnings = load_and_validate(path=pyproject_path)
|
77
|
+
config = process_loaded_project_config(config, errors, warnings)
|
78
|
+
federation, federation_config = validate_federation_in_project_config(
|
79
|
+
federation, config
|
80
|
+
)
|
81
|
+
exit_if_no_address(federation_config, "supernode remove")
|
49
82
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
)
|
56
|
-
exit_if_no_address(federation_config, "supernode rm")
|
83
|
+
channel = None
|
84
|
+
try:
|
85
|
+
auth_plugin = load_cli_auth_plugin(app, federation, federation_config)
|
86
|
+
channel = init_channel(app, federation_config, auth_plugin)
|
87
|
+
stub = ControlStub(channel) # pylint: disable=unused-variable # noqa: F841
|
57
88
|
|
58
|
-
|
89
|
+
_delete_node(stub=stub, node_id=node_id, output_format=output_format)
|
90
|
+
|
91
|
+
except ValueError as err:
|
92
|
+
typer.secho(
|
93
|
+
f"❌ {err}",
|
94
|
+
fg=typer.colors.RED,
|
95
|
+
bold=True,
|
96
|
+
)
|
97
|
+
raise typer.Exit(code=1) from err
|
98
|
+
finally:
|
99
|
+
if channel:
|
100
|
+
channel.close()
|
101
|
+
|
102
|
+
except (typer.Exit, Exception) as err: # pylint: disable=broad-except
|
103
|
+
if suppress_output:
|
104
|
+
restore_output()
|
105
|
+
e_message = captured_output.getvalue()
|
106
|
+
print_json_error(e_message, err)
|
107
|
+
else:
|
108
|
+
typer.secho(
|
109
|
+
f"{err}",
|
110
|
+
fg=typer.colors.RED,
|
111
|
+
bold=True,
|
112
|
+
)
|
113
|
+
finally:
|
114
|
+
if suppress_output:
|
115
|
+
restore_output()
|
116
|
+
captured_output.close()
|
117
|
+
|
118
|
+
|
119
|
+
def _delete_node(
|
120
|
+
stub: ControlStub,
|
121
|
+
node_id: int,
|
122
|
+
output_format: str,
|
123
|
+
) -> None:
|
124
|
+
"""Delete a SuperNode from the federation."""
|
125
|
+
with flwr_cli_grpc_exc_handler():
|
126
|
+
stub.DeleteNodeCli(request=DeleteNodeCliRequest(node_id=node_id))
|
127
|
+
typer.secho(f"✅ SuperNode {node_id} deleted successfully.", fg=typer.colors.GREEN)
|
128
|
+
if output_format == CliOutputFormat.JSON:
|
129
|
+
run_output = json.dumps(
|
130
|
+
{
|
131
|
+
"success": True,
|
132
|
+
"node-id": node_id,
|
133
|
+
}
|
134
|
+
)
|
135
|
+
restore_output()
|
136
|
+
Console().print_json(run_output)
|
flwr/cli/utils.py
CHANGED
@@ -32,6 +32,9 @@ from flwr.common.constant import (
|
|
32
32
|
FLWR_DIR,
|
33
33
|
NO_ACCOUNT_AUTH_MESSAGE,
|
34
34
|
NO_ARTIFACT_PROVIDER_MESSAGE,
|
35
|
+
NODE_NOT_FOUND_MESSAGE,
|
36
|
+
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
|
37
|
+
PUBLIC_KEY_NOT_VALID,
|
35
38
|
PULL_UNFINISHED_RUN_MESSAGE,
|
36
39
|
RUN_ID_NOT_FOUND_MESSAGE,
|
37
40
|
AuthnType,
|
@@ -293,7 +296,7 @@ def init_channel(
|
|
293
296
|
|
294
297
|
|
295
298
|
@contextmanager
|
296
|
-
def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
299
|
+
def flwr_cli_grpc_exc_handler() -> Iterator[None]: # pylint: disable=too-many-branches
|
297
300
|
"""Context manager to handle specific gRPC errors.
|
298
301
|
|
299
302
|
It catches grpc.RpcError exceptions with UNAUTHENTICATED, UNIMPLEMENTED,
|
@@ -351,16 +354,21 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
351
354
|
bold=True,
|
352
355
|
)
|
353
356
|
raise typer.Exit(code=1) from None
|
354
|
-
if (
|
355
|
-
e.
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
)
|
363
|
-
|
357
|
+
if e.code() == grpc.StatusCode.NOT_FOUND:
|
358
|
+
if e.details() == RUN_ID_NOT_FOUND_MESSAGE: # pylint: disable=E1101
|
359
|
+
typer.secho(
|
360
|
+
"❌ Run ID not found.",
|
361
|
+
fg=typer.colors.RED,
|
362
|
+
bold=True,
|
363
|
+
)
|
364
|
+
raise typer.Exit(code=1) from None
|
365
|
+
if e.details() == NODE_NOT_FOUND_MESSAGE: # pylint: disable=E1101
|
366
|
+
typer.secho(
|
367
|
+
"❌ Node ID not found for this account.",
|
368
|
+
fg=typer.colors.RED,
|
369
|
+
bold=True,
|
370
|
+
)
|
371
|
+
raise typer.Exit(code=1) from None
|
364
372
|
if e.code() == grpc.StatusCode.FAILED_PRECONDITION:
|
365
373
|
if e.details() == PULL_UNFINISHED_RUN_MESSAGE: # pylint: disable=E1101
|
366
374
|
typer.secho(
|
@@ -370,4 +378,22 @@ def flwr_cli_grpc_exc_handler() -> Iterator[None]:
|
|
370
378
|
bold=True,
|
371
379
|
)
|
372
380
|
raise typer.Exit(code=1) from None
|
381
|
+
if (
|
382
|
+
e.details() == PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
|
383
|
+
): # pylint: disable=E1101
|
384
|
+
typer.secho(
|
385
|
+
"❌ The provided public key is already in use by another "
|
386
|
+
"SuperNode.",
|
387
|
+
fg=typer.colors.RED,
|
388
|
+
bold=True,
|
389
|
+
)
|
390
|
+
raise typer.Exit(code=1) from None
|
391
|
+
if e.details() == PUBLIC_KEY_NOT_VALID: # pylint: disable=E1101
|
392
|
+
typer.secho(
|
393
|
+
"❌ The provided public key is invalid. Please provide a valid "
|
394
|
+
"NIST EC public key.",
|
395
|
+
fg=typer.colors.RED,
|
396
|
+
bold=True,
|
397
|
+
)
|
398
|
+
raise typer.Exit(code=1) from None
|
373
399
|
raise
|
flwr/common/constant.py
CHANGED
@@ -157,6 +157,9 @@ 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
|
+
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE = "Public key already in use"
|
161
|
+
PUBLIC_KEY_NOT_VALID = "The provided public key is not valid"
|
162
|
+
NODE_NOT_FOUND_MESSAGE = "Node ID not found for account"
|
160
163
|
|
161
164
|
|
162
165
|
class MessageType:
|
flwr/common/exit/exit_code.py
CHANGED
@@ -47,6 +47,9 @@ class ExitCode:
|
|
47
47
|
# SuperExec-specific exit codes (400-499)
|
48
48
|
SUPEREXEC_INVALID_PLUGIN_CONFIG = 400
|
49
49
|
|
50
|
+
# FlowerCLI-specific exit codes (500-599)
|
51
|
+
FLWRCLI_NODE_AUTH_PUBLIC_KEY_INVALID = 500
|
52
|
+
|
50
53
|
# Common exit codes (600-699)
|
51
54
|
COMMON_ADDRESS_INVALID = 600
|
52
55
|
COMMON_MISSING_EXTRA_REST = 601
|
@@ -116,6 +119,13 @@ EXIT_CODE_HELP = {
|
|
116
119
|
ExitCode.SUPEREXEC_INVALID_PLUGIN_CONFIG: (
|
117
120
|
"The YAML configuration for the SuperExec plugin is invalid."
|
118
121
|
),
|
122
|
+
# FlowerCLI-specific exit codes (500-599)
|
123
|
+
ExitCode.FLWRCLI_NODE_AUTH_PUBLIC_KEY_INVALID: (
|
124
|
+
"Node authentication requires a valid elliptic curve public key in the "
|
125
|
+
"SSH format and following a NIST standard elliptic curve (e.g. SECP384R1). "
|
126
|
+
"Please ensure that the file path points to a valid public key "
|
127
|
+
"file and try again."
|
128
|
+
),
|
119
129
|
# Common exit codes (600-699)
|
120
130
|
ExitCode.COMMON_ADDRESS_INVALID: (
|
121
131
|
"Please provide a valid URL, IPv4 or IPv6 address."
|
flwr/proto/control_pb2.py
CHANGED
@@ -19,7 +19,7 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
|
|
19
19
|
from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2
|
20
20
|
|
21
21
|
|
22
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x15\x66lwr/proto/node.proto\"\xfa\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x34\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x18.flwr.proto.ConfigRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"2\n\x10StartRunResponse\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\"1\n\x0fListRunsRequest\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"\x9d\x01\n\x10ListRunsResponse\x12;\n\x08run_dict\x18\x01 \x03(\x0b\x32).flwr.proto.ListRunsResponse.RunDictEntry\x12\x0b\n\x03now\x18\x02 \x01(\t\x1a?\n\x0cRunDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run:\x02\x38\x01\"\x18\n\x16GetLoginDetailsRequest\"\x8b\x01\n\x17GetLoginDetailsResponse\x12\x12\n\nauthn_type\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65vice_code\x18\x02 \x01(\t\x12!\n\x19verification_uri_complete\x18\x03 \x01(\t\x12\x12\n\nexpires_in\x18\x04 \x01(\x03\x12\x10\n\x08interval\x18\x05 \x01(\x03\"+\n\x14GetAuthTokensRequest\x12\x13\n\x0b\x64\x65vice_code\x18\x01 \x01(\t\"D\n\x15GetAuthTokensResponse\x12\x14\n\x0c\x61\x63\x63\x65ss_token\x18\x01 \x01(\t\x12\x15\n\rrefresh_token\x18\x02 \x01(\t\" \n\x0eStopRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\"\n\x0fStopRunResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"&\n\x14PullArtifactsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"1\n\x15PullArtifactsResponse\x12\x10\n\x03url\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x06\n\x04_url\"*\n\x14\x43reateNodeCliRequest\x12\x12\n\npublic_key\x18\x01 \x01(\
|
22
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x15\x66lwr/proto/node.proto\"\xfa\x01\n\x0fStartRunRequest\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fab\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12\x34\n\x12\x66\x65\x64\x65ration_options\x18\x03 \x01(\x0b\x32\x18.flwr.proto.ConfigRecord\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"2\n\x10StartRunResponse\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"<\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x17\n\x0f\x61\x66ter_timestamp\x18\x02 \x01(\x01\"B\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t\x12\x18\n\x10latest_timestamp\x18\x02 \x01(\x01\"1\n\x0fListRunsRequest\x12\x13\n\x06run_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_run_id\"\x9d\x01\n\x10ListRunsResponse\x12;\n\x08run_dict\x18\x01 \x03(\x0b\x32).flwr.proto.ListRunsResponse.RunDictEntry\x12\x0b\n\x03now\x18\x02 \x01(\t\x1a?\n\x0cRunDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\x1e\n\x05value\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run:\x02\x38\x01\"\x18\n\x16GetLoginDetailsRequest\"\x8b\x01\n\x17GetLoginDetailsResponse\x12\x12\n\nauthn_type\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65vice_code\x18\x02 \x01(\t\x12!\n\x19verification_uri_complete\x18\x03 \x01(\t\x12\x12\n\nexpires_in\x18\x04 \x01(\x03\x12\x10\n\x08interval\x18\x05 \x01(\x03\"+\n\x14GetAuthTokensRequest\x12\x13\n\x0b\x64\x65vice_code\x18\x01 \x01(\t\"D\n\x15GetAuthTokensResponse\x12\x14\n\x0c\x61\x63\x63\x65ss_token\x18\x01 \x01(\t\x12\x15\n\rrefresh_token\x18\x02 \x01(\t\" \n\x0eStopRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"\"\n\x0fStopRunResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"&\n\x14PullArtifactsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"1\n\x15PullArtifactsResponse\x12\x10\n\x03url\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x06\n\x04_url\"*\n\x14\x43reateNodeCliRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\"9\n\x15\x43reateNodeCliResponse\x12\x14\n\x07node_id\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\n\n\x08_node_id\"\'\n\x14\x44\x65leteNodeCliRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x04\"\x17\n\x15\x44\x65leteNodeCliResponse\"\x15\n\x13ListNodesCliRequest\"M\n\x14ListNodesCliResponse\x12(\n\nnodes_info\x18\x01 \x03(\x0b\x32\x14.flwr.proto.NodeInfo\x12\x0b\n\x03now\x18\x02 \x01(\t2\xc5\x06\n\x07\x43ontrol\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12\x44\n\x07StopRun\x12\x1a.flwr.proto.StopRunRequest\x1a\x1b.flwr.proto.StopRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x12G\n\x08ListRuns\x12\x1b.flwr.proto.ListRunsRequest\x1a\x1c.flwr.proto.ListRunsResponse\"\x00\x12\\\n\x0fGetLoginDetails\x12\".flwr.proto.GetLoginDetailsRequest\x1a#.flwr.proto.GetLoginDetailsResponse\"\x00\x12V\n\rGetAuthTokens\x12 .flwr.proto.GetAuthTokensRequest\x1a!.flwr.proto.GetAuthTokensResponse\"\x00\x12V\n\rPullArtifacts\x12 .flwr.proto.PullArtifactsRequest\x1a!.flwr.proto.PullArtifactsResponse\"\x00\x12V\n\rCreateNodeCli\x12 .flwr.proto.CreateNodeCliRequest\x1a!.flwr.proto.CreateNodeCliResponse\"\x00\x12V\n\rDeleteNodeCli\x12 .flwr.proto.DeleteNodeCliRequest\x1a!.flwr.proto.DeleteNodeCliResponse\"\x00\x12S\n\x0cListNodesCli\x12\x1f.flwr.proto.ListNodesCliRequest\x1a .flwr.proto.ListNodesCliResponse\"\x00\x62\x06proto3')
|
23
23
|
|
24
24
|
_globals = globals()
|
25
25
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
flwr/proto/control_pb2.pyi
CHANGED
@@ -239,10 +239,10 @@ global___PullArtifactsResponse = PullArtifactsResponse
|
|
239
239
|
class CreateNodeCliRequest(google.protobuf.message.Message):
|
240
240
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
241
241
|
PUBLIC_KEY_FIELD_NUMBER: builtins.int
|
242
|
-
public_key:
|
242
|
+
public_key: builtins.bytes
|
243
243
|
def __init__(self,
|
244
244
|
*,
|
245
|
-
public_key:
|
245
|
+
public_key: builtins.bytes = ...,
|
246
246
|
) -> None: ...
|
247
247
|
def ClearField(self, field_name: typing_extensions.Literal["public_key",b"public_key"]) -> None: ...
|
248
248
|
global___CreateNodeCliRequest = CreateNodeCliRequest
|
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\"\
|
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\"\xe4\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(\x01\x12\x1a\n\x12heartbeat_interval\x18\t \x01(\x01\x12\x12\n\npublic_key\x18\n \x01(\x0c\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=
|
27
|
+
_globals['_NODEINFO']._serialized_end=291
|
28
28
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/node_pb2.pyi
CHANGED
@@ -32,6 +32,7 @@ class NodeInfo(google.protobuf.message.Message):
|
|
32
32
|
DELETED_AT_FIELD_NUMBER: builtins.int
|
33
33
|
ONLINE_UNTIL_FIELD_NUMBER: builtins.int
|
34
34
|
HEARTBEAT_INTERVAL_FIELD_NUMBER: builtins.int
|
35
|
+
PUBLIC_KEY_FIELD_NUMBER: builtins.int
|
35
36
|
node_id: builtins.int
|
36
37
|
owner_aid: typing.Text
|
37
38
|
status: typing.Text
|
@@ -41,6 +42,7 @@ class NodeInfo(google.protobuf.message.Message):
|
|
41
42
|
deleted_at: typing.Text
|
42
43
|
online_until: builtins.float
|
43
44
|
heartbeat_interval: builtins.float
|
45
|
+
public_key: builtins.bytes
|
44
46
|
def __init__(self,
|
45
47
|
*,
|
46
48
|
node_id: builtins.int = ...,
|
@@ -52,6 +54,7 @@ class NodeInfo(google.protobuf.message.Message):
|
|
52
54
|
deleted_at: typing.Text = ...,
|
53
55
|
online_until: builtins.float = ...,
|
54
56
|
heartbeat_interval: builtins.float = ...,
|
57
|
+
public_key: builtins.bytes = ...,
|
55
58
|
) -> 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: ...
|
59
|
+
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","public_key",b"public_key","status",b"status"]) -> None: ...
|
57
60
|
global___NodeInfo = NodeInfo
|
@@ -18,7 +18,7 @@ from logging import ERROR
|
|
18
18
|
from typing import Optional
|
19
19
|
|
20
20
|
from flwr.common import Message, log
|
21
|
-
from flwr.common.constant import Status
|
21
|
+
from flwr.common.constant import NOOP_FLWR_AID, Status
|
22
22
|
from flwr.common.inflatable import UnexpectedObjectContentError
|
23
23
|
from flwr.common.serde import (
|
24
24
|
fab_to_proto,
|
@@ -70,7 +70,9 @@ def create_node(
|
|
70
70
|
) -> CreateNodeResponse:
|
71
71
|
"""."""
|
72
72
|
# Create node
|
73
|
-
node_id = state.create_node(
|
73
|
+
node_id = state.create_node(
|
74
|
+
NOOP_FLWR_AID, request.public_key, request.heartbeat_interval
|
75
|
+
)
|
74
76
|
return CreateNodeResponse(node=Node(node_id=node_id))
|
75
77
|
|
76
78
|
|
@@ -81,7 +83,7 @@ def delete_node(request: DeleteNodeRequest, state: LinkState) -> DeleteNodeRespo
|
|
81
83
|
return DeleteNodeResponse()
|
82
84
|
|
83
85
|
# Update state
|
84
|
-
state.delete_node(node_id=request.node.node_id)
|
86
|
+
state.delete_node(NOOP_FLWR_AID, node_id=request.node.node_id)
|
85
87
|
return DeleteNodeResponse()
|
86
88
|
|
87
89
|
|
@@ -34,6 +34,7 @@ from flwr.clientapp.utils import get_load_client_app_fn
|
|
34
34
|
from flwr.common import Message
|
35
35
|
from flwr.common.constant import (
|
36
36
|
HEARTBEAT_MAX_INTERVAL,
|
37
|
+
NOOP_FLWR_AID,
|
37
38
|
NUM_PARTITIONS_KEY,
|
38
39
|
PARTITION_ID_KEY,
|
39
40
|
ErrorCode,
|
@@ -56,7 +57,9 @@ def _register_nodes(
|
|
56
57
|
for i in range(num_nodes):
|
57
58
|
node_id = state.create_node(
|
58
59
|
# No node authentication in simulation;
|
59
|
-
# use
|
60
|
+
# use NOOP_FLWR_AID as owner_aid and
|
61
|
+
# use random bytes as public key
|
62
|
+
NOOP_FLWR_AID,
|
60
63
|
secrets.token_bytes(32),
|
61
64
|
heartbeat_interval=HEARTBEAT_MAX_INTERVAL,
|
62
65
|
)
|
@@ -69,10 +69,10 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
69
69
|
|
70
70
|
def __init__(self) -> None:
|
71
71
|
|
72
|
-
# Map node_id to
|
72
|
+
# Map node_id to NodeInfo
|
73
73
|
self.nodes: dict[int, NodeInfo] = {}
|
74
|
-
self.
|
75
|
-
self.
|
74
|
+
self.registered_node_public_keys: set[bytes] = set()
|
75
|
+
self.owner_to_node_ids: dict[str, set[int]] = {} # Quick lookup
|
76
76
|
|
77
77
|
# Map run_id to RunRecord
|
78
78
|
self.run_ids: dict[int, RunRecord] = {}
|
@@ -330,7 +330,9 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
330
330
|
"""
|
331
331
|
return len(self.message_res_store)
|
332
332
|
|
333
|
-
def create_node(
|
333
|
+
def create_node(
|
334
|
+
self, owner_aid: str, public_key: bytes, heartbeat_interval: float
|
335
|
+
) -> int:
|
334
336
|
"""Create, store in the link state, and return `node_id`."""
|
335
337
|
# Sample a random int64 as node_id
|
336
338
|
node_id = generate_rand_int_from_bytes(
|
@@ -341,14 +343,14 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
341
343
|
if node_id in self.nodes:
|
342
344
|
log(ERROR, "Unexpected node registration failure.")
|
343
345
|
return 0
|
344
|
-
if public_key in self.
|
346
|
+
if public_key in self.registered_node_public_keys:
|
345
347
|
raise ValueError("Public key already in use")
|
346
348
|
|
347
349
|
# Mark the node online until now().timestamp() + heartbeat_interval
|
348
350
|
current = now()
|
349
351
|
self.nodes[node_id] = NodeInfo(
|
350
352
|
node_id=node_id,
|
351
|
-
owner_aid=
|
353
|
+
owner_aid=owner_aid, # Unused for now
|
352
354
|
status="created", # Unused for now
|
353
355
|
created_at=current.isoformat(), # Unused for now
|
354
356
|
last_activated_at=current.isoformat(), # Unused for now
|
@@ -356,22 +358,22 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
356
358
|
deleted_at="", # Unused for now
|
357
359
|
online_until=current.timestamp() + heartbeat_interval,
|
358
360
|
heartbeat_interval=heartbeat_interval,
|
361
|
+
public_key=public_key,
|
359
362
|
)
|
360
|
-
self.
|
361
|
-
self.
|
363
|
+
self.registered_node_public_keys.add(public_key)
|
364
|
+
self.owner_to_node_ids.setdefault(owner_aid, set()).add(node_id)
|
362
365
|
return node_id
|
363
366
|
|
364
|
-
def delete_node(self, node_id: int) -> None:
|
367
|
+
def delete_node(self, owner_aid: str, node_id: int) -> None:
|
365
368
|
"""Delete a node."""
|
366
369
|
with self.lock:
|
367
|
-
if node_id not in self.nodes:
|
368
|
-
raise ValueError(
|
369
|
-
|
370
|
-
|
371
|
-
if pk := self.node_id_to_public_key.pop(node_id, None):
|
372
|
-
del self.public_key_to_node_id[pk]
|
370
|
+
if node_id not in self.nodes or owner_aid != self.nodes[node_id].owner_aid:
|
371
|
+
raise ValueError(
|
372
|
+
f"Node ID {node_id} not found or unauthorized deletion attempt."
|
373
|
+
)
|
373
374
|
|
374
|
-
|
375
|
+
node = self.nodes.pop(node_id)
|
376
|
+
self.registered_node_public_keys.discard(node.public_key)
|
375
377
|
|
376
378
|
def get_nodes(self, run_id: int) -> set[int]:
|
377
379
|
"""Return all available nodes.
|
@@ -391,29 +393,13 @@ class InMemoryLinkState(LinkState): # pylint: disable=R0902,R0904
|
|
391
393
|
if info.online_until > current_time
|
392
394
|
}
|
393
395
|
|
394
|
-
def
|
395
|
-
"""Set `public_key` for the specified `node_id`."""
|
396
|
-
with self.lock:
|
397
|
-
if node_id not in self.nodes:
|
398
|
-
raise ValueError(f"Node {node_id} not found")
|
399
|
-
|
400
|
-
if public_key in self.public_key_to_node_id:
|
401
|
-
raise ValueError("Public key already in use")
|
402
|
-
|
403
|
-
self.public_key_to_node_id[public_key] = node_id
|
404
|
-
self.node_id_to_public_key[node_id] = public_key
|
405
|
-
|
406
|
-
def get_node_public_key(self, node_id: int) -> Optional[bytes]:
|
396
|
+
def get_node_public_key(self, node_id: int) -> bytes:
|
407
397
|
"""Get `public_key` for the specified `node_id`."""
|
408
398
|
with self.lock:
|
409
|
-
if
|
410
|
-
raise ValueError(f"Node {node_id} not found")
|
411
|
-
|
412
|
-
return self.node_id_to_public_key.get(node_id)
|
399
|
+
if (node := self.nodes.get(node_id)) is None:
|
400
|
+
raise ValueError(f"Node ID {node_id} not found")
|
413
401
|
|
414
|
-
|
415
|
-
"""Retrieve stored `node_id` filtered by `node_public_keys`."""
|
416
|
-
return self.public_key_to_node_id.get(node_public_key)
|
402
|
+
return node.public_key
|
417
403
|
|
418
404
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
419
405
|
def create_run(
|
@@ -128,11 +128,13 @@ class LinkState(CoreState): # pylint: disable=R0904
|
|
128
128
|
"""Get all instruction Message IDs for the given run_id."""
|
129
129
|
|
130
130
|
@abc.abstractmethod
|
131
|
-
def create_node(
|
131
|
+
def create_node(
|
132
|
+
self, owner_aid: str, public_key: bytes, heartbeat_interval: float
|
133
|
+
) -> int:
|
132
134
|
"""Create, store in the link state, and return `node_id`."""
|
133
135
|
|
134
136
|
@abc.abstractmethod
|
135
|
-
def delete_node(self, node_id: int) -> None:
|
137
|
+
def delete_node(self, owner_aid: str, node_id: int) -> None:
|
136
138
|
"""Remove `node_id` from the link state."""
|
137
139
|
|
138
140
|
@abc.abstractmethod
|
@@ -146,16 +148,24 @@ class LinkState(CoreState): # pylint: disable=R0904
|
|
146
148
|
"""
|
147
149
|
|
148
150
|
@abc.abstractmethod
|
149
|
-
def
|
150
|
-
"""
|
151
|
+
def get_node_public_key(self, node_id: int) -> bytes:
|
152
|
+
"""Get `public_key` for the specified `node_id`.
|
151
153
|
|
152
|
-
|
153
|
-
|
154
|
-
|
154
|
+
Parameters
|
155
|
+
----------
|
156
|
+
node_id : int
|
157
|
+
The identifier of the node whose public key is to be retrieved.
|
155
158
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
+
Returns
|
160
|
+
-------
|
161
|
+
bytes
|
162
|
+
The public key associated with the specified `node_id`.
|
163
|
+
|
164
|
+
Raises
|
165
|
+
------
|
166
|
+
ValueError
|
167
|
+
If the specified `node_id` does not exist in the link state.
|
168
|
+
"""
|
159
169
|
|
160
170
|
@abc.abstractmethod
|
161
171
|
def create_run( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
@@ -94,6 +94,10 @@ SQL_CREATE_INDEX_ONLINE_UNTIL = """
|
|
94
94
|
CREATE INDEX IF NOT EXISTS idx_online_until ON node (online_until);
|
95
95
|
"""
|
96
96
|
|
97
|
+
SQL_CREATE_INDEX_OWNER_AID = """
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_node_owner_aid ON node(owner_aid);
|
99
|
+
"""
|
100
|
+
|
97
101
|
SQL_CREATE_TABLE_RUN = """
|
98
102
|
CREATE TABLE IF NOT EXISTS run(
|
99
103
|
run_id INTEGER UNIQUE,
|
@@ -228,6 +232,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
228
232
|
cur.execute(SQL_CREATE_TABLE_PUBLIC_KEY)
|
229
233
|
cur.execute(SQL_CREATE_TABLE_TOKEN_STORE)
|
230
234
|
cur.execute(SQL_CREATE_INDEX_ONLINE_UNTIL)
|
235
|
+
cur.execute(SQL_CREATE_INDEX_OWNER_AID)
|
231
236
|
res = cur.execute("SELECT name FROM sqlite_schema;")
|
232
237
|
return res.fetchall()
|
233
238
|
|
@@ -602,7 +607,9 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
602
607
|
|
603
608
|
return {row["message_id"] for row in rows}
|
604
609
|
|
605
|
-
def create_node(
|
610
|
+
def create_node(
|
611
|
+
self, owner_aid: str, public_key: bytes, heartbeat_interval: float
|
612
|
+
) -> int:
|
606
613
|
"""Create, store in the link state, and return `node_id`."""
|
607
614
|
# Sample a random uint64 as node_id
|
608
615
|
uint64_node_id = generate_rand_int_from_bytes(
|
@@ -626,7 +633,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
626
633
|
query,
|
627
634
|
(
|
628
635
|
sint64_node_id, # node_id
|
629
|
-
|
636
|
+
owner_aid, # owner_aid, unused for now
|
630
637
|
"created", # status, unused for now
|
631
638
|
now().isoformat(), # created_at, unused for now
|
632
639
|
now().isoformat(), # last_activated_at, unused for now
|
@@ -647,13 +654,13 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
647
654
|
# Note: we need to return the uint64 value of the node_id
|
648
655
|
return uint64_node_id
|
649
656
|
|
650
|
-
def delete_node(self, node_id: int) -> None:
|
657
|
+
def delete_node(self, owner_aid: str, node_id: int) -> None:
|
651
658
|
"""Delete a node."""
|
652
659
|
# Convert the uint64 value to sint64 for SQLite
|
653
660
|
sint64_node_id = convert_uint64_to_sint64(node_id)
|
654
661
|
|
655
|
-
query = "DELETE FROM node WHERE node_id = ?"
|
656
|
-
params = (sint64_node_id,)
|
662
|
+
query = "DELETE FROM node WHERE node_id = ? AND owner_aid = ?"
|
663
|
+
params = (sint64_node_id, owner_aid)
|
657
664
|
|
658
665
|
if self.conn is None:
|
659
666
|
raise AttributeError("LinkState is not initialized.")
|
@@ -662,7 +669,9 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
662
669
|
with self.conn:
|
663
670
|
rows = self.conn.execute(query, params)
|
664
671
|
if rows.rowcount < 1:
|
665
|
-
raise ValueError(
|
672
|
+
raise ValueError(
|
673
|
+
f"Node ID {node_id} not found or unauthorized deletion attempt."
|
674
|
+
)
|
666
675
|
except KeyError as exc:
|
667
676
|
log(ERROR, {"query": query, "data": params, "exception": exc})
|
668
677
|
|
@@ -690,26 +699,7 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
690
699
|
result: set[int] = {convert_sint64_to_uint64(row["node_id"]) for row in rows}
|
691
700
|
return result
|
692
701
|
|
693
|
-
def
|
694
|
-
"""Set `public_key` for the specified `node_id`."""
|
695
|
-
# Convert the uint64 value to sint64 for SQLite
|
696
|
-
sint64_node_id = convert_uint64_to_sint64(node_id)
|
697
|
-
|
698
|
-
# Check if the node exists in the `node` table
|
699
|
-
query = "SELECT 1 FROM node WHERE node_id = ?"
|
700
|
-
if not self.query(query, (sint64_node_id,)):
|
701
|
-
raise ValueError(f"Node {node_id} not found")
|
702
|
-
|
703
|
-
# Check if the public key is already in use in the `node` table
|
704
|
-
query = "SELECT 1 FROM node WHERE public_key = ?"
|
705
|
-
if self.query(query, (public_key,)):
|
706
|
-
raise ValueError("Public key already in use")
|
707
|
-
|
708
|
-
# Update the `node` table to set the public key for the given node ID
|
709
|
-
query = "UPDATE node SET public_key = ? WHERE node_id = ?"
|
710
|
-
self.query(query, (public_key, sint64_node_id))
|
711
|
-
|
712
|
-
def get_node_public_key(self, node_id: int) -> Optional[bytes]:
|
702
|
+
def get_node_public_key(self, node_id: int) -> bytes:
|
713
703
|
"""Get `public_key` for the specified `node_id`."""
|
714
704
|
# Convert the uint64 value to sint64 for SQLite
|
715
705
|
sint64_node_id = convert_uint64_to_sint64(node_id)
|
@@ -720,23 +710,10 @@ class SqliteLinkState(LinkState): # pylint: disable=R0904
|
|
720
710
|
|
721
711
|
# If no result is found, return None
|
722
712
|
if not rows:
|
723
|
-
raise ValueError(f"Node {node_id} not found")
|
713
|
+
raise ValueError(f"Node ID {node_id} not found")
|
724
714
|
|
725
|
-
# Return the public key
|
726
|
-
return rows[0]["public_key"]
|
727
|
-
|
728
|
-
def get_node_id(self, node_public_key: bytes) -> Optional[int]:
|
729
|
-
"""Retrieve stored `node_id` filtered by `node_public_keys`."""
|
730
|
-
query = "SELECT node_id FROM node WHERE public_key = :public_key;"
|
731
|
-
row = self.query(query, {"public_key": node_public_key})
|
732
|
-
if len(row) > 0:
|
733
|
-
node_id: int = row[0]["node_id"]
|
734
|
-
|
735
|
-
# Convert the sint64 value to uint64 after reading from SQLite
|
736
|
-
uint64_node_id = convert_sint64_to_uint64(node_id)
|
737
|
-
|
738
|
-
return uint64_node_id
|
739
|
-
return None
|
715
|
+
# Return the public key
|
716
|
+
return cast(bytes, rows[0]["public_key"])
|
740
717
|
|
741
718
|
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
742
719
|
def create_run(
|
@@ -107,3 +107,11 @@ def verify_signature(
|
|
107
107
|
return True
|
108
108
|
except InvalidSignature:
|
109
109
|
return False
|
110
|
+
|
111
|
+
|
112
|
+
def uses_nist_ec_curve(public_key: ec.EllipticCurvePublicKey) -> bool:
|
113
|
+
"""Return True if the provided key uses a NIST EC curve."""
|
114
|
+
return isinstance(
|
115
|
+
public_key.curve,
|
116
|
+
(ec.SECP192R1, ec.SECP224R1, ec.SECP256R1, ec.SECP384R1, ec.SECP521R1),
|
117
|
+
)
|
@@ -28,9 +28,13 @@ from flwr.cli.config_utils import get_fab_metadata
|
|
28
28
|
from flwr.common import Context, RecordDict, now
|
29
29
|
from flwr.common.constant import (
|
30
30
|
FAB_MAX_SIZE,
|
31
|
+
HEARTBEAT_DEFAULT_INTERVAL,
|
31
32
|
LOG_STREAM_INTERVAL,
|
32
33
|
NO_ACCOUNT_AUTH_MESSAGE,
|
33
34
|
NO_ARTIFACT_PROVIDER_MESSAGE,
|
35
|
+
NODE_NOT_FOUND_MESSAGE,
|
36
|
+
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
|
37
|
+
PUBLIC_KEY_NOT_VALID,
|
34
38
|
PULL_UNFINISHED_RUN_MESSAGE,
|
35
39
|
RUN_ID_NOT_FOUND_MESSAGE,
|
36
40
|
Status,
|
@@ -70,6 +74,7 @@ from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
70
74
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
71
75
|
from flwr.supercore.ffs import FfsFactory
|
72
76
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
77
|
+
from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
|
73
78
|
from flwr.superlink.artifact_provider import ArtifactProvider
|
74
79
|
from flwr.superlink.auth_plugin import ControlAuthnPlugin
|
75
80
|
|
@@ -389,13 +394,60 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
389
394
|
) -> CreateNodeCliResponse:
|
390
395
|
"""Add a SuperNode."""
|
391
396
|
log(INFO, "ControlServicer.CreateNodeCli")
|
392
|
-
|
397
|
+
|
398
|
+
# Verify public key
|
399
|
+
try:
|
400
|
+
# Attempt to deserialize public key
|
401
|
+
pub_key = bytes_to_public_key(request.public_key)
|
402
|
+
# Check if it's a NIST EC curve public key
|
403
|
+
if not uses_nist_ec_curve(pub_key):
|
404
|
+
err_msg = "The provided public key is not a NIST EC curve public key."
|
405
|
+
log(ERROR, "%s", err_msg)
|
406
|
+
raise ValueError(err_msg)
|
407
|
+
except (ValueError, AttributeError) as err:
|
408
|
+
log(ERROR, "%s", err)
|
409
|
+
context.abort(grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_NOT_VALID)
|
410
|
+
|
411
|
+
# Init link state
|
412
|
+
state = self.linkstate_factory.state()
|
413
|
+
node_id = 0
|
414
|
+
|
415
|
+
flwr_aid = shared_account_info.get().flwr_aid
|
416
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
417
|
+
try:
|
418
|
+
node_id = state.create_node(
|
419
|
+
owner_aid=flwr_aid,
|
420
|
+
public_key=request.public_key,
|
421
|
+
heartbeat_interval=HEARTBEAT_DEFAULT_INTERVAL,
|
422
|
+
)
|
423
|
+
|
424
|
+
except ValueError:
|
425
|
+
# Public key already in use
|
426
|
+
log(ERROR, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE)
|
427
|
+
context.abort(
|
428
|
+
grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
|
429
|
+
)
|
430
|
+
log(INFO, "[ControlServicer.CreateNodeCli] Created node_id=%s", node_id)
|
431
|
+
|
432
|
+
return CreateNodeCliResponse(node_id=node_id)
|
393
433
|
|
394
434
|
def DeleteNodeCli(
|
395
435
|
self, request: DeleteNodeCliRequest, context: grpc.ServicerContext
|
396
436
|
) -> DeleteNodeCliResponse:
|
397
437
|
"""Remove a SuperNode."""
|
398
438
|
log(INFO, "ControlServicer.RemoveNode")
|
439
|
+
|
440
|
+
# Init link state
|
441
|
+
state = self.linkstate_factory.state()
|
442
|
+
|
443
|
+
flwr_aid = shared_account_info.get().flwr_aid
|
444
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
445
|
+
try:
|
446
|
+
state.delete_node(owner_aid=flwr_aid, node_id=request.node_id)
|
447
|
+
except ValueError:
|
448
|
+
log(ERROR, NODE_NOT_FOUND_MESSAGE)
|
449
|
+
context.abort(grpc.StatusCode.NOT_FOUND, NODE_NOT_FOUND_MESSAGE)
|
450
|
+
|
399
451
|
return DeleteNodeCliResponse()
|
400
452
|
|
401
453
|
def ListNodesCli(
|
@@ -479,7 +531,7 @@ def _create_list_runs_response(
|
|
479
531
|
|
480
532
|
def _check_flwr_aid_exists(
|
481
533
|
flwr_aid: Optional[str], context: grpc.ServicerContext
|
482
|
-
) ->
|
534
|
+
) -> str:
|
483
535
|
"""Guard clause to check if `flwr_aid` exists."""
|
484
536
|
if flwr_aid is None:
|
485
537
|
context.abort(
|
@@ -487,6 +539,7 @@ def _check_flwr_aid_exists(
|
|
487
539
|
"️⛔️ Failed to fetch the account information.",
|
488
540
|
)
|
489
541
|
raise RuntimeError # This line is unreachable
|
542
|
+
return flwr_aid
|
490
543
|
|
491
544
|
|
492
545
|
def _check_flwr_aid_in_run(
|
{flwr_nightly-1.23.0.dev20251008.dist-info → flwr_nightly-1.23.0.dev20251010.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.dev20251010
|
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.dev20251008.dist-info → flwr_nightly-1.23.0.dev20251010.dist-info}/RECORD
RENAMED
@@ -87,10 +87,10 @@ flwr/cli/run/__init__.py,sha256=RPyB7KbYTFl6YRiilCch6oezxrLQrl1kijV7BMGkLbA,790
|
|
87
87
|
flwr/cli/run/run.py,sha256=ED1mDmO1PnSAgtVOrCeWwzwPm6t3aFYSs3Rh36BJzqk,8161
|
88
88
|
flwr/cli/stop.py,sha256=W7ynTYLm0-_1nC5Il4IaZTji6A3GCCm_0R-HQUudAsI,4988
|
89
89
|
flwr/cli/supernode/__init__.py,sha256=DVrTcyCg9NFll6glPLAAA6WPi7boxu6pFY_PRqIyHMk,893
|
90
|
-
flwr/cli/supernode/create.py,sha256=
|
91
|
-
flwr/cli/supernode/delete.py,sha256=
|
90
|
+
flwr/cli/supernode/create.py,sha256=9KvRO0IrZa4jw0sypAYxFlzzpjmnzf1KW71b-ySxeuI,6383
|
91
|
+
flwr/cli/supernode/delete.py,sha256=SmeKpvVtly8iisNpzJ-MNY-fe0gH2jwwilJs_NMo7do,4528
|
92
92
|
flwr/cli/supernode/ls.py,sha256=exeu-9fpkh27k2oyYNNT7uDhHgf8dlX0TR6WwMV8KIQ,8538
|
93
|
-
flwr/cli/utils.py,sha256=
|
93
|
+
flwr/cli/utils.py,sha256=66RqZdF0DKsqmR4ZqE9zZ1bX6opdxD2U50GV41WQDoY,14864
|
94
94
|
flwr/client/__init__.py,sha256=Q0MIF442vLIGSkcwHKq_sIfECQynLARJrumAscq2Q6E,1241
|
95
95
|
flwr/client/client.py,sha256=3HAchxvknKG9jYbB7swNyDj-e5vUWDuMKoLvbT7jCVM,7895
|
96
96
|
flwr/client/dpfedavg_numpy_client.py,sha256=3hul067cT2E9jBhzp7bFnFAZ_D2nWcIUEdHYE05FpzU,7404
|
@@ -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=PKzVHuA9_Hc6ZfKkYxU68R6Kh7lOMpeUJcH9U50yDW8,9583
|
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
|
@@ -136,7 +136,7 @@ flwr/common/event_log_plugin/__init__.py,sha256=ts3VAL3Fk6Grp1EK_1Qg_V-BfOof9F86
|
|
136
136
|
flwr/common/event_log_plugin/event_log_plugin.py,sha256=4SkVa1Ic-sPlICJShBuggXmXDcQtWQ1KDby4kthFNF0,2064
|
137
137
|
flwr/common/exit/__init__.py,sha256=8W7xaO1iw0vacgmQW7FTFbSh7csNv6XfsgIlnIbNF6U,978
|
138
138
|
flwr/common/exit/exit.py,sha256=DcXJfbpW1g-pQJqSZmps-1MZydd7T7RaarghIf2e4tU,3636
|
139
|
-
flwr/common/exit/exit_code.py,sha256=
|
139
|
+
flwr/common/exit/exit_code.py,sha256=g6g10X85mUlN9seFzXZeMNX8e3f2D290DZiF8p2HBAw,5885
|
140
140
|
flwr/common/exit/exit_handler.py,sha256=uzDdWwhKgc1w5csZS52b86kjmEApmDZKwMn_X0zDZZo,2126
|
141
141
|
flwr/common/exit/signal_handler.py,sha256=wqxykrwgmpFzmEMhpnlM7RtO0PnqIvYiSB1qYahZ5Sk,3710
|
142
142
|
flwr/common/grpc.py,sha256=nHnFC7E84pZVTvd6BhcSYWnGd0jf8t5UmGea04qvilM,9806
|
@@ -191,8 +191,8 @@ flwr/proto/clientappio_pb2.py,sha256=vJjzwWydhg7LruK8cvRAeVQeHPsJztgdIW9nyiPBZF0
|
|
191
191
|
flwr/proto/clientappio_pb2.pyi,sha256=XbFvpZvvrS7QcH5AFXfpRGl4hQvhd3QdKO6x0oTlCCU,165
|
192
192
|
flwr/proto/clientappio_pb2_grpc.py,sha256=iobNROP0qvn5zddx7k-uIi_dJWP3T_BRp_kbKq086i8,17550
|
193
193
|
flwr/proto/clientappio_pb2_grpc.pyi,sha256=Ytf1O1ktKB0Vsuc3AWLIErGjIJYokzKYzi2uA7mdMeg,4785
|
194
|
-
flwr/proto/control_pb2.py,sha256=
|
195
|
-
flwr/proto/control_pb2.pyi,sha256=
|
194
|
+
flwr/proto/control_pb2.py,sha256=zPejyt6qE3bzHHpSs_RJkTiOJI6YbtRVuNmmNrQgazc,7744
|
195
|
+
flwr/proto/control_pb2.pyi,sha256=iBpjs4mVgpa_QeW-01hUk179VyxNa6_vWlDFXApISCU,13386
|
196
196
|
flwr/proto/control_pb2_grpc.py,sha256=Of6tnetwnzvJfJWEXhuTFszz4T_IJbdLJD9uRXBdG04,17182
|
197
197
|
flwr/proto/control_pb2_grpc.pyi,sha256=9z_MRrexz0o3_owST8SvhCjQIIAn-SwMWKIihPHzqdM,4801
|
198
198
|
flwr/proto/error_pb2.py,sha256=PQVWrfjVPo88ql_KgV9nCxyQNCcV9PVfmcw7sOzTMro,1084
|
@@ -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=
|
227
|
-
flwr/proto/node_pb2.pyi,sha256=
|
226
|
+
flwr/proto/node_pb2.py,sha256=C8Pj-6rGP0t44eb4OHkze8wHDf_b_5g8S906tyuqfUI,1572
|
227
|
+
flwr/proto/node_pb2.pyi,sha256=hxMkWNE2NjA4j9RVGz8qrfotd4B_1sgdcO4flv-CjHY,2368
|
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
|
@@ -308,19 +308,19 @@ flwr/server/superlink/fleet/grpc_rere/__init__.py,sha256=ahDJJ1e-lDxBpeBMgPk7YZt
|
|
308
308
|
flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py,sha256=xkmbYjzt7IZCSvtjgyZVTC7Mm18t7pUB6OUxN_Th8nU,8847
|
309
309
|
flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py,sha256=TNK_0-Cpt_s9qTVjm9imke2sF_o_Vb5c-4WDnVg5UGA,5998
|
310
310
|
flwr/server/superlink/fleet/message_handler/__init__.py,sha256=fHsRV0KvJ8HtgSA4_YBsEzuhJLjO8p6xx4aCY2oE1p4,731
|
311
|
-
flwr/server/superlink/fleet/message_handler/message_handler.py,sha256
|
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
|
313
313
|
flwr/server/superlink/fleet/rest_rere/rest_api.py,sha256=mxWKwGpgHPqd7cGFqd2ASnR-KZduIzLfT-d2yiNCqQ0,9257
|
314
314
|
flwr/server/superlink/fleet/vce/__init__.py,sha256=XOKbAWOzlCqEOQ3M2cBYkH7HKA7PxlbCJMunt-ty-DY,784
|
315
315
|
flwr/server/superlink/fleet/vce/backend/__init__.py,sha256=PPH89Yqd1XKm-sRJN6R0WQlKT_b4v54Kzl2yzHAFzM8,1437
|
316
316
|
flwr/server/superlink/fleet/vce/backend/backend.py,sha256=cSrHZ5SjCCvy4vI0pgsyjtx3cDMuMQve8KcKkK-dWWo,2196
|
317
317
|
flwr/server/superlink/fleet/vce/backend/raybackend.py,sha256=cBZYTmfiAsb1HmVUmOQXYLU-UJmJTFWkj1wW4RYRDuc,7218
|
318
|
-
flwr/server/superlink/fleet/vce/vce_api.py,sha256=
|
318
|
+
flwr/server/superlink/fleet/vce/vce_api.py,sha256=cWlRkgn7G_cS4jw6g1ua6ZcGsyF-1YwP9IgSQ6Dk62Q,13209
|
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=oga9vECdDFSFs6egfOS9iXMQmgc5pWgDSfGwi76XOp8,28082
|
321
|
+
flwr/server/superlink/linkstate/linkstate.py,sha256=08C1v7dAMOo5346tKNDJ7Y0OCxhpdPdRU11xPwmOoDw,13400
|
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=2a3EP-PFUtg-LUJlct0PQloQ4kuKb0xe18CBsYZHb6U,45361
|
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
|
@@ -393,7 +393,7 @@ flwr/supercore/object_store/object_store.py,sha256=J-rI3X7ET-F6dqOyM-UfHKCCQtPJ_
|
|
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
395
|
flwr/supercore/primitives/__init__.py,sha256=Tx8GOjnmMo8Y74RsDGrMpfr-E0Nl8dcUDF784_ge6F8,745
|
396
|
-
flwr/supercore/primitives/asymmetric.py,sha256=
|
396
|
+
flwr/supercore/primitives/asymmetric.py,sha256=wpO0o0G_vStRknFitw2SqyIBSzaBfuXfMc44u-UcxTs,3774
|
397
397
|
flwr/supercore/superexec/__init__.py,sha256=XKX208hZ6a9gZ4KT9kMqfpCtp_8VGxekzKFfHSu2esQ,707
|
398
398
|
flwr/supercore/superexec/plugin/__init__.py,sha256=GNwq8uNdE8RB7ywEFRAvKjLFzgS3YXgz39-HBGsemWw,1035
|
399
399
|
flwr/supercore/superexec/plugin/base_exec_plugin.py,sha256=fL-Ufc9Dp56OhWOzNSJUc7HumbkuSDYqZKwde2opG4g,2074
|
@@ -415,7 +415,7 @@ flwr/superlink/servicer/control/control_account_auth_interceptor.py,sha256=Tbi4W
|
|
415
415
|
flwr/superlink/servicer/control/control_event_log_interceptor.py,sha256=5uBl6VcJlUOgCF0d4kmsmJc1Rs1qxyouaZv0-uH2axs,5969
|
416
416
|
flwr/superlink/servicer/control/control_grpc.py,sha256=MRCaX4I2a5ogjKmhtFs6Mj-VdWemxL2h3gU9QbQmvCA,4183
|
417
417
|
flwr/superlink/servicer/control/control_license_interceptor.py,sha256=T3AzmRt-PPwyTq3hrdpmZHQd5_CpPOk7TtnFZrB-JRY,3349
|
418
|
-
flwr/superlink/servicer/control/control_servicer.py,sha256=
|
418
|
+
flwr/superlink/servicer/control/control_servicer.py,sha256=qQHmhKjn2iIssO63dHSnrK6dZsh_jZrxpSAYH_qV5gA,20550
|
419
419
|
flwr/supernode/__init__.py,sha256=KgeCaVvXWrU3rptNR1y0oBp4YtXbAcrnCcJAiOoWkI4,707
|
420
420
|
flwr/supernode/cli/__init__.py,sha256=JuEMr0-s9zv-PEWKuLB9tj1ocNfroSyNJ-oyv7ati9A,887
|
421
421
|
flwr/supernode/cli/flower_supernode.py,sha256=7aBm0z03OU-npVd1onLCvUotyhSvlZLxAnFkGVMhZcw,8670
|
@@ -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.dev20251010.dist-info/METADATA,sha256=HtYyoP2UzTQPv3_ixhl6ZPE6VLR2UhzoiWLCspZ9qCM,14559
|
434
|
+
flwr_nightly-1.23.0.dev20251010.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
435
|
+
flwr_nightly-1.23.0.dev20251010.dist-info/entry_points.txt,sha256=hxHD2ixb_vJFDOlZV-zB4Ao32_BQlL34ftsDh1GXv14,420
|
436
|
+
flwr_nightly-1.23.0.dev20251010.dist-info/RECORD,,
|
{flwr_nightly-1.23.0.dev20251008.dist-info → flwr_nightly-1.23.0.dev20251010.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|